Loading identity/aidl/android/hardware/identity/IIdentityCredential.aidl +11 −18 Original line number Diff line number Diff line Loading @@ -160,17 +160,10 @@ interface IIdentityCredential { * ItemsRequestBytes * ] * * SessionTranscript = [ * DeviceEngagementBytes, * EReaderKeyBytes * ] * SessionTranscript = any * * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) * ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest) * * EReaderKey.Pub = COSE_Key ; Ephemeral public key provided by reader * * The public key corresponding to the key used to made signature, can be found in the * 'x5chain' unprotected header element of the COSE_Sign1 structure (as as described * in 'draft-ietf-cose-x509-04'). There will be at least one certificate in said element Loading @@ -184,8 +177,12 @@ interface IIdentityCredential { * * If the SessionTranscript CBOR is not empty, the X and Y coordinates of the public * part of the key-pair previously generated by createEphemeralKeyPair() must appear * somewhere in the bytes of DeviceEngagement structure. Both X and Y should be in * uncompressed form. If this is not satisfied, the call fails with * somewhere in the bytes of the CBOR. Each of these coordinates must appear encoded * with the most significant bits first and use the exact amount of bits indicated by * the key size of the ephemeral keys. For example, if the ephemeral key is using the * P-256 curve then the 32 bytes for the X coordinate encoded with the most significant * bits first must appear somewhere in the CBOR and ditto for the 32 bytes for the Y * coordinate. If this is not satisfied, the call fails with * STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND. * * @param accessControlProfiles Loading Loading @@ -298,13 +295,8 @@ interface IIdentityCredential { * * DocType = tstr * * SessionTranscript = [ * DeviceEngagementBytes, * EReaderKeyBytes * ] * SessionTranscript = any * * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) * DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces) * * where Loading Loading @@ -356,8 +348,9 @@ interface IIdentityCredential { * * - subjectPublicKeyInfo: must contain attested public key. * * @param out signingKeyBlob contains an encrypted copy of the newly-generated private * signing key. * @param out signingKeyBlob contains an AES-GCM-ENC(storageKey, R, signingKey, docType) * where signingKey is an EC private key in uncompressed form. That is, the returned * blob is an encrypted copy of the newly-generated private signing key. * * @return an X.509 certificate for the new signing key, signed by the credential key. */ Loading identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl +22 −2 Original line number Diff line number Diff line Loading @@ -29,9 +29,27 @@ interface IWritableIdentityCredential { * Gets the certificate chain for credentialKey which can be used to prove the hardware * characteristics to an issuing authority. Must not be called more than once. * * The following non-optional fields for the X.509 certificate shall be set as follows: * * - version: INTEGER 2 (means v3 certificate). * * - serialNumber: INTEGER 1 (fixed value: same on all certs). * * - signature: must be set to ECDSA. * * - subject: CN shall be set to "Android Identity Credential Key". * * - issuer: shall be set to "credentialStoreName (credentialStoreAuthorName)" using the * values returned in HardwareInformation. * * - validity: should be from current time and expire at the same time as the * attestation batch certificate used. * * - subjectPublicKeyInfo: must contain attested public key. * * The certificate chain must be generated using Keymaster Attestation * (see https://source.android.com/security/keystore/attestation) with the * following additional requirements: * following additional requirements on the data in the attestation extension: * * - The attestationVersion field in the attestation extension must be at least 3. * Loading Loading @@ -109,7 +127,8 @@ interface IWritableIdentityCredential { * in Tag::ATTESTATION_APPLICATION_ID. This schema is described in * https://developer.android.com/training/articles/security-key-attestation#certificate_schema_attestationid * * @param attestationChallenge a challenge set by the issuer to ensure freshness. * @param attestationChallenge a challenge set by the issuer to ensure freshness. If * this is empty, the call fails with STATUS_INVALID_DATA. * * @return the X.509 certificate chain for the credentialKey */ Loading Loading @@ -250,6 +269,7 @@ interface IWritableIdentityCredential { * CredentialKeys = [ * bstr, ; storageKey, a 128-bit AES key * bstr ; credentialPrivKey, the private key for credentialKey * ; in uncompressed form * ] * * @param out proofOfProvisioningSignature proves to the IA that the credential was imported Loading identity/aidl/default/IdentityCredential.cpp +9 −26 Original line number Diff line number Diff line Loading @@ -164,6 +164,7 @@ ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge } *outChallenge = challenge; authChallenge_ = challenge; return ndk::ScopedAStatus::ok(); } Loading Loading @@ -223,7 +224,8 @@ bool checkUserAuthentication(const SecureAccessControlProfile& profile, } if (authToken.challenge != int64_t(authChallenge)) { LOG(ERROR) << "Challenge in authToken doesn't match the challenge we created"; LOG(ERROR) << "Challenge in authToken (" << uint64_t(authToken.challenge) << ") " << "doesn't match the challenge we created (" << authChallenge << ")"; return false; } return true; Loading Loading @@ -341,28 +343,6 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( // // We do this by just searching for the X and Y coordinates. if (sessionTranscript.size() > 0) { const cppbor::Array* array = sessionTranscriptItem_->asArray(); if (array == nullptr || array->size() != 2) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, "SessionTranscript is not an array with two items")); } const cppbor::Semantic* taggedEncodedDE = (*array)[0]->asSemantic(); if (taggedEncodedDE == nullptr || taggedEncodedDE->value() != 24) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, "First item in SessionTranscript array is not a " "semantic with value 24")); } const cppbor::Bstr* encodedDE = (taggedEncodedDE->child())->asBstr(); if (encodedDE == nullptr) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, "Child of semantic in first item in SessionTranscript " "array is not a bstr")); } const vector<uint8_t>& bytesDE = encodedDE->value(); auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_); if (!getXYSuccess) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( Loading @@ -370,8 +350,10 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( "Error extracting X and Y from ePub")); } if (sessionTranscript.size() > 0 && !(memmem(bytesDE.data(), bytesDE.size(), ePubX.data(), ePubX.size()) != nullptr && memmem(bytesDE.data(), bytesDE.size(), ePubY.data(), ePubY.size()) != nullptr)) { !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(), ePubX.size()) != nullptr && memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(), ePubY.size()) != nullptr)) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, "Did not find ephemeral public key's X and Y coordinates in " Loading Loading @@ -478,9 +460,10 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( } // Validate all the access control profiles in the requestData. bool haveAuthToken = (authToken.mac.size() > 0); bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0)); for (const auto& profile : accessControlProfiles) { if (!secureAccessControlProfileCheckMac(profile, storageKey_)) { LOG(ERROR) << "Error checking MAC for profile"; return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, "Error checking MAC for profile")); Loading identity/aidl/default/WritableIdentityCredential.cpp +11 −0 Original line number Diff line number Diff line Loading @@ -65,6 +65,10 @@ ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate( IIdentityCredentialStore::STATUS_FAILED, "Error attestation certificate previously generated")); } if (attestationChallenge.empty()) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge can not be empty")); } vector<uint8_t> challenge(attestationChallenge.begin(), attestationChallenge.end()); vector<uint8_t> appId(attestationApplicationId.begin(), attestationApplicationId.end()); Loading Loading @@ -165,6 +169,13 @@ ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile( "userAuthenticationRequired is false but timeout is non-zero")); } // If |userAuthenticationRequired| is true, then |secureUserId| must be non-zero. if (userAuthenticationRequired && secureUserId == 0) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, "userAuthenticationRequired is true but secureUserId is zero")); } profile.id = id; profile.readerCertificate = readerCertificate; profile.userAuthenticationRequired = userAuthenticationRequired; Loading identity/aidl/vts/Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,8 @@ cc_test { "VtsIdentityTestUtils.cpp", "VtsAttestationTests.cpp", "VtsAttestationParserSupport.cpp", "UserAuthTests.cpp", "ReaderAuthTests.cpp", ], shared_libs: [ "android.hardware.keymaster@4.0", Loading @@ -18,6 +20,7 @@ cc_test { "libkeymaster_portable", "libsoft_attestation_cert", "libpuresoftkeymasterdevice", "android.hardware.keymaster-ndk_platform", ], static_libs: [ "libcppbor", Loading Loading
identity/aidl/android/hardware/identity/IIdentityCredential.aidl +11 −18 Original line number Diff line number Diff line Loading @@ -160,17 +160,10 @@ interface IIdentityCredential { * ItemsRequestBytes * ] * * SessionTranscript = [ * DeviceEngagementBytes, * EReaderKeyBytes * ] * SessionTranscript = any * * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) * ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest) * * EReaderKey.Pub = COSE_Key ; Ephemeral public key provided by reader * * The public key corresponding to the key used to made signature, can be found in the * 'x5chain' unprotected header element of the COSE_Sign1 structure (as as described * in 'draft-ietf-cose-x509-04'). There will be at least one certificate in said element Loading @@ -184,8 +177,12 @@ interface IIdentityCredential { * * If the SessionTranscript CBOR is not empty, the X and Y coordinates of the public * part of the key-pair previously generated by createEphemeralKeyPair() must appear * somewhere in the bytes of DeviceEngagement structure. Both X and Y should be in * uncompressed form. If this is not satisfied, the call fails with * somewhere in the bytes of the CBOR. Each of these coordinates must appear encoded * with the most significant bits first and use the exact amount of bits indicated by * the key size of the ephemeral keys. For example, if the ephemeral key is using the * P-256 curve then the 32 bytes for the X coordinate encoded with the most significant * bits first must appear somewhere in the CBOR and ditto for the 32 bytes for the Y * coordinate. If this is not satisfied, the call fails with * STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND. * * @param accessControlProfiles Loading Loading @@ -298,13 +295,8 @@ interface IIdentityCredential { * * DocType = tstr * * SessionTranscript = [ * DeviceEngagementBytes, * EReaderKeyBytes * ] * SessionTranscript = any * * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) * DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces) * * where Loading Loading @@ -356,8 +348,9 @@ interface IIdentityCredential { * * - subjectPublicKeyInfo: must contain attested public key. * * @param out signingKeyBlob contains an encrypted copy of the newly-generated private * signing key. * @param out signingKeyBlob contains an AES-GCM-ENC(storageKey, R, signingKey, docType) * where signingKey is an EC private key in uncompressed form. That is, the returned * blob is an encrypted copy of the newly-generated private signing key. * * @return an X.509 certificate for the new signing key, signed by the credential key. */ Loading
identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl +22 −2 Original line number Diff line number Diff line Loading @@ -29,9 +29,27 @@ interface IWritableIdentityCredential { * Gets the certificate chain for credentialKey which can be used to prove the hardware * characteristics to an issuing authority. Must not be called more than once. * * The following non-optional fields for the X.509 certificate shall be set as follows: * * - version: INTEGER 2 (means v3 certificate). * * - serialNumber: INTEGER 1 (fixed value: same on all certs). * * - signature: must be set to ECDSA. * * - subject: CN shall be set to "Android Identity Credential Key". * * - issuer: shall be set to "credentialStoreName (credentialStoreAuthorName)" using the * values returned in HardwareInformation. * * - validity: should be from current time and expire at the same time as the * attestation batch certificate used. * * - subjectPublicKeyInfo: must contain attested public key. * * The certificate chain must be generated using Keymaster Attestation * (see https://source.android.com/security/keystore/attestation) with the * following additional requirements: * following additional requirements on the data in the attestation extension: * * - The attestationVersion field in the attestation extension must be at least 3. * Loading Loading @@ -109,7 +127,8 @@ interface IWritableIdentityCredential { * in Tag::ATTESTATION_APPLICATION_ID. This schema is described in * https://developer.android.com/training/articles/security-key-attestation#certificate_schema_attestationid * * @param attestationChallenge a challenge set by the issuer to ensure freshness. * @param attestationChallenge a challenge set by the issuer to ensure freshness. If * this is empty, the call fails with STATUS_INVALID_DATA. * * @return the X.509 certificate chain for the credentialKey */ Loading Loading @@ -250,6 +269,7 @@ interface IWritableIdentityCredential { * CredentialKeys = [ * bstr, ; storageKey, a 128-bit AES key * bstr ; credentialPrivKey, the private key for credentialKey * ; in uncompressed form * ] * * @param out proofOfProvisioningSignature proves to the IA that the credential was imported Loading
identity/aidl/default/IdentityCredential.cpp +9 −26 Original line number Diff line number Diff line Loading @@ -164,6 +164,7 @@ ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge } *outChallenge = challenge; authChallenge_ = challenge; return ndk::ScopedAStatus::ok(); } Loading Loading @@ -223,7 +224,8 @@ bool checkUserAuthentication(const SecureAccessControlProfile& profile, } if (authToken.challenge != int64_t(authChallenge)) { LOG(ERROR) << "Challenge in authToken doesn't match the challenge we created"; LOG(ERROR) << "Challenge in authToken (" << uint64_t(authToken.challenge) << ") " << "doesn't match the challenge we created (" << authChallenge << ")"; return false; } return true; Loading Loading @@ -341,28 +343,6 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( // // We do this by just searching for the X and Y coordinates. if (sessionTranscript.size() > 0) { const cppbor::Array* array = sessionTranscriptItem_->asArray(); if (array == nullptr || array->size() != 2) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, "SessionTranscript is not an array with two items")); } const cppbor::Semantic* taggedEncodedDE = (*array)[0]->asSemantic(); if (taggedEncodedDE == nullptr || taggedEncodedDE->value() != 24) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, "First item in SessionTranscript array is not a " "semantic with value 24")); } const cppbor::Bstr* encodedDE = (taggedEncodedDE->child())->asBstr(); if (encodedDE == nullptr) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, "Child of semantic in first item in SessionTranscript " "array is not a bstr")); } const vector<uint8_t>& bytesDE = encodedDE->value(); auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_); if (!getXYSuccess) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( Loading @@ -370,8 +350,10 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( "Error extracting X and Y from ePub")); } if (sessionTranscript.size() > 0 && !(memmem(bytesDE.data(), bytesDE.size(), ePubX.data(), ePubX.size()) != nullptr && memmem(bytesDE.data(), bytesDE.size(), ePubY.data(), ePubY.size()) != nullptr)) { !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(), ePubX.size()) != nullptr && memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(), ePubY.size()) != nullptr)) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, "Did not find ephemeral public key's X and Y coordinates in " Loading Loading @@ -478,9 +460,10 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( } // Validate all the access control profiles in the requestData. bool haveAuthToken = (authToken.mac.size() > 0); bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0)); for (const auto& profile : accessControlProfiles) { if (!secureAccessControlProfileCheckMac(profile, storageKey_)) { LOG(ERROR) << "Error checking MAC for profile"; return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, "Error checking MAC for profile")); Loading
identity/aidl/default/WritableIdentityCredential.cpp +11 −0 Original line number Diff line number Diff line Loading @@ -65,6 +65,10 @@ ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate( IIdentityCredentialStore::STATUS_FAILED, "Error attestation certificate previously generated")); } if (attestationChallenge.empty()) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge can not be empty")); } vector<uint8_t> challenge(attestationChallenge.begin(), attestationChallenge.end()); vector<uint8_t> appId(attestationApplicationId.begin(), attestationApplicationId.end()); Loading Loading @@ -165,6 +169,13 @@ ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile( "userAuthenticationRequired is false but timeout is non-zero")); } // If |userAuthenticationRequired| is true, then |secureUserId| must be non-zero. if (userAuthenticationRequired && secureUserId == 0) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, "userAuthenticationRequired is true but secureUserId is zero")); } profile.id = id; profile.readerCertificate = readerCertificate; profile.userAuthenticationRequired = userAuthenticationRequired; Loading
identity/aidl/vts/Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,8 @@ cc_test { "VtsIdentityTestUtils.cpp", "VtsAttestationTests.cpp", "VtsAttestationParserSupport.cpp", "UserAuthTests.cpp", "ReaderAuthTests.cpp", ], shared_libs: [ "android.hardware.keymaster@4.0", Loading @@ -18,6 +20,7 @@ cc_test { "libkeymaster_portable", "libsoft_attestation_cert", "libpuresoftkeymasterdevice", "android.hardware.keymaster-ndk_platform", ], static_libs: [ "libcppbor", Loading