diff --git a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h index 0f27a7229a..f7ec7c516f 100644 --- a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h +++ b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h @@ -33,6 +33,7 @@ using ::std::optional; using ::std::string; using ::std::tuple; using ::std::vector; +using ::std::pair; // --------------------------------------------------------------------------- // Miscellaneous utilities. @@ -119,6 +120,12 @@ optional> encryptAes128Gcm(const vector& key, const vec optional, vector>>> createEcKeyPairAndAttestation( const vector& challenge, const vector& applicationId); +// Like createEcKeyPairAndAttestation() but allows you to choose the public key. +// +optional>> createAttestationForEcPublicKey( + const vector& publicKey, const vector& challenge, + const vector& applicationId); + // Creates an 256-bit EC key using the NID_X9_62_prime256v1 curve, returns the // PKCS#8 encoded key-pair. // @@ -155,6 +162,12 @@ optional> ecKeyPairGetPkcs12(const vector& keyPair, con // optional> signEcDsa(const vector& key, const vector& data); +// Like signEcDsa() but instead of taking the data to be signed, takes a digest +// of it instead. +// +optional> signEcDsaDigest(const vector& key, + const vector& dataDigest); + // Calculates the HMAC with SHA-256 for |data| using |key|. The calculated HMAC // is returned and will be 32 bytes. // @@ -175,6 +188,27 @@ bool checkEcDsaSignature(const vector& digest, const vector& s // optional> certificateChainGetTopMostKey(const vector& certificateChain); +// Extracts the public-key from the top-most certificate in |certificateChain| +// (which should be a concatenated chain of DER-encoded X.509 certificates). +// +// Return offset and size of the public-key +// +optional> certificateFindPublicKey(const vector& x509Certificate); + +// Extracts the TbsCertificate from the top-most certificate in |certificateChain| +// (which should be a concatenated chain of DER-encoded X.509 certificates). +// +// Return offset and size of the TbsCertificate +// +optional> certificateTbsCertificate(const vector& x509Certificate); + +// Extracts the Signature from the top-most certificate in |certificateChain| +// (which should be a concatenated chain of DER-encoded X.509 certificates). +// +// Return offset and size of the Signature +// +optional> certificateFindSignature(const vector& x509Certificate); + // Generates a X.509 certificate for |publicKey| (which must be in the format // returned by ecKeyPairGetPublicKey()). // @@ -231,6 +265,11 @@ optional>> certificateChainSplit(const vector& c // bool certificateChainValidate(const vector& certificateChain); +// Returns true if |certificate| is signed by |publicKey|. +// +bool certificateSignedByPublicKey(const vector& certificate, + const vector& publicKey); + // Signs |data| and |detachedContent| with |key| (which must be in the format // returned by ecKeyPairGetPrivateKey()). // @@ -243,6 +282,21 @@ optional> coseSignEcDsa(const vector& key, const vector const vector& detachedContent, const vector& certificateChain); +// Creates a COSE_Signature1 where |signatureToBeSigned| is the ECDSA signature +// of the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process". +// +// The |signatureToBeSigned| is expected to be 64 bytes and contain the R value, +// then the S value. +// +// The |data| parameter will be included in the COSE_Sign1 CBOR. +// +// If |certificateChain| is non-empty it's included in the 'x5chain' +// protected header element (as as described in'draft-ietf-cose-x509-04'). +// +optional> coseSignEcDsaWithSignature(const vector& signatureToBeSigned, + const vector& data, + const vector& certificateChain); + // Checks that |signatureCoseSign1| (in COSE_Sign1 format) is a valid signature // made with |public_key| (which must be in the format returned by // ecKeyPairGetPublicKey()) where |detachedContent| is the detached content. @@ -251,9 +305,23 @@ bool coseCheckEcDsaSignature(const vector& signatureCoseSign1, const vector& detachedContent, const vector& publicKey); +// Converts a DER-encoded signature to the format used in 'signature' bstr in COSE_Sign1. +bool ecdsaSignatureDerToCose(const vector& ecdsaDerSignature, + vector& ecdsaCoseSignature); + +// Converts from the format in in 'signature' bstr in COSE_Sign1 to DER encoding. +bool ecdsaSignatureCoseToDer(const vector& ecdsaCoseSignature, + vector& ecdsaDerSignature); + // Extracts the payload from a COSE_Sign1. optional> coseSignGetPayload(const vector& signatureCoseSign1); +// Extracts the signature (of the ToBeSigned CBOR) from a COSE_Sign1. +optional> coseSignGetSignature(const vector& signatureCoseSign1); + +// Extracts the signature algorithm from a COSE_Sign1. +optional coseSignGetAlg(const vector& signatureCoseSign1); + // Extracts the X.509 certificate chain, if present. Returns the data as a // concatenated chain of DER-encoded X.509 certificates // @@ -269,6 +337,16 @@ optional> coseSignGetX5Chain(const vector& signatureCos optional> coseMac0(const vector& key, const vector& data, const vector& detachedContent); +// Creates a COSE_Mac0 where |digestToBeMaced| is the HMAC-SHA256 +// of the ToBeMaced CBOR from RFC 8051 "6.3. How to Compute and Verify a MAC". +// +// The |digestToBeMaced| is expected to be 32 bytes. +// +// The |data| parameter will be included in the COSE_Mac0 CBOR. +// +optional> coseMacWithDigest(const vector& digestToBeMaced, + const vector& data); + // --------------------------------------------------------------------------- // Utility functions specific to IdentityCredential. // --------------------------------------------------------------------------- diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp index e9d5d6c7cc..8e099e7d2f 100644 --- a/identity/support/src/IdentityCredentialSupport.cpp +++ b/identity/support/src/IdentityCredentialSupport.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -684,6 +685,48 @@ static bool parseX509Certificates(const vector& certificateChain, return true; } +bool certificateSignedByPublicKey(const vector& certificate, + const vector& publicKey) { + const unsigned char* p = certificate.data(); + auto x509 = X509_Ptr(d2i_X509(nullptr, &p, certificate.size())); + if (x509 == nullptr) { + LOG(ERROR) << "Error parsing X509 certificate"; + return false; + } + + auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + auto point = EC_POINT_Ptr(EC_POINT_new(group.get())); + if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) != + 1) { + LOG(ERROR) << "Error decoding publicKey"; + return false; + } + auto ecKey = EC_KEY_Ptr(EC_KEY_new()); + auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new()); + if (ecKey.get() == nullptr || pkey.get() == nullptr) { + LOG(ERROR) << "Memory allocation failed"; + return false; + } + if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) { + LOG(ERROR) << "Error setting group"; + return false; + } + if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) { + LOG(ERROR) << "Error setting point"; + return false; + } + if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) { + LOG(ERROR) << "Error setting key"; + return false; + } + + if (X509_verify(x509.get(), pkey.get()) != 1) { + return false; + } + + return true; +} + // TODO: Right now the only check we perform is to check that each certificate // is signed by its successor. We should - but currently don't - also check // things like valid dates etc. @@ -770,7 +813,8 @@ vector sha256(const vector& data) { return ret; } -optional> signEcDsa(const vector& key, const vector& data) { +optional> signEcDsaDigest(const vector& key, + const vector& dataDigest) { auto bn = BIGNUM_Ptr(BN_bin2bn(key.data(), key.size(), nullptr)); if (bn.get() == nullptr) { LOG(ERROR) << "Error creating BIGNUM"; @@ -783,8 +827,7 @@ optional> signEcDsa(const vector& key, const vector> signEcDsa(const vector& key, const vector> signEcDsa(const vector& key, const vector& data) { + return signEcDsaDigest(key, sha256(data)); +} + optional> hmacSha256(const vector& key, const vector& data) { HMAC_CTX ctx; HMAC_CTX_init(&ctx); @@ -955,6 +1002,51 @@ optional, vector>>> createEcKeyPairAnd return make_pair(keyPair, attestationCert.value()); } +optional>> createAttestationForEcPublicKey( + const vector& publicKey, const vector& challenge, + const vector& applicationId) { + auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + auto point = EC_POINT_Ptr(EC_POINT_new(group.get())); + if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) != + 1) { + LOG(ERROR) << "Error decoding publicKey"; + return {}; + } + auto ecKey = EC_KEY_Ptr(EC_KEY_new()); + auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new()); + if (ecKey.get() == nullptr || pkey.get() == nullptr) { + LOG(ERROR) << "Memory allocation failed"; + return {}; + } + if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) { + LOG(ERROR) << "Error setting group"; + return {}; + } + if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) { + LOG(ERROR) << "Error setting point"; + return {}; + } + if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) { + LOG(ERROR) << "Error setting key"; + return {}; + } + + uint64_t now = (std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()). + count()/ 1000000000); + uint64_t secondsInOneYear = 365 * 24 * 60 * 60; + uint64_t expireTimeMs = (now + secondsInOneYear) * 1000; + + optional>> attestationCert = + createAttestation(pkey.get(), applicationId, challenge, now * 1000, expireTimeMs); + if (!attestationCert) { + LOG(ERROR) << "Error create attestation from key and challenge"; + return {}; + } + + return attestationCert.value(); +} + optional> createEcKeyPair() { auto ec_key = EC_KEY_Ptr(EC_KEY_new()); auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new()); @@ -1477,6 +1569,120 @@ optional> certificateChainGetTopMostKey(const vector& c return publicKey; } +optional> certificateFindPublicKey(const vector& x509Certificate) { + vector certs; + if (!parseX509Certificates(x509Certificate, certs)) { + return {}; + } + if (certs.size() < 1) { + LOG(ERROR) << "No certificates in chain"; + return {}; + } + + auto pkey = EVP_PKEY_Ptr(X509_get_pubkey(certs[0].get())); + if (pkey.get() == nullptr) { + LOG(ERROR) << "No public key"; + return {}; + } + + auto ecKey = EC_KEY_Ptr(EVP_PKEY_get1_EC_KEY(pkey.get())); + if (ecKey.get() == nullptr) { + LOG(ERROR) << "Failed getting EC key"; + return {}; + } + + auto ecGroup = EC_KEY_get0_group(ecKey.get()); + auto ecPoint = EC_KEY_get0_public_key(ecKey.get()); + int size = EC_POINT_point2oct(ecGroup, ecPoint, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, + nullptr); + if (size == 0) { + LOG(ERROR) << "Error generating public key encoding"; + return {}; + } + vector publicKey; + publicKey.resize(size); + EC_POINT_point2oct(ecGroup, ecPoint, POINT_CONVERSION_UNCOMPRESSED, publicKey.data(), + publicKey.size(), nullptr); + + size_t publicKeyOffset = 0; + size_t publicKeySize = (size_t)size; + void* location = memmem((const void*)x509Certificate.data(), x509Certificate.size(), + (const void*)publicKey.data(), publicKey.size()); + + if (location == NULL) { + LOG(ERROR) << "Error finding publicKey from x509Certificate"; + return {}; + } + publicKeyOffset = (size_t)((const char*)location - (const char*)x509Certificate.data()); + + return std::make_pair(publicKeyOffset, publicKeySize); +} + +optional> certificateTbsCertificate(const vector& x509Certificate) { + vector certs; + if (!parseX509Certificates(x509Certificate, certs)) { + return {}; + } + if (certs.size() < 1) { + LOG(ERROR) << "No certificates in chain"; + return {}; + } + + unsigned char* buf = NULL; + int len = i2d_re_X509_tbs(certs[0].get(), &buf); + if ((len < 0) || (buf == NULL)) { + LOG(ERROR) << "fail to extract tbsCertificate in x509Certificate"; + return {}; + } + + vector tbsCertificate(len); + memcpy(tbsCertificate.data(), buf, len); + + size_t tbsCertificateOffset = 0; + size_t tbsCertificateSize = (size_t)len; + void* location = memmem((const void*)x509Certificate.data(), x509Certificate.size(), + (const void*)tbsCertificate.data(), tbsCertificate.size()); + + if (location == NULL) { + LOG(ERROR) << "Error finding tbsCertificate from x509Certificate"; + return {}; + } + tbsCertificateOffset = (size_t)((const char*)location - (const char*)x509Certificate.data()); + + return std::make_pair(tbsCertificateOffset, tbsCertificateSize); +} + +optional> certificateFindSignature(const vector& x509Certificate) { + vector certs; + if (!parseX509Certificates(x509Certificate, certs)) { + return {}; + } + if (certs.size() < 1) { + LOG(ERROR) << "No certificates in chain"; + return {}; + } + + ASN1_BIT_STRING* psig; + X509_ALGOR* palg; + X509_get0_signature((const ASN1_BIT_STRING**)&psig, (const X509_ALGOR**)&palg, certs[0].get()); + + vector signature(psig->length); + memcpy(signature.data(), psig->data, psig->length); + + size_t signatureOffset = 0; + size_t signatureSize = (size_t)psig->length; + void* location = memmem((const void*)x509Certificate.data(), x509Certificate.size(), + (const void*)signature.data(), signature.size()); + + if (location == NULL) { + LOG(ERROR) << "Error finding signature from x509Certificate"; + return {}; + } + signatureOffset = (size_t)((const char*)location - (const char*)x509Certificate.data()); + + return std::make_pair(signatureOffset, signatureSize); +} + // --------------------------------------------------------------------------- // COSE Utility Functions // --------------------------------------------------------------------------- @@ -1574,6 +1780,55 @@ bool ecdsaSignatureDerToCose(const vector& ecdsaDerSignature, return true; } +optional> coseSignEcDsaWithSignature(const vector& signatureToBeSigned, + const vector& data, + const vector& certificateChain) { + if (signatureToBeSigned.size() != 64) { + LOG(ERROR) << "Invalid size for signatureToBeSigned, expected 64 got " + << signatureToBeSigned.size(); + return {}; + } + + cppbor::Map unprotectedHeaders; + cppbor::Map protectedHeaders; + + protectedHeaders.add(COSE_LABEL_ALG, COSE_ALG_ECDSA_256); + + if (certificateChain.size() != 0) { + optional>> certs = support::certificateChainSplit(certificateChain); + if (!certs) { + LOG(ERROR) << "Error splitting certificate chain"; + return {}; + } + if (certs.value().size() == 1) { + unprotectedHeaders.add(COSE_LABEL_X5CHAIN, certs.value()[0]); + } else { + cppbor::Array certArray; + for (const vector& cert : certs.value()) { + certArray.add(cert); + } + unprotectedHeaders.add(COSE_LABEL_X5CHAIN, std::move(certArray)); + } + } + + vector encodedProtectedHeaders = coseEncodeHeaders(protectedHeaders); + + cppbor::Array coseSign1; + coseSign1.add(encodedProtectedHeaders); + coseSign1.add(std::move(unprotectedHeaders)); + if (data.size() == 0) { + cppbor::Null nullValue; + coseSign1.add(std::move(nullValue)); + } else { + coseSign1.add(data); + } + coseSign1.add(signatureToBeSigned); + vector signatureCoseSign1; + signatureCoseSign1 = coseSign1.encode(); + + return signatureCoseSign1; +} + optional> coseSignEcDsa(const vector& key, const vector& data, const vector& detachedContent, const vector& certificateChain) { @@ -1709,6 +1964,35 @@ bool coseCheckEcDsaSignature(const vector& signatureCoseSign1, return true; } +// Extracts the signature (of the ToBeSigned CBOR) from a COSE_Sign1. +optional> coseSignGetSignature(const vector& signatureCoseSign1) { + auto [item, _, message] = cppbor::parse(signatureCoseSign1); + if (item == nullptr) { + LOG(ERROR) << "Passed-in COSE_Sign1 is not valid CBOR: " << message; + return {}; + } + const cppbor::Array* array = item->asArray(); + if (array == nullptr) { + LOG(ERROR) << "Value for COSE_Sign1 is not an array"; + return {}; + } + if (array->size() != 4) { + LOG(ERROR) << "Value for COSE_Sign1 is not an array of size 4"; + return {}; + } + + vector signature; + const cppbor::Bstr* signatureAsBstr = (*array)[3]->asBstr(); + if (signatureAsBstr == nullptr) { + LOG(ERROR) << "Value for signature is not a bstr"; + return {}; + } + // Copy payload into |data| + signature = signatureAsBstr->value(); + + return signature; +} + optional> coseSignGetPayload(const vector& signatureCoseSign1) { auto [item, _, message] = cppbor::parse(signatureCoseSign1); if (item == nullptr) { @@ -1746,6 +2030,59 @@ optional> coseSignGetPayload(const vector& signatureCos return data; } +optional coseSignGetAlg(const vector& signatureCoseSign1) { + auto [item, _, message] = cppbor::parse(signatureCoseSign1); + if (item == nullptr) { + LOG(ERROR) << "Passed-in COSE_Sign1 is not valid CBOR: " << message; + return {}; + } + const cppbor::Array* array = item->asArray(); + if (array == nullptr) { + LOG(ERROR) << "Value for COSE_Sign1 is not an array"; + return {}; + } + if (array->size() != 4) { + LOG(ERROR) << "Value for COSE_Sign1 is not an array of size 4"; + return {}; + } + + const cppbor::Bstr* protectedHeadersBytes = (*array)[0]->asBstr(); + if (protectedHeadersBytes == nullptr) { + LOG(ERROR) << "Value for protectedHeaders is not a bstr"; + return {}; + } + auto [item2, _2, message2] = cppbor::parse(protectedHeadersBytes->value()); + if (item2 == nullptr) { + LOG(ERROR) << "Error parsing protectedHeaders: " << message2; + return {}; + } + const cppbor::Map* protectedHeaders = item2->asMap(); + if (protectedHeaders == nullptr) { + LOG(ERROR) << "Decoded CBOR for protectedHeaders is not a map"; + return {}; + } + + for (size_t n = 0; n < protectedHeaders->size(); n++) { + auto [keyItem, valueItem] = (*protectedHeaders)[n]; + const cppbor::Int* number = keyItem->asInt(); + if (number == nullptr) { + LOG(ERROR) << "Key item in top-level map is not a number"; + return {}; + } + int label = number->value(); + if (label == COSE_LABEL_ALG) { + const cppbor::Int* number = valueItem->asInt(); + if (number != nullptr) { + return number->value(); + } + LOG(ERROR) << "Value for COSE_LABEL_ALG label is not a number"; + return {}; + } + } + LOG(ERROR) << "Did not find COSE_LABEL_ALG label in protected headers"; + return {}; +} + optional> coseSignGetX5Chain(const vector& signatureCoseSign1) { auto [item, _, message] = cppbor::parse(signatureCoseSign1); if (item == nullptr) { @@ -1861,6 +2198,28 @@ optional> coseMac0(const vector& key, const vector> coseMacWithDigest(const vector& digestToBeMaced, + const vector& data) { + cppbor::Map unprotectedHeaders; + cppbor::Map protectedHeaders; + + protectedHeaders.add(COSE_LABEL_ALG, COSE_ALG_HMAC_256_256); + + vector encodedProtectedHeaders = coseEncodeHeaders(protectedHeaders); + + cppbor::Array array; + array.add(encodedProtectedHeaders); + array.add(std::move(unprotectedHeaders)); + if (data.size() == 0) { + cppbor::Null nullValue; + array.add(std::move(nullValue)); + } else { + array.add(data); + } + array.add(digestToBeMaced); + return array.encode(); +} + // --------------------------------------------------------------------------- // Utility functions specific to IdentityCredential. // ---------------------------------------------------------------------------