blob: cb1ced692f85ecddac62a9acd42be165aa21cfe5 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cast/sender/channel/cast_auth_util.h"
#include <openssl/rand.h>
#include <algorithm>
#include <memory>
#include "cast/common/certificate/cast_cert_validator.h"
#include "cast/common/certificate/cast_cert_validator_internal.h"
#include "cast/common/certificate/cast_crl.h"
#include "cast/common/channel/proto/cast_channel.pb.h"
#include "platform/api/time.h"
#include "platform/base/error.h"
#include "util/osp_logging.h"
namespace openscreen {
namespace cast {
using ::cast::channel::AuthResponse;
using ::cast::channel::CastMessage;
using ::cast::channel::DeviceAuthMessage;
using ::cast::channel::HashAlgorithm;
namespace {
#define PARSE_ERROR_PREFIX "Failed to parse auth message: "
// The maximum number of days a cert can live for.
constexpr int kMaxSelfSignedCertLifetimeInDays = 4;
// The size of the nonce challenge in bytes.
constexpr int kNonceSizeInBytes = 16;
// The number of hours after which a nonce is regenerated.
constexpr int kNonceExpirationTimeInHours = 24;
// Extracts an embedded DeviceAuthMessage payload from an auth challenge reply
// message.
Error ParseAuthMessage(const CastMessage& challenge_reply,
DeviceAuthMessage* auth_message) {
if (challenge_reply.payload_type() !=
::cast::channel::CastMessage_PayloadType_BINARY) {
return Error(Error::Code::kCastV2WrongPayloadType,
PARSE_ERROR_PREFIX "Wrong payload type in challenge reply");
}
if (!challenge_reply.has_payload_binary()) {
return Error(Error::Code::kCastV2NoPayload, PARSE_ERROR_PREFIX
"Payload type is binary but payload_binary field not set");
}
if (!auth_message->ParseFromString(challenge_reply.payload_binary())) {
return Error(Error::Code::kCastV2PayloadParsingFailed, PARSE_ERROR_PREFIX
"Cannot parse binary payload into DeviceAuthMessage");
}
if (auth_message->has_error()) {
std::stringstream ss;
ss << PARSE_ERROR_PREFIX "Auth message error: "
<< auth_message->error().error_type();
return Error(Error::Code::kCastV2MessageError, ss.str());
}
if (!auth_message->has_response()) {
return Error(Error::Code::kCastV2NoResponse,
PARSE_ERROR_PREFIX "Auth message has no response field");
}
return Error::None();
}
class CastNonce {
public:
static CastNonce* GetInstance() {
static CastNonce* cast_nonce = new CastNonce();
return cast_nonce;
}
static const std::string& Get() {
GetInstance()->EnsureNonceTimely();
return GetInstance()->nonce_;
}
private:
CastNonce() : nonce_(kNonceSizeInBytes, 0) { GenerateNonce(); }
void GenerateNonce() {
OSP_CHECK_EQ(
RAND_bytes(reinterpret_cast<uint8_t*>(&nonce_[0]), kNonceSizeInBytes),
1);
nonce_generation_time_ = GetWallTimeSinceUnixEpoch();
}
void EnsureNonceTimely() {
if (GetWallTimeSinceUnixEpoch() >
(nonce_generation_time_ +
std::chrono::hours(kNonceExpirationTimeInHours))) {
GenerateNonce();
}
}
// The nonce challenge to send to the Cast receiver.
// The nonce is updated daily.
std::string nonce_;
std::chrono::seconds nonce_generation_time_;
};
// Maps Error::Code from certificate verification to Error.
// If crl_required is set to false, all revocation related errors are ignored.
Error MapToOpenscreenError(Error::Code error, bool crl_required) {
switch (error) {
case Error::Code::kErrCertsMissing:
return Error(Error::Code::kCastV2PeerCertEmpty,
"Failed to locate certificates.");
case Error::Code::kErrCertsParse:
return Error(Error::Code::kErrCertsParse,
"Failed to parse certificates.");
case Error::Code::kErrCertsDateInvalid:
return Error(Error::Code::kCastV2CertNotSignedByTrustedCa,
"Failed date validity check.");
case Error::Code::kErrCertsVerifyGeneric:
return Error(Error::Code::kCastV2CertNotSignedByTrustedCa,
"Failed with a generic certificate verification error.");
case Error::Code::kErrCertsRestrictions:
return Error(Error::Code::kCastV2CertNotSignedByTrustedCa,
"Failed certificate restrictions.");
case Error::Code::kErrCertsVerifyUntrustedCert:
return Error(Error::Code::kCastV2CertNotSignedByTrustedCa,
"Failed with untrusted certificate.");
case Error::Code::kErrCrlInvalid:
// This error is only encountered if |crl_required| is true.
OSP_DCHECK(crl_required);
return Error(Error::Code::kErrCrlInvalid,
"Failed to provide a valid CRL.");
case Error::Code::kErrCertsRevoked:
return Error(Error::Code::kErrCertsRevoked,
"Failed certificate revocation check.");
case Error::Code::kNone:
return Error::None();
default:
return Error(Error::Code::kCastV2CertNotSignedByTrustedCa,
"Failed verifying cast device certificate.");
}
return Error::None();
}
Error VerifyAndMapDigestAlgorithm(HashAlgorithm response_digest_algorithm,
DigestAlgorithm* digest_algorithm,
bool enforce_sha256_checking) {
switch (response_digest_algorithm) {
case ::cast::channel::SHA1:
if (enforce_sha256_checking) {
return Error(Error::Code::kCastV2DigestUnsupported,
"Unsupported digest algorithm.");
}
*digest_algorithm = DigestAlgorithm::kSha1;
break;
case ::cast::channel::SHA256:
*digest_algorithm = DigestAlgorithm::kSha256;
break;
default:
return Error::Code::kCastV2DigestUnsupported;
}
return Error::None();
}
} // namespace
// static
AuthContext AuthContext::Create() {
return AuthContext(CastNonce::Get());
}
AuthContext::AuthContext(const std::string& nonce) : nonce_(nonce) {}
AuthContext::~AuthContext() {}
Error AuthContext::VerifySenderNonce(const std::string& nonce_response,
bool enforce_nonce_checking) const {
if (nonce_ != nonce_response) {
if (enforce_nonce_checking) {
return Error(Error::Code::kCastV2SenderNonceMismatch,
"Sender nonce mismatched.");
}
}
return Error::None();
}
Error VerifyTLSCertificateValidity(X509* peer_cert,
std::chrono::seconds verification_time) {
// Ensure the peer cert is valid and doesn't have an excessive remaining
// lifetime. Although it is not verified as an X.509 certificate, the entire
// structure is signed by the AuthResponse, so the validity field from X.509
// is repurposed as this signature's expiration.
DateTime not_before;
DateTime not_after;
if (!GetCertValidTimeRange(peer_cert, &not_before, &not_after)) {
return Error(Error::Code::kErrCertsParse,
PARSE_ERROR_PREFIX "Parsing validity fields failed.");
}
std::chrono::seconds lifetime_limit =
verification_time +
std::chrono::hours(24 * kMaxSelfSignedCertLifetimeInDays);
DateTime verification_time_exploded = {};
DateTime lifetime_limit_exploded = {};
OSP_CHECK(DateTimeFromSeconds(verification_time.count(),
&verification_time_exploded));
OSP_CHECK(
DateTimeFromSeconds(lifetime_limit.count(), &lifetime_limit_exploded));
if (verification_time_exploded < not_before) {
return Error(Error::Code::kCastV2TlsCertValidStartDateInFuture,
PARSE_ERROR_PREFIX
"Certificate's valid start date is in the future.");
}
if (not_after < verification_time_exploded) {
return Error(Error::Code::kCastV2TlsCertExpired,
PARSE_ERROR_PREFIX "Certificate has expired.");
}
if (lifetime_limit_exploded < not_after) {
return Error(Error::Code::kCastV2TlsCertValidityPeriodTooLong,
PARSE_ERROR_PREFIX "Peer cert lifetime is too long.");
}
return Error::None();
}
ErrorOr<CastDeviceCertPolicy> VerifyCredentialsImpl(
const AuthResponse& response,
const std::vector<uint8_t>& signature_input,
const CRLPolicy& crl_policy,
TrustStore* cast_trust_store,
TrustStore* crl_trust_store,
const DateTime& verification_time,
bool enforce_sha256_checking);
ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReplyImpl(
const CastMessage& challenge_reply,
X509* peer_cert,
const AuthContext& auth_context,
const CRLPolicy& crl_policy,
TrustStore* cast_trust_store,
TrustStore* crl_trust_store,
const DateTime& verification_time) {
DeviceAuthMessage auth_message;
Error result = ParseAuthMessage(challenge_reply, &auth_message);
if (!result.ok()) {
return result;
}
result = VerifyTLSCertificateValidity(peer_cert,
DateTimeToSeconds(verification_time));
if (!result.ok()) {
return result;
}
const AuthResponse& response = auth_message.response();
const std::string& nonce_response = response.sender_nonce();
result = auth_context.VerifySenderNonce(nonce_response);
if (!result.ok()) {
return result;
}
int cert_len = i2d_X509(peer_cert, nullptr);
if (cert_len <= 0) {
return Error(Error::Code::kErrCertsParse, "Serializing cert failed.");
}
size_t nonce_response_size = nonce_response.size();
std::vector<uint8_t> nonce_plus_peer_cert_der(nonce_response_size + cert_len,
0);
std::copy(nonce_response.begin(), nonce_response.end(),
&nonce_plus_peer_cert_der[0]);
uint8_t* data = &nonce_plus_peer_cert_der[nonce_response_size];
if (!i2d_X509(peer_cert, &data)) {
return Error(Error::Code::kErrCertsParse, "Serializing cert failed.");
}
return VerifyCredentialsImpl(response, nonce_plus_peer_cert_der, crl_policy,
cast_trust_store, crl_trust_store,
verification_time, false);
}
ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReply(
const CastMessage& challenge_reply,
X509* peer_cert,
const AuthContext& auth_context) {
DateTime now = {};
OSP_CHECK(DateTimeFromSeconds(GetWallTimeSinceUnixEpoch().count(), &now));
CRLPolicy policy = CRLPolicy::kCrlOptional;
return AuthenticateChallengeReplyImpl(
challenge_reply, peer_cert, auth_context, policy,
/* cast_trust_store */ nullptr, /* crl_trust_store */ nullptr, now);
}
ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReplyForTest(
const CastMessage& challenge_reply,
X509* peer_cert,
const AuthContext& auth_context,
CRLPolicy crl_policy,
TrustStore* cast_trust_store,
TrustStore* crl_trust_store,
const DateTime& verification_time) {
return AuthenticateChallengeReplyImpl(
challenge_reply, peer_cert, auth_context, crl_policy, cast_trust_store,
crl_trust_store, verification_time);
}
// This function does the following
//
// * Verifies that the certificate chain |response.client_auth_certificate| +
// |response.intermediate_certificate| is valid and chains to a trusted Cast
// root. The list of trusted Cast roots can be overrided by providing a
// non-nullptr |cast_trust_store|. The certificate is verified at
// |verification_time|.
//
// * Verifies that none of the certificates in the chain are revoked based on
// the CRL provided in the response |response.crl|. The CRL is verified to be
// valid and its issuer certificate chains to a trusted Cast CRL root. The
// list of trusted Cast CRL roots can be overrided by providing a non-nullptr
// |crl_trust_store|. If |crl_policy| is kCrlOptional then the result of
// revocation checking is ignored. The CRL is verified at |verification_time|.
//
// * Verifies that |response.signature| matches the signature of
// |signature_input| by |response.client_auth_certificate|'s public key.
ErrorOr<CastDeviceCertPolicy> VerifyCredentialsImpl(
const AuthResponse& response,
const std::vector<uint8_t>& signature_input,
const CRLPolicy& crl_policy,
TrustStore* cast_trust_store,
TrustStore* crl_trust_store,
const DateTime& verification_time,
bool enforce_sha256_checking) {
if (response.signature().empty() && !signature_input.empty()) {
return Error(Error::Code::kCastV2SignatureEmpty, "Signature is empty.");
}
// Verify the certificate
std::unique_ptr<CertVerificationContext> verification_context;
// Build a single vector containing the certificate chain.
std::vector<std::string> cert_chain;
cert_chain.push_back(response.client_auth_certificate());
cert_chain.insert(cert_chain.end(),
response.intermediate_certificate().begin(),
response.intermediate_certificate().end());
// Parse the CRL.
std::unique_ptr<CastCRL> crl;
if (!response.crl().empty()) {
crl = ParseAndVerifyCRL(response.crl(), verification_time, crl_trust_store);
}
// Perform certificate verification.
CastDeviceCertPolicy device_policy;
Error verify_result =
VerifyDeviceCert(cert_chain, verification_time, &verification_context,
&device_policy, crl.get(), crl_policy, cast_trust_store);
// Handle and report errors.
Error result = MapToOpenscreenError(verify_result.code(),
crl_policy == CRLPolicy::kCrlRequired);
if (!result.ok()) {
return result;
}
// The certificate is verified at this point.
DigestAlgorithm digest_algorithm;
Error digest_result = VerifyAndMapDigestAlgorithm(
response.hash_algorithm(), &digest_algorithm, enforce_sha256_checking);
if (!digest_result.ok()) {
return digest_result;
}
ConstDataSpan signature = {
reinterpret_cast<const uint8_t*>(response.signature().data()),
static_cast<uint32_t>(response.signature().size())};
ConstDataSpan siginput = {signature_input.data(),
static_cast<uint32_t>(signature_input.size())};
if (!verification_context->VerifySignatureOverData(signature, siginput,
digest_algorithm)) {
return Error(Error::Code::kCastV2SignedBlobsMismatch,
"Failed verifying signature over data.");
}
return device_policy;
}
ErrorOr<CastDeviceCertPolicy> VerifyCredentials(
const AuthResponse& response,
const std::vector<uint8_t>& signature_input,
bool enforce_revocation_checking,
bool enforce_sha256_checking) {
DateTime now = {};
OSP_CHECK(DateTimeFromSeconds(GetWallTimeSinceUnixEpoch().count(), &now));
CRLPolicy policy = (enforce_revocation_checking) ? CRLPolicy::kCrlRequired
: CRLPolicy::kCrlOptional;
return VerifyCredentialsImpl(response, signature_input, policy, nullptr,
nullptr, now, enforce_sha256_checking);
}
ErrorOr<CastDeviceCertPolicy> VerifyCredentialsForTest(
const AuthResponse& response,
const std::vector<uint8_t>& signature_input,
CRLPolicy crl_policy,
TrustStore* cast_trust_store,
TrustStore* crl_trust_store,
const DateTime& verification_time,
bool enforce_sha256_checking) {
return VerifyCredentialsImpl(response, signature_input, crl_policy,
cast_trust_store, crl_trust_store,
verification_time, enforce_sha256_checking);
}
} // namespace cast
} // namespace openscreen