Merge "Identity Credential changes for Android 12" am: ef90842503 am: 409331de91 am: 3b83d41967

Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/1464242

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: Ib6012072f2682bcb31c5aead9cb8f01e560a3560
This commit is contained in:
David Zeuthen
2021-01-27 07:54:40 +00:00
committed by Automerger Merge Worker
48 changed files with 1925 additions and 287 deletions

View File

@@ -287,7 +287,7 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.identity</name>
<version>1-2</version>
<version>1-3</version>
<interface>
<name>IIdentityCredentialStore</name>
<instance>default</instance>

View File

@@ -1,14 +1,29 @@
///////////////////////////////////////////////////////////////////////////////
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
// edit this file. It looks like you are doing that because you have modified
// an AIDL interface in a backward-incompatible way, e.g., deleting a function
// from an interface or a field from a parcelable and it broke the build. That
// breakage is intended.
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible changes to the AIDL files built
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped

View File

@@ -1,14 +1,29 @@
///////////////////////////////////////////////////////////////////////////////
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
// edit this file. It looks like you are doing that because you have modified
// an AIDL interface in a backward-incompatible way, e.g., deleting a function
// from an interface or a field from a parcelable and it broke the build. That
// breakage is intended.
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible changes to the AIDL files built
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped

View File

@@ -1,14 +1,29 @@
///////////////////////////////////////////////////////////////////////////////
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
// edit this file. It looks like you are doing that because you have modified
// an AIDL interface in a backward-incompatible way, e.g., deleting a function
// from an interface or a field from a parcelable and it broke the build. That
// breakage is intended.
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible changes to the AIDL files built
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped

View File

@@ -1,14 +1,29 @@
///////////////////////////////////////////////////////////////////////////////
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
// edit this file. It looks like you are doing that because you have modified
// an AIDL interface in a backward-incompatible way, e.g., deleting a function
// from an interface or a field from a parcelable and it broke the build. That
// breakage is intended.
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible changes to the AIDL files built
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
@@ -18,6 +33,9 @@
package android.hardware.identity;
@VintfStability
interface IIdentityCredential {
/**
* @deprecated use deleteCredentalWithChallenge() instead.
*/
byte[] deleteCredential();
byte[] createEphemeralKeyPair();
void setReaderEphemeralPublicKey(in byte[] publicKey);
@@ -29,4 +47,7 @@ interface IIdentityCredential {
android.hardware.identity.Certificate generateSigningKeyPair(out byte[] signingKeyBlob);
void setRequestedNamespaces(in android.hardware.identity.RequestNamespace[] requestNamespaces);
void setVerificationToken(in android.hardware.keymaster.VerificationToken verificationToken);
byte[] deleteCredentialWithChallenge(in byte[] challenge);
byte[] proveOwnership(in byte[] challenge);
android.hardware.identity.IWritableIdentityCredential updateCredential();
}

View File

@@ -1,14 +1,29 @@
///////////////////////////////////////////////////////////////////////////////
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
// edit this file. It looks like you are doing that because you have modified
// an AIDL interface in a backward-incompatible way, e.g., deleting a function
// from an interface or a field from a parcelable and it broke the build. That
// breakage is intended.
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible changes to the AIDL files built
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped

View File

@@ -1,14 +1,29 @@
///////////////////////////////////////////////////////////////////////////////
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
// edit this file. It looks like you are doing that because you have modified
// an AIDL interface in a backward-incompatible way, e.g., deleting a function
// from an interface or a field from a parcelable and it broke the build. That
// breakage is intended.
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible changes to the AIDL files built
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped

View File

@@ -1,14 +1,29 @@
///////////////////////////////////////////////////////////////////////////////
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
// edit this file. It looks like you are doing that because you have modified
// an AIDL interface in a backward-incompatible way, e.g., deleting a function
// from an interface or a field from a parcelable and it broke the build. That
// breakage is intended.
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible changes to the AIDL files built
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped

View File

@@ -1,14 +1,29 @@
///////////////////////////////////////////////////////////////////////////////
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
// edit this file. It looks like you are doing that because you have modified
// an AIDL interface in a backward-incompatible way, e.g., deleting a function
// from an interface or a field from a parcelable and it broke the build. That
// breakage is intended.
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible changes to the AIDL files built
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped

View File

@@ -1,14 +1,29 @@
///////////////////////////////////////////////////////////////////////////////
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
// edit this file. It looks like you are doing that because you have modified
// an AIDL interface in a backward-incompatible way, e.g., deleting a function
// from an interface or a field from a parcelable and it broke the build. That
// breakage is intended.
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible changes to the AIDL files built
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped

View File

@@ -19,6 +19,7 @@ package android.hardware.identity;
import android.hardware.identity.Certificate;
import android.hardware.identity.RequestNamespace;
import android.hardware.identity.SecureAccessControlProfile;
import android.hardware.identity.IWritableIdentityCredential;
import android.hardware.keymaster.HardwareAuthToken;
import android.hardware.keymaster.VerificationToken;
@@ -40,7 +41,11 @@ interface IIdentityCredential {
* After this method has been called, the persistent storage used for credentialData should
* be deleted.
*
* @return a COSE_Sign1 signature described above.
* This method was deprecated in API version 3 because there's no challenge so freshness
* can't be checked. Use deleteCredentalWithChallenge() instead.
*
* @return a COSE_Sign1 signature described above
* @deprecated use deleteCredentalWithChallenge() instead.
*/
byte[] deleteCredential();
@@ -353,6 +358,18 @@ interface IIdentityCredential {
*
* - subjectPublicKeyInfo: must contain attested public key.
*
* As of API version 3, the certificate shall also have an X.509 extension at
* OID 1.3.6.1.4.1.11129.2.1.26 which shall contain an OCTET STRING with the
* bytes of the CBOR with the following CDDL:
*
* ProofOfBinding = [
* "ProofOfBinding",
* bstr, // Contains SHA-256(ProofOfProvisioning)
* ]
*
* This CBOR enables an issuer to determine the exact state of the credential it
* returns issuer-signed data for.
*
* @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.
@@ -381,4 +398,63 @@ interface IIdentityCredential {
* The verification token. This token is only valid if the timestamp field is non-zero.
*/
void setVerificationToken(in VerificationToken verificationToken);
/**
* Delete a credential.
*
* This method returns a COSE_Sign1 data structure signed by CredentialKey
* with payload set to the ProofOfDeletion CBOR below:
*
* ProofOfDeletion = [
* "ProofOfDeletion", ; tstr
* tstr, ; DocType
* bstr, ; Challenge
* bool ; true if this is a test credential, should
* ; always be false.
* ]
*
* After this method has been called, the persistent storage used for credentialData should
* be deleted.
*
* This method was introduced in API version 3.
*
* @param challenge a challenge set by the issuer to ensure freshness. Maximum size is 32 bytes
* and it may be empty. Fails with STATUS_INVALID_DATA if bigger than 32 bytes.
* @return a COSE_Sign1 signature described above.
*/
byte[] deleteCredentialWithChallenge(in byte[] challenge);
/**
* Prove ownership of credential.
*
* This method returns a COSE_Sign1 data structure signed by CredentialKey with payload
* set to the ProofOfOwnership CBOR below.
*
* ProofOfOwnership = [
* "ProofOfOwnership", ; tstr
* tstr, ; DocType
* bstr, ; Challenge
* bool ; true if this is a test credential, should
* ; always be false.
* ]
*
* This method was introduced in API version 3.
*
* @param challenge a challenge set by the issuer to ensure freshness. Maximum size is 32 bytes
* and it may be empty. Fails with STATUS_INVALID_DATA if bigger than 32 bytes.
* @return a COSE_Sign1 signature described above.
*/
byte[] proveOwnership(in byte[] challenge);
/**
* Called to start updating the credential with new data items.
*
* If the getAttestationCertificate() method is called on the returned object
* it fails with the error STATUS_FAILED.
*
* This method was introduced in API version 3.
*
* @return an IWritableIdentityCredential
*/
IWritableIdentityCredential updateCredential();
}

View File

@@ -104,6 +104,11 @@ import android.hardware.identity.CipherSuite;
* All binder calls in the HAL may return a ServiceSpecificException with statuses from the
* STATUS_* integers defined in this interface. Each method states which status can be returned
* and under which circumstances.
*
* The API described here is API version 3 which corresponds to feature version 202101
* of the android.security.identity Framework API. An XML file declaring the feature
* android.hardware.identity_credential (or android.hardware.identity_credential.direct_access
* if implementing the Direct Access HAL) should be included declaring this feature version.
*/
@VintfStability
interface IIdentityCredentialStore {
@@ -230,6 +235,9 @@ interface IIdentityCredentialStore {
* return argument of the same name in finishAddingEntries(), in
* IWritableIdentityCredential.
*
* Note that the format of credentialData may depend on the feature version.
* Implementations must support credentialData created by an earlier feature version.
*
* @return an IIdentityCredential interface that provides operations on the Credential.
*/
IIdentityCredential getCredential(in CipherSuite cipherSuite, in byte[] credentialData);

View File

@@ -263,7 +263,9 @@ interface IWritableIdentityCredential {
*
* where HBK is a unique hardware-bound key that has never existed outside of the secure
* environment (except it's all zeroes if testCredential is True) and CredentialKeys is
* the CBOR-encoded structure (in CDDL notation):
* the CBOR-encoded structure (in CDDL notation) given below.
*
* In API versions 1 and 2 it was the following
*
* CredentialKeys = [
* bstr, ; storageKey, a 128-bit AES key
@@ -271,6 +273,17 @@ interface IWritableIdentityCredential {
* ; in uncompressed form
* ]
*
* In API version 3 or later it must be the following
*
* CredentialKeys = [
* bstr, ; storageKey, a 128-bit AES key
* bstr ; credentialPrivKey, the private key for credentialKey
* ; in uncompressed form
* bstr ; SHA-256(ProofOfProvisioning)
* ]
*
* Additional elements may be added to the CredentialKeys array in future versions.
*
* @param out proofOfProvisioningSignature proves to the IA that the credential was imported
* into the secure hardware without alteration or error. When the final addEntry() call is
* made (when the number of provisioned entries equals the sum of the items in
@@ -321,4 +334,5 @@ interface IWritableIdentityCredential {
* @param expectedProofOfProvisioningSize the expected size of ProofOfProvisioning.
*/
void setExpectedProofOfProvisioningSize(in int expectedProofOfProvisioningSize);
}

View File

@@ -12,6 +12,7 @@ cc_library_static {
cflags: [
"-Wall",
"-Wextra",
"-Wno-deprecated-declarations",
],
shared_libs: [
"liblog",
@@ -28,8 +29,8 @@ cc_library_static {
"libsoft_attestation_cert",
"libpuresoftkeymasterdevice",
"android.hardware.identity-support-lib",
"android.hardware.identity-ndk_platform",
"android.hardware.keymaster-ndk_platform",
"android.hardware.identity-unstable-ndk_platform",
"android.hardware.keymaster-unstable-ndk_platform",
],
}
@@ -88,8 +89,8 @@ cc_binary {
"libsoft_attestation_cert",
"libpuresoftkeymasterdevice",
"android.hardware.identity-support-lib",
"android.hardware.identity-ndk_platform",
"android.hardware.keymaster-ndk_platform",
"android.hardware.identity-unstable-ndk_platform",
"android.hardware.keymaster-unstable-ndk_platform",
"android.hardware.identity-libeic-hal-common",
"android.hardware.identity-libeic-library",
],
@@ -97,4 +98,14 @@ cc_binary {
"service.cpp",
"FakeSecureHardwareProxy.cpp",
],
required: [
"android.hardware.identity_credential.xml",
],
}
prebuilt_etc {
name: "android.hardware.identity_credential.xml",
sub_dir: "permissions",
vendor: true,
src: "android.hardware.identity_credential.xml",
}

View File

@@ -45,6 +45,7 @@
#include "EicOps.h"
using ::std::map;
using ::std::optional;
using ::std::string;
using ::std::tuple;
@@ -212,7 +213,8 @@ bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
return false;
}
if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) {
eicDebug("Private key is not %zd bytes long as expected", (size_t)EIC_P256_PRIV_KEY_SIZE);
eicDebug("Private key is %zd bytes, expected %zd", privKey.value().size(),
(size_t)EIC_P256_PRIV_KEY_SIZE);
return false;
}
@@ -224,7 +226,7 @@ bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
}
// ecKeyPairGetPublicKey() returns 0x04 | x | y, we don't want the leading 0x04.
if (pubKey.value().size() != EIC_P256_PUB_KEY_SIZE + 1) {
eicDebug("Private key is %zd bytes long, expected %zd", pubKey.value().size(),
eicDebug("Public key is %zd bytes long, expected %zd", pubKey.value().size(),
(size_t)EIC_P256_PRIV_KEY_SIZE + 1);
return false;
}
@@ -272,7 +274,8 @@ bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const
return false;
}
if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) {
eicDebug("Private key is not %zd bytes long as expected", (size_t)EIC_P256_PRIV_KEY_SIZE);
eicDebug("Private key is %zd bytes, expected %zd", privKey.value().size(),
(size_t)EIC_P256_PRIV_KEY_SIZE);
return false;
}
@@ -284,8 +287,8 @@ bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const
bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE], unsigned int serial,
const char* issuerName, const char* subjectName, time_t validityNotBefore,
time_t validityNotAfter, uint8_t* cert,
size_t* certSize) { // inout
time_t validityNotAfter, const uint8_t* proofOfBinding,
size_t proofOfBindingSize, uint8_t* cert, size_t* certSize) { // inout
vector<uint8_t> signingKeyVec(EIC_P256_PRIV_KEY_SIZE);
memcpy(signingKeyVec.data(), signingKey, EIC_P256_PRIV_KEY_SIZE);
@@ -293,12 +296,18 @@ bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
pubKeyVec[0] = 0x04;
memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE);
std::string serialDecimal = android::base::StringPrintf("%d", serial);
string serialDecimal = android::base::StringPrintf("%d", serial);
map<string, vector<uint8_t>> extensions;
if (proofOfBinding != nullptr) {
vector<uint8_t> proofOfBindingVec(proofOfBinding, proofOfBinding + proofOfBindingSize);
extensions["1.3.6.1.4.1.11129.2.1.26"] = proofOfBindingVec;
}
optional<vector<uint8_t>> certVec =
android::hardware::identity::support::ecPublicKeyGenerateCertificate(
pubKeyVec, signingKeyVec, serialDecimal, issuerName, subjectName,
validityNotBefore, validityNotAfter);
validityNotBefore, validityNotAfter, extensions);
if (!certVec) {
eicDebug("Error generating certificate");
return false;

View File

@@ -67,6 +67,13 @@ bool FakeSecureHardwareProvisioningProxy::initialize(bool testCredential) {
return eicProvisioningInit(&ctx_, testCredential);
}
bool FakeSecureHardwareProvisioningProxy::initializeForUpdate(
bool testCredential, string docType, vector<uint8_t> encryptedCredentialKeys) {
return eicProvisioningInitForUpdate(&ctx_, testCredential, docType.c_str(),
encryptedCredentialKeys.data(),
encryptedCredentialKeys.size());
}
// Returns public key certificate.
optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::createCredentialKey(
const vector<uint8_t>& challenge, const vector<uint8_t>& applicationId) {
@@ -140,14 +147,16 @@ optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishAddingEntri
return signatureOfToBeSigned;
}
// Returns encryptedCredentialKeys (80 bytes).
// Returns encryptedCredentialKeys.
optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishGetCredentialData(
const string& docType) {
vector<uint8_t> encryptedCredentialKeys(80);
vector<uint8_t> encryptedCredentialKeys(116);
size_t size = encryptedCredentialKeys.size();
if (!eicProvisioningFinishGetCredentialData(&ctx_, docType.c_str(),
encryptedCredentialKeys.data())) {
encryptedCredentialKeys.data(), &size)) {
return {};
}
encryptedCredentialKeys.resize(size);
return encryptedCredentialKeys;
}
@@ -162,7 +171,7 @@ bool FakeSecureHardwarePresentationProxy::initialize(bool testCredential, string
LOG(INFO) << "FakeSecureHardwarePresentationProxy created, sizeof(EicPresentation): "
<< sizeof(EicPresentation);
return eicPresentationInit(&ctx_, testCredential, docType.c_str(),
encryptedCredentialKeys.data());
encryptedCredentialKeys.data(), encryptedCredentialKeys.size());
}
// Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
@@ -312,13 +321,27 @@ optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::finishRetrieval()
}
optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::deleteCredential(
const string& docType, size_t proofOfDeletionCborSize) {
const string& docType, const vector<uint8_t>& challenge, bool includeChallenge,
size_t proofOfDeletionCborSize) {
vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
if (!eicPresentationDeleteCredential(&ctx_, docType.c_str(), proofOfDeletionCborSize,
if (!eicPresentationDeleteCredential(&ctx_, docType.c_str(), challenge.data(), challenge.size(),
includeChallenge, proofOfDeletionCborSize,
signatureOfToBeSigned.data())) {
return {};
}
return signatureOfToBeSigned;
}
optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::proveOwnership(
const string& docType, bool testCredential, const vector<uint8_t>& challenge,
size_t proofOfOwnershipCborSize) {
vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
if (!eicPresentationProveOwnership(&ctx_, docType.c_str(), testCredential, challenge.data(),
challenge.size(), proofOfOwnershipCborSize,
signatureOfToBeSigned.data())) {
return {};
}
return signatureOfToBeSigned;
}
} // namespace android::hardware::identity

View File

@@ -32,6 +32,9 @@ class FakeSecureHardwareProvisioningProxy : public SecureHardwareProvisioningPro
bool initialize(bool testCredential) override;
bool initializeForUpdate(bool testCredential, string docType,
vector<uint8_t> encryptedCredentialKeys) override;
bool shutdown() override;
// Returns public key certificate.
@@ -122,8 +125,14 @@ class FakeSecureHardwarePresentationProxy : public SecureHardwarePresentationPro
optional<vector<uint8_t>> finishRetrieval() override;
optional<vector<uint8_t>> deleteCredential(const string& docType,
const vector<uint8_t>& challenge,
bool includeChallenge,
size_t proofOfDeletionCborSize) override;
optional<vector<uint8_t>> proveOwnership(const string& docType, bool testCredential,
const vector<uint8_t>& challenge,
size_t proofOfOwnershipCborSize) override;
bool shutdown() override;
protected:

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<permissions>
<feature name="android.hardware.identity_credential" version="202101" />
</permissions>

View File

@@ -30,6 +30,7 @@
#include <cppbor_parse.h>
#include "FakeSecureHardwareProxy.h"
#include "WritableIdentityCredential.h"
namespace aidl::android::hardware::identity {
@@ -70,14 +71,8 @@ int IdentityCredential::initialize() {
docType_ = docTypeItem->value();
testCredential_ = testCredentialItem->value();
const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
if (encryptedCredentialKeys.size() != 80) {
LOG(ERROR) << "Unexpected size for encrypted CredentialKeys";
return IIdentityCredentialStore::STATUS_INVALID_DATA;
}
if (!hwProxy_->initialize(testCredential_, docType_, encryptedCredentialKeys)) {
encryptedCredentialKeys_ = encryptedCredentialKeysItem->value();
if (!hwProxy_->initialize(testCredential_, docType_, encryptedCredentialKeys_)) {
LOG(ERROR) << "hwProxy->initialize failed";
return false;
}
@@ -87,12 +82,32 @@ int IdentityCredential::initialize() {
ndk::ScopedAStatus IdentityCredential::deleteCredential(
vector<uint8_t>* outProofOfDeletionSignature) {
return deleteCredentialCommon({}, false, outProofOfDeletionSignature);
}
ndk::ScopedAStatus IdentityCredential::deleteCredentialWithChallenge(
const vector<uint8_t>& challenge, vector<uint8_t>* outProofOfDeletionSignature) {
return deleteCredentialCommon(challenge, true, outProofOfDeletionSignature);
}
ndk::ScopedAStatus IdentityCredential::deleteCredentialCommon(
const vector<uint8_t>& challenge, bool includeChallenge,
vector<uint8_t>* outProofOfDeletionSignature) {
if (challenge.size() > 32) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
}
cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
if (includeChallenge) {
array = {"ProofOfDeletion", docType_, challenge, testCredential_};
}
vector<uint8_t> proofOfDeletionCbor = array.encode();
vector<uint8_t> podDigest = support::sha256(proofOfDeletionCbor);
optional<vector<uint8_t>> signatureOfToBeSigned =
hwProxy_->deleteCredential(docType_, proofOfDeletionCbor.size());
optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->deleteCredential(
docType_, challenge, includeChallenge, proofOfDeletionCbor.size());
if (!signatureOfToBeSigned) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED, "Error signing ProofOfDeletion"));
@@ -111,6 +126,38 @@ ndk::ScopedAStatus IdentityCredential::deleteCredential(
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus IdentityCredential::proveOwnership(
const vector<uint8_t>& challenge, vector<uint8_t>* outProofOfOwnershipSignature) {
if (challenge.size() > 32) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
}
cppbor::Array array;
array = {"ProofOfOwnership", docType_, challenge, testCredential_};
vector<uint8_t> proofOfOwnershipCbor = array.encode();
vector<uint8_t> podDigest = support::sha256(proofOfOwnershipCbor);
optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->proveOwnership(
docType_, testCredential_, challenge, proofOfOwnershipCbor.size());
if (!signatureOfToBeSigned) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED, "Error signing ProofOfOwnership"));
}
optional<vector<uint8_t>> signature =
support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
proofOfOwnershipCbor, // data
{}); // certificateChain
if (!signature) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
}
*outProofOfOwnershipSignature = signature.value();
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
optional<vector<uint8_t>> ephemeralPriv = hwProxy_->createEphemeralKeyPair();
if (!ephemeralPriv) {
@@ -833,4 +880,19 @@ ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus IdentityCredential::updateCredential(
shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
sp<SecureHardwareProvisioningProxy> hwProxy = hwProxyFactory_->createProvisioningProxy();
shared_ptr<WritableIdentityCredential> wc =
ndk::SharedRefBase::make<WritableIdentityCredential>(hwProxy, docType_,
testCredential_);
if (!wc->initializeForUpdate(encryptedCredentialKeys_)) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
"Error initializing WritableIdentityCredential for update"));
}
*outWritableCredential = wc;
return ndk::ScopedAStatus::ok();
}
} // namespace aidl::android::hardware::identity

View File

@@ -45,9 +45,11 @@ using ::std::vector;
class IdentityCredential : public BnIdentityCredential {
public:
IdentityCredential(sp<SecureHardwarePresentationProxy> hwProxy,
IdentityCredential(sp<SecureHardwareProxyFactory> hwProxyFactory,
sp<SecureHardwarePresentationProxy> hwProxy,
const vector<uint8_t>& credentialData)
: hwProxy_(hwProxy),
: hwProxyFactory_(hwProxyFactory),
hwProxy_(hwProxy),
credentialData_(credentialData),
numStartRetrievalCalls_(0),
expectedDeviceNameSpacesSize_(0) {}
@@ -58,6 +60,11 @@ class IdentityCredential : public BnIdentityCredential {
// Methods from IIdentityCredential follow.
ndk::ScopedAStatus deleteCredential(vector<uint8_t>* outProofOfDeletionSignature) override;
ndk::ScopedAStatus deleteCredentialWithChallenge(
const vector<uint8_t>& challenge,
vector<uint8_t>* outProofOfDeletionSignature) override;
ndk::ScopedAStatus proveOwnership(const vector<uint8_t>& challenge,
vector<uint8_t>* outProofOfOwnershipSignature) override;
ndk::ScopedAStatus createEphemeralKeyPair(vector<uint8_t>* outKeyPair) override;
ndk::ScopedAStatus setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) override;
ndk::ScopedAStatus createAuthChallenge(int64_t* outChallenge) override;
@@ -79,8 +86,16 @@ class IdentityCredential : public BnIdentityCredential {
ndk::ScopedAStatus generateSigningKeyPair(vector<uint8_t>* outSigningKeyBlob,
Certificate* outSigningKeyCertificate) override;
ndk::ScopedAStatus updateCredential(
shared_ptr<IWritableIdentityCredential>* outWritableCredential) override;
private:
ndk::ScopedAStatus deleteCredentialCommon(const vector<uint8_t>& challenge,
bool includeChallenge,
vector<uint8_t>* outProofOfDeletionSignature);
// Set by constructor
sp<SecureHardwareProxyFactory> hwProxyFactory_;
sp<SecureHardwarePresentationProxy> hwProxy_;
vector<uint8_t> credentialData_;
int numStartRetrievalCalls_;
@@ -88,6 +103,7 @@ class IdentityCredential : public BnIdentityCredential {
// Set by initialize()
string docType_;
bool testCredential_;
vector<uint8_t> encryptedCredentialKeys_;
// Set by createEphemeralKeyPair()
vector<uint8_t> ephemeralPublicKey_;

View File

@@ -63,7 +63,7 @@ ndk::ScopedAStatus IdentityCredentialStore::getCredential(
sp<SecureHardwarePresentationProxy> hwProxy = hwProxyFactory_->createPresentationProxy();
shared_ptr<IdentityCredential> credential =
ndk::SharedRefBase::make<IdentityCredential>(hwProxy, credentialData);
ndk::SharedRefBase::make<IdentityCredential>(hwProxyFactory_, hwProxy, credentialData);
auto ret = credential->initialize();
if (ret != IIdentityCredentialStore::STATUS_OK) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(

View File

@@ -64,6 +64,9 @@ class SecureHardwareProvisioningProxy : public RefBase {
virtual bool initialize(bool testCredential) = 0;
virtual bool initializeForUpdate(bool testCredential, string docType,
vector<uint8_t> encryptedCredentialKeys) = 0;
// Returns public key certificate chain with attestation.
//
// This must return an entire certificate chain and its implementation must
@@ -164,8 +167,14 @@ class SecureHardwarePresentationProxy : public RefBase {
virtual optional<vector<uint8_t>> finishRetrieval();
virtual optional<vector<uint8_t>> deleteCredential(const string& docType,
const vector<uint8_t>& challenge,
bool includeChallenge,
size_t proofOfDeletionCborSize) = 0;
virtual optional<vector<uint8_t>> proveOwnership(const string& docType, bool testCredential,
const vector<uint8_t>& challenge,
size_t proofOfOwnershipCborSize) = 0;
virtual bool shutdown() = 0;
};

View File

@@ -40,7 +40,20 @@ using namespace ::android::hardware::identity;
bool WritableIdentityCredential::initialize() {
if (!hwProxy_->initialize(testCredential_)) {
LOG(ERROR) << "hwProxy->initialize failed";
LOG(ERROR) << "hwProxy->initialize() failed";
return false;
}
startPersonalizationCalled_ = false;
firstEntry_ = true;
return true;
}
// Used when updating a credential. Returns false on failure.
bool WritableIdentityCredential::initializeForUpdate(
const vector<uint8_t>& encryptedCredentialKeys) {
if (!hwProxy_->initializeForUpdate(testCredential_, docType_, encryptedCredentialKeys)) {
LOG(ERROR) << "hwProxy->initializeForUpdate() failed";
return false;
}
startPersonalizationCalled_ = false;

View File

@@ -36,16 +36,22 @@ using ::std::vector;
class WritableIdentityCredential : public BnWritableIdentityCredential {
public:
// For a new credential, call initialize() right after construction.
//
// For an updated credential, call initializeForUpdate() right after construction.
//
WritableIdentityCredential(sp<SecureHardwareProvisioningProxy> hwProxy, const string& docType,
bool testCredential)
: hwProxy_(hwProxy), docType_(docType), testCredential_(testCredential) {}
~WritableIdentityCredential();
// Creates the Credential Key. Returns false on failure. Must be called
// right after construction.
// Creates the Credential Key. Returns false on failure.
bool initialize();
// Used when updating a credential. Returns false on failure.
bool initializeForUpdate(const vector<uint8_t>& encryptedCredentialKeys);
// Methods from IWritableIdentityCredential follow.
ndk::ScopedAStatus getAttestationCertificate(const vector<uint8_t>& attestationApplicationId,
const vector<uint8_t>& attestationChallenge,

View File

@@ -1,7 +1,7 @@
<manifest version="1.0" type="device">
<hal format="aidl">
<name>android.hardware.identity</name>
<version>2</version>
<version>3</version>
<interface>
<name>IIdentityCredentialStore</name>
<instance>default</instance>

View File

@@ -17,6 +17,7 @@
#include "EicCbor.h"
void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize) {
eicMemSet(cbor, '\0', sizeof(EicCbor));
cbor->size = 0;
cbor->bufferSize = bufferSize;
cbor->buffer = buffer;
@@ -26,6 +27,7 @@ void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize) {
void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize,
const uint8_t* hmacKey, size_t hmacKeySize) {
eicMemSet(cbor, '\0', sizeof(EicCbor));
cbor->size = 0;
cbor->bufferSize = bufferSize;
cbor->buffer = buffer;
@@ -33,6 +35,10 @@ void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize,
eicOpsHmacSha256Init(&cbor->digester.hmacSha256, hmacKey, hmacKeySize);
}
void eicCborEnableSecondaryDigesterSha256(EicCbor* cbor, EicSha256Ctx* sha256) {
cbor->secondaryDigesterSha256 = sha256;
}
void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
switch (cbor->digestType) {
case EIC_CBOR_DIGEST_TYPE_SHA256:
@@ -53,6 +59,9 @@ void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size) {
eicOpsHmacSha256Update(&cbor->digester.hmacSha256, data, size);
break;
}
if (cbor->secondaryDigesterSha256 != NULL) {
eicOpsSha256Update(cbor->secondaryDigesterSha256, data, size);
}
if (cbor->size >= cbor->bufferSize) {
cbor->size += size;

View File

@@ -53,6 +53,9 @@ typedef struct {
EicHmacSha256Ctx hmacSha256;
} digester;
// The secondary digester, may be unset.
EicSha256Ctx* secondaryDigesterSha256;
// The buffer used for building up CBOR or NULL if bufferSize is 0.
uint8_t* buffer;
} EicCbor;
@@ -70,6 +73,14 @@ void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize);
void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize,
const uint8_t* hmacKey, size_t hmacKeySize);
/* Enables a secondary digester.
*
* May be enabled midway through processing, this can be used to e.g. calculate
* a digest of Sig_structure (for COSE_Sign1) and a separate digest of its
* payload.
*/
void eicCborEnableSecondaryDigesterSha256(EicCbor* cbor, EicSha256Ctx* sha256);
/* Finishes building CBOR and returns the digest. */
void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]);

View File

@@ -207,14 +207,17 @@ bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const
// Generate an X.509 certificate for the key identified by |publicKey| which
// must be of the form returned by eicOpsCreateEcKey().
//
// If proofOfBinding is not NULL, it will be included as an OCTET_STRING
// X.509 extension at OID 1.3.6.1.4.1.11129.2.1.26.
//
// The certificate will be signed by the key identified by |signingKey| which
// must be of the form returned by eicOpsCreateEcKey().
//
bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE], unsigned int serial,
const char* issuerName, const char* subjectName, time_t validityNotBefore,
time_t validityNotAfter, uint8_t* cert,
size_t* certSize); // inout
time_t validityNotAfter, const uint8_t* proofOfBinding,
size_t proofOfBindingSize, uint8_t* cert, size_t* certSize); // inout
// Uses |privateKey| to create an ECDSA signature of some data (the SHA-256 must
// be given by |digestOfData|). Returns the signature in |signature|.

View File

@@ -19,13 +19,28 @@
#include <inttypes.h>
bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType,
const uint8_t encryptedCredentialKeys[80]) {
uint8_t credentialKeys[52];
const uint8_t* encryptedCredentialKeys,
size_t encryptedCredentialKeysSize) {
uint8_t credentialKeys[86];
bool expectPopSha256 = false;
// For feature version 202009 it's 52 bytes long and for feature version 202101 it's 86
// bytes (the additional data is the ProofOfProvisioning SHA-256). We need
// to support loading all feature versions.
//
if (encryptedCredentialKeysSize == 52 + 28) {
/* do nothing */
} else if (encryptedCredentialKeysSize == 86 + 28) {
expectPopSha256 = true;
} else {
eicDebug("Unexpected size %zd for encryptedCredentialKeys", encryptedCredentialKeysSize);
return false;
}
eicMemSet(ctx, '\0', sizeof(EicPresentation));
if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys,
80,
encryptedCredentialKeysSize,
// DocType is the additionalAuthenticatedData
(const uint8_t*)docType, eicStrLen(docType), credentialKeys)) {
eicDebug("Error decrypting CredentialKeys");
@@ -34,25 +49,42 @@ bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char*
// It's supposed to look like this;
//
// Feature version 202009:
//
// CredentialKeys = [
// bstr, ; storageKey, a 128-bit AES key
// bstr ; credentialPrivKey, the private key for credentialKey
// bstr, ; credentialPrivKey, the private key for credentialKey
// ]
//
// where storageKey is 16 bytes and credentialPrivateKey is 32 bytes.
// Feature version 202101:
//
// So the first two bytes will be 0x82 0x50 indicating resp. an array of two elements
// and a bstr of 16 elements. Sixteen bytes later (offset 18 and 19) there will be
// a bstr of 32 bytes. It's encoded as two bytes 0x58 and 0x20.
// CredentialKeys = [
// bstr, ; storageKey, a 128-bit AES key
// bstr, ; credentialPrivKey, the private key for credentialKey
// bstr ; proofOfProvisioning SHA-256
// ]
//
if (credentialKeys[0] != 0x82 || credentialKeys[1] != 0x50 || credentialKeys[18] != 0x58 ||
credentialKeys[19] != 0x20) {
// where storageKey is 16 bytes, credentialPrivateKey is 32 bytes, and proofOfProvisioning
// SHA-256 is 32 bytes.
//
if (credentialKeys[0] != (expectPopSha256 ? 0x83 : 0x82) || // array of two or three elements
credentialKeys[1] != 0x50 || // 16-byte bstr
credentialKeys[18] != 0x58 || credentialKeys[19] != 0x20) { // 32-byte bstr
eicDebug("Invalid CBOR for CredentialKeys");
return false;
}
if (expectPopSha256) {
if (credentialKeys[52] != 0x58 || credentialKeys[53] != 0x20) { // 32-byte bstr
eicDebug("Invalid CBOR for CredentialKeys");
return false;
}
}
eicMemCpy(ctx->storageKey, credentialKeys + 2, EIC_AES_128_KEY_SIZE);
eicMemCpy(ctx->credentialPrivateKey, credentialKeys + 20, EIC_P256_PRIV_KEY_SIZE);
ctx->testCredential = testCredential;
if (expectPopSha256) {
eicMemCpy(ctx->proofOfProvisioningSha256, credentialKeys + 54, EIC_SHA256_DIGEST_SIZE);
}
return true;
}
@@ -61,6 +93,35 @@ bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* doc
uint8_t signingKeyBlob[60]) {
uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE];
uint8_t signingKeyPub[EIC_P256_PUB_KEY_SIZE];
uint8_t cborBuf[64];
// Generate the ProofOfBinding CBOR to include in the X.509 certificate in
// IdentityCredentialAuthenticationKeyExtension CBOR. This CBOR is defined
// by the following CDDL
//
// ProofOfBinding = [
// "ProofOfBinding",
// bstr, // Contains the SHA-256 of ProofOfProvisioning
// ]
//
// This array may grow in the future if other information needs to be
// conveyed.
//
// The bytes of ProofOfBinding is is represented as an OCTET_STRING
// and stored at OID 1.3.6.1.4.1.11129.2.1.26.
//
EicCbor cbor;
eicCborInit(&cbor, cborBuf, sizeof cborBuf);
eicCborAppendArray(&cbor, 2);
eicCborAppendString(&cbor, "ProofOfBinding");
eicCborAppendByteString(&cbor, ctx->proofOfProvisioningSha256, EIC_SHA256_DIGEST_SIZE);
if (cbor.size > sizeof(cborBuf)) {
eicDebug("Exceeded buffer size");
return false;
}
const uint8_t* proofOfBinding = cborBuf;
size_t proofOfBindingSize = cbor.size;
if (!eicOpsCreateEcKey(signingKeyPriv, signingKeyPub)) {
eicDebug("Error creating signing key");
@@ -73,7 +134,8 @@ bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* doc
if (!eicOpsSignEcKey(signingKeyPub, ctx->credentialPrivateKey, 1,
"Android Identity Credential Key", // issuer CN
"Android Identity Credential Authentication Key", // subject CN
validityNotBefore, validityNotAfter, publicKeyCert, publicKeyCertSize)) {
validityNotBefore, validityNotAfter, proofOfBinding, proofOfBindingSize,
publicKeyCert, publicKeyCertSize)) {
eicDebug("Error creating certificate for signing key");
return false;
}
@@ -674,7 +736,8 @@ bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMac
}
bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType,
size_t proofOfDeletionCborSize,
const uint8_t* challenge, size_t challengeSize,
bool includeChallenge, size_t proofOfDeletionCborSize,
uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
EicCbor cbor;
@@ -712,9 +775,12 @@ bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType,
eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, proofOfDeletionCborSize);
// Finally, the CBOR that we're actually signing.
eicCborAppendArray(&cbor, 3);
eicCborAppendArray(&cbor, includeChallenge ? 4 : 3);
eicCborAppendString(&cbor, "ProofOfDeletion");
eicCborAppendString(&cbor, docType);
if (includeChallenge) {
eicCborAppendByteString(&cbor, challenge, challengeSize);
}
eicCborAppendBool(&cbor, ctx->testCredential);
uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
@@ -726,3 +792,59 @@ bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType,
return true;
}
bool eicPresentationProveOwnership(EicPresentation* ctx, const char* docType, bool testCredential,
const uint8_t* challenge, size_t challengeSize,
size_t proofOfOwnershipCborSize,
uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
EicCbor cbor;
eicCborInit(&cbor, NULL, 0);
// What we're going to sign is the COSE ToBeSigned structure which
// looks like the following:
//
// Sig_structure = [
// context : "Signature" / "Signature1" / "CounterSignature",
// body_protected : empty_or_serialized_map,
// ? sign_protected : empty_or_serialized_map,
// external_aad : bstr,
// payload : bstr
// ]
//
eicCborAppendArray(&cbor, 4);
eicCborAppendString(&cbor, "Signature1");
// The COSE Encoded protected headers is just a single field with
// COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
// hard-code the CBOR encoding:
static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
sizeof(coseEncodedProtectedHeaders));
// We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
// so external_aad is the empty bstr
static const uint8_t externalAad[0] = {};
eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
// For the payload, the _encoded_ form follows here. We handle this by simply
// opening a bstr, and then writing the CBOR. This requires us to know the
// size of said bstr, ahead of time.
eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, proofOfOwnershipCborSize);
// Finally, the CBOR that we're actually signing.
eicCborAppendArray(&cbor, 4);
eicCborAppendString(&cbor, "ProofOfOwnership");
eicCborAppendString(&cbor, docType);
eicCborAppendByteString(&cbor, challenge, challengeSize);
eicCborAppendBool(&cbor, testCredential);
uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
eicCborFinal(&cbor, cborSha256);
if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) {
eicDebug("Error signing proofOfDeletion");
return false;
}
return true;
}

View File

@@ -31,6 +31,8 @@ extern "C" {
#define EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE 65
typedef struct {
int featureLevel;
uint8_t storageKey[EIC_AES_128_KEY_SIZE];
uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE];
@@ -79,12 +81,17 @@ typedef struct {
// SHA-256 for AdditionalData, updated for each entry.
uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE];
// SHA-256 of ProofOfProvisioning. Set to NUL-bytes or initialized from CredentialKeys data
// if credential was created with feature version 202101 or later.
uint8_t proofOfProvisioningSha256[EIC_SHA256_DIGEST_SIZE];
size_t expectedCborSizeAtEnd;
EicCbor cbor;
} EicPresentation;
bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType,
const uint8_t encryptedCredentialKeys[80]);
const uint8_t* encryptedCredentialKeys,
size_t encryptedCredentialKeysSize);
bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, time_t now,
uint8_t* publicKeyCert, size_t* publicKeyCertSize,
@@ -219,9 +226,19 @@ bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMac
// where content is set to the ProofOfDeletion CBOR.
//
bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType,
size_t proofOfDeletionCborSize,
const uint8_t* challenge, size_t challengeSize,
bool includeChallenge, size_t proofOfDeletionCborSize,
uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
// where content is set to the ProofOfOwnership CBOR.
//
bool eicPresentationProveOwnership(EicPresentation* ctx, const char* docType, bool testCredential,
const uint8_t* challenge, size_t challengeSize,
size_t proofOfOwnershipCborSize,
uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
#ifdef __cplusplus
}
#endif

View File

@@ -26,10 +26,84 @@ bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential) {
return true;
}
bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential, const char* docType,
const uint8_t* encryptedCredentialKeys,
size_t encryptedCredentialKeysSize) {
uint8_t credentialKeys[86];
// For feature version 202009 it's 52 bytes long and for feature version 202101 it's 86
// bytes (the additional data is the ProofOfProvisioning SHA-256). We need
// to support loading all feature versions.
//
bool expectPopSha256 = false;
if (encryptedCredentialKeysSize == 52 + 28) {
/* do nothing */
} else if (encryptedCredentialKeysSize == 86 + 28) {
expectPopSha256 = true;
} else {
eicDebug("Unexpected size %zd for encryptedCredentialKeys", encryptedCredentialKeysSize);
return false;
}
eicMemSet(ctx, '\0', sizeof(EicProvisioning));
ctx->testCredential = testCredential;
if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys,
encryptedCredentialKeysSize,
// DocType is the additionalAuthenticatedData
(const uint8_t*)docType, eicStrLen(docType), credentialKeys)) {
eicDebug("Error decrypting CredentialKeys");
return false;
}
// It's supposed to look like this;
//
// Feature version 202009:
//
// CredentialKeys = [
// bstr, ; storageKey, a 128-bit AES key
// bstr, ; credentialPrivKey, the private key for credentialKey
// ]
//
// Feature version 202101:
//
// CredentialKeys = [
// bstr, ; storageKey, a 128-bit AES key
// bstr, ; credentialPrivKey, the private key for credentialKey
// bstr ; proofOfProvisioning SHA-256
// ]
//
// where storageKey is 16 bytes, credentialPrivateKey is 32 bytes, and proofOfProvisioning
// SHA-256 is 32 bytes.
//
if (credentialKeys[0] != (expectPopSha256 ? 0x83 : 0x82) || // array of two or three elements
credentialKeys[1] != 0x50 || // 16-byte bstr
credentialKeys[18] != 0x58 || credentialKeys[19] != 0x20) { // 32-byte bstr
eicDebug("Invalid CBOR for CredentialKeys");
return false;
}
if (expectPopSha256) {
if (credentialKeys[52] != 0x58 || credentialKeys[53] != 0x20) { // 32-byte bstr
eicDebug("Invalid CBOR for CredentialKeys");
return false;
}
}
eicMemCpy(ctx->storageKey, credentialKeys + 2, EIC_AES_128_KEY_SIZE);
eicMemCpy(ctx->credentialPrivateKey, credentialKeys + 20, EIC_P256_PRIV_KEY_SIZE);
// Note: We don't care about the previous ProofOfProvisioning SHA-256
ctx->isUpdate = true;
return true;
}
bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge,
size_t challengeSize, const uint8_t* applicationId,
size_t applicationIdSize, uint8_t* publicKeyCert,
size_t* publicKeyCertSize) {
if (ctx->isUpdate) {
eicDebug("Cannot create CredentialKey on update");
return false;
}
if (!eicOpsCreateCredentialKey(ctx->credentialPrivateKey, challenge, challengeSize,
applicationId, applicationIdSize, ctx->testCredential,
publicKeyCert, publicKeyCertSize)) {
@@ -96,6 +170,9 @@ bool eicProvisioningStartPersonalization(EicProvisioning* ctx, int accessControl
eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedProofOfProvisioningSize);
ctx->expectedCborSizeAtEnd = expectedProofOfProvisioningSize + ctx->cbor.size;
eicOpsSha256Init(&ctx->proofOfProvisioningDigester);
eicCborEnableSecondaryDigesterSha256(&ctx->cbor, &ctx->proofOfProvisioningDigester);
eicCborAppendArray(&ctx->cbor, 5);
eicCborAppendString(&ctx->cbor, "ProofOfProvisioning");
eicCborAppendString(&ctx->cbor, docType);
@@ -260,14 +337,23 @@ bool eicProvisioningFinishAddingEntries(
}
bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType,
uint8_t encryptedCredentialKeys[80]) {
uint8_t* encryptedCredentialKeys,
size_t* encryptedCredentialKeysSize) {
EicCbor cbor;
uint8_t cborBuf[52];
uint8_t cborBuf[86];
if (*encryptedCredentialKeysSize < 86 + 28) {
eicDebug("encryptedCredentialKeysSize is %zd which is insufficient");
return false;
}
eicCborInit(&cbor, cborBuf, sizeof(cborBuf));
eicCborAppendArray(&cbor, 2);
eicCborAppendArray(&cbor, 3);
eicCborAppendByteString(&cbor, ctx->storageKey, EIC_AES_128_KEY_SIZE);
eicCborAppendByteString(&cbor, ctx->credentialPrivateKey, EIC_P256_PRIV_KEY_SIZE);
uint8_t popSha256[EIC_SHA256_DIGEST_SIZE];
eicOpsSha256Final(&ctx->proofOfProvisioningDigester, popSha256);
eicCborAppendByteString(&cbor, popSha256, EIC_SHA256_DIGEST_SIZE);
if (cbor.size > sizeof(cborBuf)) {
eicDebug("Exceeded buffer size");
return false;
@@ -285,6 +371,7 @@ bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* do
eicDebug("Error encrypting CredentialKeys");
return false;
}
*encryptedCredentialKeysSize = cbor.size + 28;
return true;
}

View File

@@ -31,7 +31,7 @@ extern "C" {
#define EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS 32
typedef struct {
// Set by eicCreateCredentialKey.
// Set by eicCreateCredentialKey() OR eicProvisioningInitForUpdate()
uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE];
int numEntryCounts;
@@ -43,6 +43,7 @@ typedef struct {
size_t curEntrySize;
size_t curEntryNumBytesReceived;
// Set by eicProvisioningInit() OR eicProvisioningInitForUpdate()
uint8_t storageKey[EIC_AES_128_KEY_SIZE];
size_t expectedCborSizeAtEnd;
@@ -50,13 +51,23 @@ typedef struct {
// SHA-256 for AdditionalData, updated for each entry.
uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE];
// Digester just for ProofOfProvisioning (without Sig_structure).
EicSha256Ctx proofOfProvisioningDigester;
EicCbor cbor;
bool testCredential;
// Set to true if this is an update.
bool isUpdate;
} EicProvisioning;
bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential);
bool eicProvisioningInitForUpdate(EicProvisioning* ctx, bool testCredential, const char* docType,
const uint8_t* encryptedCredentialKeys,
size_t encryptedCredentialKeysSize);
bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge,
size_t challengeSize, const uint8_t* applicationId,
size_t applicationIdSize, uint8_t* publicKeyCert,
@@ -107,14 +118,18 @@ bool eicProvisioningFinishAddingEntries(
// CredentialKeys = [
// bstr, ; storageKey, a 128-bit AES key
// bstr ; credentialPrivKey, the private key for credentialKey
// bstr ; SHA-256(ProofOfProvisioning)
// ]
//
// for feature version 202101. For feature version 202009 the third field was not present.
//
// Since |storageKey| is 16 bytes and |credentialPrivKey| is 32 bytes, the
// encoded CBOR for CredentialKeys is 52 bytes and consequently
// |encryptedCredentialKeys| will be 52 + 28 = 80 bytes.
// encoded CBOR for CredentialKeys is 86 bytes and consequently
// |encryptedCredentialKeys| will be no longer than 86 + 28 = 114 bytes.
//
bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType,
uint8_t encryptedCredentialKeys[80]);
uint8_t* encryptedCredentialKeys,
size_t* encryptedCredentialKeysSize);
#ifdef __cplusplus
}

View File

@@ -4,13 +4,21 @@ cc_test {
"VtsHalTargetTestDefaults",
"use_libaidlvintf_gtest_helper_static",
],
cflags: [
"-Wno-deprecated-declarations",
],
srcs: [
"VtsHalIdentityEndToEndTest.cpp",
"VtsIWritableIdentityCredentialTests.cpp",
"VtsIdentityTestUtils.cpp",
"Util.cpp",
"VtsAttestationTests.cpp",
"UserAuthTests.cpp",
"ReaderAuthTests.cpp",
"DeleteCredentialTests.cpp",
"ProveOwnershipTests.cpp",
"UpdateCredentialTests.cpp",
"EndToEndTests.cpp",
"TestCredentialTests.cpp",
"AuthenticationKeyTests.cpp",
],
shared_libs: [
"libbinder",
@@ -22,9 +30,9 @@ cc_test {
"libpuresoftkeymasterdevice",
"android.hardware.keymaster@4.0",
"android.hardware.identity-support-lib",
"android.hardware.identity-cpp",
"android.hardware.keymaster-cpp",
"android.hardware.keymaster-ndk_platform",
"android.hardware.identity-unstable-cpp",
"android.hardware.keymaster-unstable-cpp",
"android.hardware.keymaster-unstable-ndk_platform",
"libkeymaster4support",
"libkeymaster4_1support",
],

View File

@@ -0,0 +1,194 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "TestCredentialTests"
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
#include <aidl/android/hardware/keymaster/VerificationToken.h>
#include <android-base/logging.h>
#include <android/hardware/identity/IIdentityCredentialStore.h>
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <cppbor.h>
#include <cppbor_parse.h>
#include <gtest/gtest.h>
#include <future>
#include <map>
#include <utility>
#include "Util.h"
namespace android::hardware::identity {
using std::endl;
using std::make_pair;
using std::map;
using std::optional;
using std::pair;
using std::string;
using std::tie;
using std::vector;
using ::android::sp;
using ::android::String16;
using ::android::binder::Status;
using ::android::hardware::keymaster::HardwareAuthToken;
using ::android::hardware::keymaster::VerificationToken;
class AuthenticationKeyTests : public testing::TestWithParam<string> {
public:
virtual void SetUp() override {
string halInstanceName = GetParam();
credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
String16(halInstanceName.c_str()));
ASSERT_NE(credentialStore_, nullptr);
halApiVersion_ = credentialStore_->getInterfaceVersion();
}
sp<IIdentityCredentialStore> credentialStore_;
int halApiVersion_;
};
TEST_P(AuthenticationKeyTests, proofOfProvisionInAuthKeyCert) {
if (halApiVersion_ < 3) {
GTEST_SKIP() << "Need HAL API version 3, have " << halApiVersion_;
}
string docType = "org.iso.18013-5.2019.mdl";
sp<IWritableIdentityCredential> wc;
ASSERT_TRUE(credentialStore_
->createCredential(docType,
true, // testCredential
&wc)
.isOk());
vector<uint8_t> attestationApplicationId = {};
vector<uint8_t> attestationChallenge = {1};
vector<Certificate> certChain;
ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
&certChain)
.isOk());
optional<vector<uint8_t>> optCredentialPubKey =
support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
ASSERT_TRUE(optCredentialPubKey);
vector<uint8_t> credentialPubKey;
credentialPubKey = optCredentialPubKey.value();
size_t proofOfProvisioningSize = 112;
// Not in v1 HAL, may fail
wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
{1} /* numDataElementsPerNamespace */)
.isOk());
// Access control profile 0: open access - don't care about the returned SACP
SecureAccessControlProfile sacp;
ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
// Single entry - don't care about the returned encrypted data
vector<uint8_t> encryptedData;
vector<uint8_t> tstrLastName = cppbor::Tstr("Turing").encode();
ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Last name", tstrLastName.size()).isOk());
ASSERT_TRUE(wc->addEntryValue(tstrLastName, &encryptedData).isOk());
vector<uint8_t> proofOfProvisioningSignature;
vector<uint8_t> credentialData;
Status status = wc->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
optional<vector<uint8_t>> proofOfProvisioning =
support::coseSignGetPayload(proofOfProvisioningSignature);
ASSERT_TRUE(proofOfProvisioning);
string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {});
EXPECT_EQ(
"[\n"
" 'ProofOfProvisioning',\n"
" 'org.iso.18013-5.2019.mdl',\n"
" [\n"
" {\n"
" 'id' : 1,\n"
" },\n"
" ],\n"
" {\n"
" 'ns' : [\n"
" {\n"
" 'name' : 'Last name',\n"
" 'value' : 'Turing',\n"
" 'accessControlProfiles' : [1, ],\n"
" },\n"
" ],\n"
" },\n"
" true,\n"
"]",
cborPretty);
// Make sure it's signed by the CredentialKey in the returned cert chain.
EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
{}, // Additional data
credentialPubKey));
// Now get a credential and have it create AuthenticationKey so we can check
// the certificate.
sp<IIdentityCredential> credential;
ASSERT_TRUE(credentialStore_
->getCredential(
CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
credentialData, &credential)
.isOk());
vector<uint8_t> signingKeyBlob;
Certificate signingKeyCertificate;
ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
optional<vector<uint8_t>> signingPubKey =
support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate);
EXPECT_TRUE(signingPubKey);
// SHA-256(ProofOfProvisioning) is embedded in CBOR with the following CDDL
//
// ProofOfBinding = [
// "ProofOfBinding",
// bstr, // Contains the SHA-256 of ProofOfProvisioning
// ]
//
// Check that.
//
optional<vector<uint8_t>> proofOfBinding = support::certificateGetExtension(
signingKeyCertificate.encodedCertificate, "1.3.6.1.4.1.11129.2.1.26");
ASSERT_TRUE(proofOfBinding);
auto [item, _, message] = cppbor::parse(proofOfBinding.value());
ASSERT_NE(item, nullptr) << message;
const cppbor::Array* arrayItem = item->asArray();
ASSERT_NE(arrayItem, nullptr);
ASSERT_EQ(arrayItem->size(), 2);
const cppbor::Tstr* strItem = (*arrayItem)[0]->asTstr();
ASSERT_NE(strItem, nullptr);
EXPECT_EQ(strItem->value(), "ProofOfBinding");
const cppbor::Bstr* popSha256Item = (*arrayItem)[1]->asBstr();
ASSERT_NE(popSha256Item, nullptr);
EXPECT_EQ(popSha256Item->value(), support::sha256(proofOfProvisioning.value()));
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AuthenticationKeyTests);
INSTANTIATE_TEST_SUITE_P(
Identity, AuthenticationKeyTests,
testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
android::PrintInstanceNameToString);
} // namespace android::hardware::identity

View File

@@ -0,0 +1,169 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "DeleteCredentialTests"
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
#include <aidl/android/hardware/keymaster/VerificationToken.h>
#include <android-base/logging.h>
#include <android/hardware/identity/IIdentityCredentialStore.h>
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <cppbor.h>
#include <cppbor_parse.h>
#include <gtest/gtest.h>
#include <future>
#include <map>
#include <utility>
#include "Util.h"
namespace android::hardware::identity {
using std::endl;
using std::make_pair;
using std::map;
using std::optional;
using std::pair;
using std::string;
using std::tie;
using std::vector;
using ::android::sp;
using ::android::String16;
using ::android::binder::Status;
using ::android::hardware::keymaster::HardwareAuthToken;
using ::android::hardware::keymaster::VerificationToken;
class DeleteCredentialTests : public testing::TestWithParam<string> {
public:
virtual void SetUp() override {
credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
String16(GetParam().c_str()));
ASSERT_NE(credentialStore_, nullptr);
halApiVersion_ = credentialStore_->getInterfaceVersion();
}
void provisionData();
// Set by provisionData
vector<uint8_t> credentialData_;
vector<uint8_t> credentialPubKey_;
sp<IIdentityCredentialStore> credentialStore_;
int halApiVersion_;
};
void DeleteCredentialTests::provisionData() {
string docType = "org.iso.18013-5.2019.mdl";
bool testCredential = true;
sp<IWritableIdentityCredential> wc;
ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
vector<uint8_t> attestationApplicationId = {};
vector<uint8_t> attestationChallenge = {1};
vector<Certificate> certChain;
ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
&certChain)
.isOk());
optional<vector<uint8_t>> optCredentialPubKey =
support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
ASSERT_TRUE(optCredentialPubKey);
credentialPubKey_ = optCredentialPubKey.value();
size_t proofOfProvisioningSize = 106;
// Not in v1 HAL, may fail
wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
{1} /* numDataElementsPerNamespace */)
.isOk());
// Access control profile 0: open access - don't care about the returned SACP
SecureAccessControlProfile sacp;
ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
// Single entry - don't care about the returned encrypted data
ASSERT_TRUE(wc->beginAddEntry({0}, "ns", "Some Data", 1).isOk());
vector<uint8_t> encryptedData;
ASSERT_TRUE(wc->addEntryValue({9}, &encryptedData).isOk());
vector<uint8_t> proofOfProvisioningSignature;
Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
}
TEST_P(DeleteCredentialTests, Delete) {
provisionData();
sp<IIdentityCredential> credential;
ASSERT_TRUE(credentialStore_
->getCredential(
CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
credentialData_, &credential)
.isOk());
vector<uint8_t> proofOfDeletionSignature;
ASSERT_TRUE(credential->deleteCredential(&proofOfDeletionSignature).isOk());
optional<vector<uint8_t>> proofOfDeletion =
support::coseSignGetPayload(proofOfDeletionSignature);
ASSERT_TRUE(proofOfDeletion);
string cborPretty = support::cborPrettyPrint(proofOfDeletion.value(), 32, {});
EXPECT_EQ("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', true, ]", cborPretty);
EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfDeletionSignature, {}, // Additional data
credentialPubKey_));
}
TEST_P(DeleteCredentialTests, DeleteWithChallenge) {
if (halApiVersion_ < 3) {
GTEST_SKIP() << "Need HAL API version 3, have " << halApiVersion_;
}
provisionData();
sp<IIdentityCredential> credential;
ASSERT_TRUE(credentialStore_
->getCredential(
CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
credentialData_, &credential)
.isOk());
vector<uint8_t> challenge = {65, 66, 67};
vector<uint8_t> proofOfDeletionSignature;
ASSERT_TRUE(
credential->deleteCredentialWithChallenge(challenge, &proofOfDeletionSignature).isOk());
optional<vector<uint8_t>> proofOfDeletion =
support::coseSignGetPayload(proofOfDeletionSignature);
ASSERT_TRUE(proofOfDeletion);
string cborPretty = support::cborPrettyPrint(proofOfDeletion.value(), 32, {});
EXPECT_EQ("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', {0x41, 0x42, 0x43}, true, ]",
cborPretty);
EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfDeletionSignature, {}, // Additional data
credentialPubKey_));
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DeleteCredentialTests);
INSTANTIATE_TEST_SUITE_P(
Identity, DeleteCredentialTests,
testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
android::PrintInstanceNameToString);
} // namespace android::hardware::identity

View File

@@ -29,7 +29,7 @@
#include <map>
#include <tuple>
#include "VtsIdentityTestUtils.h"
#include "Util.h"
namespace android::hardware::identity {
@@ -50,18 +50,20 @@ using ::android::hardware::keymaster::VerificationToken;
using test_utils::validateAttestationCertificate;
class IdentityAidl : public testing::TestWithParam<std::string> {
class EndToEndTests : public testing::TestWithParam<std::string> {
public:
virtual void SetUp() override {
credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
String16(GetParam().c_str()));
ASSERT_NE(credentialStore_, nullptr);
halApiVersion_ = credentialStore_->getInterfaceVersion();
}
sp<IIdentityCredentialStore> credentialStore_;
int halApiVersion_;
};
TEST_P(IdentityAidl, hardwareInformation) {
TEST_P(EndToEndTests, hardwareInformation) {
HardwareInformation info;
ASSERT_TRUE(credentialStore_->getHardwareInformation(&info).isOk());
ASSERT_GT(info.credentialStoreName.size(), 0);
@@ -69,20 +71,21 @@ TEST_P(IdentityAidl, hardwareInformation) {
ASSERT_GE(info.dataChunkSize, 256);
}
tuple<bool, string, vector<uint8_t>, vector<uint8_t>> extractFromTestCredentialData(
const vector<uint8_t>& credentialData) {
tuple<bool, string, vector<uint8_t>, vector<uint8_t>, vector<uint8_t>>
extractFromTestCredentialData(const vector<uint8_t>& credentialData) {
string docType;
vector<uint8_t> storageKey;
vector<uint8_t> credentialPrivKey;
vector<uint8_t> sha256Pop;
auto [item, _, message] = cppbor::parse(credentialData);
if (item == nullptr) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
const cppbor::Array* arrayItem = item->asArray();
if (arrayItem == nullptr || arrayItem->size() != 3) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
@@ -92,7 +95,7 @@ tuple<bool, string, vector<uint8_t>, vector<uint8_t>> extractFromTestCredentialD
const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
if (docTypeItem == nullptr || testCredentialItem == nullptr ||
encryptedCredentialKeysItem == nullptr) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
docType = docTypeItem->value();
@@ -103,28 +106,38 @@ tuple<bool, string, vector<uint8_t>, vector<uint8_t>> extractFromTestCredentialD
optional<vector<uint8_t>> decryptedCredentialKeys =
support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
if (!decryptedCredentialKeys) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
if (dckItem == nullptr) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
const cppbor::Array* dckArrayItem = dckItem->asArray();
if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
if (dckArrayItem == nullptr) {
return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
if (dckArrayItem->size() < 2) {
return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
return make_tuple(false, docType, storageKey, credentialPrivKey);
return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
storageKey = storageKeyItem->value();
credentialPrivKey = credentialPrivKeyItem->value();
return make_tuple(true, docType, storageKey, credentialPrivKey);
if (dckArrayItem->size() == 3) {
const cppbor::Bstr* sha256PopItem = (*dckArrayItem)[2]->asBstr();
if (sha256PopItem == nullptr) {
return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop);
}
sha256Pop = sha256PopItem->value();
}
return make_tuple(true, docType, storageKey, credentialPrivKey, sha256Pop);
}
TEST_P(IdentityAidl, createAndRetrieveCredential) {
TEST_P(EndToEndTests, createAndRetrieveCredential) {
// First, generate a key-pair for the reader since its public key will be
// part of the request data.
vector<uint8_t> readerKey;
@@ -277,8 +290,9 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
// Extract doctype, storage key, and credentialPrivKey from credentialData... this works
// only because we asked for a test-credential meaning that the HBK is all zeroes.
auto [exSuccess, exDocType, exStorageKey, exCredentialPrivKey] =
auto [exSuccess, exDocType, exStorageKey, exCredentialPrivKey, exSha256Pop] =
extractFromTestCredentialData(credentialData);
ASSERT_TRUE(exSuccess);
ASSERT_EQ(exDocType, "org.iso.18013-5.2019.mdl");
// ... check that the public key derived from the private key matches what was
@@ -291,6 +305,13 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
ASSERT_TRUE(exCredentialPubKey);
ASSERT_EQ(exCredentialPubKey.value(), credentialPubKey.value());
// Starting with API version 3 (feature version 202101) we require SHA-256(ProofOfProvisioning)
// to be in CredentialKeys (which is stored encrypted in CredentialData). Check
// that it's there with the expected value.
if (halApiVersion_ >= 3) {
ASSERT_EQ(exSha256Pop, support::sha256(proofOfProvisioning.value()));
}
// Now that the credential has been provisioned, read it back and check the
// correct data is returned.
sp<IIdentityCredential> credential;
@@ -498,13 +519,11 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) {
EXPECT_EQ(mac, calculatedMac);
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(IdentityAidl);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EndToEndTests);
INSTANTIATE_TEST_SUITE_P(
Identity, IdentityAidl,
Identity, EndToEndTests,
testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
android::PrintInstanceNameToString);
// INSTANTIATE_TEST_SUITE_P(Identity, IdentityAidl,
// testing::Values("android.hardware.identity.IIdentityCredentialStore/default"));
} // namespace android::hardware::identity

View File

@@ -0,0 +1,146 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "ProveOwnershipTests"
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
#include <aidl/android/hardware/keymaster/VerificationToken.h>
#include <android-base/logging.h>
#include <android/hardware/identity/IIdentityCredentialStore.h>
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <cppbor.h>
#include <cppbor_parse.h>
#include <gtest/gtest.h>
#include <future>
#include <map>
#include <utility>
#include "Util.h"
namespace android::hardware::identity {
using std::endl;
using std::make_pair;
using std::map;
using std::optional;
using std::pair;
using std::string;
using std::tie;
using std::vector;
using ::android::sp;
using ::android::String16;
using ::android::binder::Status;
using ::android::hardware::keymaster::HardwareAuthToken;
using ::android::hardware::keymaster::VerificationToken;
class ProveOwnershipTests : public testing::TestWithParam<string> {
public:
virtual void SetUp() override {
credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
String16(GetParam().c_str()));
ASSERT_NE(credentialStore_, nullptr);
halApiVersion_ = credentialStore_->getInterfaceVersion();
}
void provisionData();
// Set by provisionData
vector<uint8_t> credentialData_;
vector<uint8_t> credentialPubKey_;
sp<IIdentityCredentialStore> credentialStore_;
int halApiVersion_;
};
void ProveOwnershipTests::provisionData() {
string docType = "org.iso.18013-5.2019.mdl";
bool testCredential = true;
sp<IWritableIdentityCredential> wc;
ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
vector<uint8_t> attestationApplicationId = {};
vector<uint8_t> attestationChallenge = {1};
vector<Certificate> certChain;
ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
&certChain)
.isOk());
optional<vector<uint8_t>> optCredentialPubKey =
support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
ASSERT_TRUE(optCredentialPubKey);
credentialPubKey_ = optCredentialPubKey.value();
size_t proofOfProvisioningSize = 106;
// Not in v1 HAL, may fail
wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
{1} /* numDataElementsPerNamespace */)
.isOk());
// Access control profile 0: open access - don't care about the returned SACP
SecureAccessControlProfile sacp;
ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
// Single entry - don't care about the returned encrypted data
ASSERT_TRUE(wc->beginAddEntry({0}, "ns", "Some Data", 1).isOk());
vector<uint8_t> encryptedData;
ASSERT_TRUE(wc->addEntryValue({9}, &encryptedData).isOk());
vector<uint8_t> proofOfProvisioningSignature;
Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
}
TEST_P(ProveOwnershipTests, proveOwnership) {
if (halApiVersion_ < 3) {
GTEST_SKIP() << "Need HAL API version 3, have " << halApiVersion_;
}
provisionData();
sp<IIdentityCredential> credential;
ASSERT_TRUE(credentialStore_
->getCredential(
CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
credentialData_, &credential)
.isOk());
vector<uint8_t> challenge = {17, 18};
vector<uint8_t> proofOfOwnershipSignature;
ASSERT_TRUE(credential->proveOwnership(challenge, &proofOfOwnershipSignature).isOk());
optional<vector<uint8_t>> proofOfOwnership =
support::coseSignGetPayload(proofOfOwnershipSignature);
ASSERT_TRUE(proofOfOwnership);
string cborPretty = support::cborPrettyPrint(proofOfOwnership.value(), 32, {});
EXPECT_EQ("['ProofOfOwnership', 'org.iso.18013-5.2019.mdl', {0x11, 0x12}, true, ]", cborPretty);
EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfOwnershipSignature, {}, // Additional data
credentialPubKey_));
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ProveOwnershipTests);
INSTANTIATE_TEST_SUITE_P(
Identity, ProveOwnershipTests,
testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
android::PrintInstanceNameToString);
} // namespace android::hardware::identity

View File

@@ -32,7 +32,7 @@
#include <map>
#include <utility>
#include "VtsIdentityTestUtils.h"
#include "Util.h"
namespace android::hardware::identity {
@@ -123,9 +123,9 @@ vector<uint8_t> generateReaderCert(const vector<uint8_t>& publicKey,
const vector<uint8_t>& signingKey) {
time_t validityNotBefore = 0;
time_t validityNotAfter = 0xffffffff;
optional<vector<uint8_t>> cert =
support::ecPublicKeyGenerateCertificate(publicKey, signingKey, "24601", "Issuer",
"Subject", validityNotBefore, validityNotAfter);
optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate(
publicKey, signingKey, "24601", "Issuer", "Subject", validityNotBefore,
validityNotAfter, {});
return cert.value();
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "TestCredentialTests"
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
#include <aidl/android/hardware/keymaster/VerificationToken.h>
#include <android-base/logging.h>
#include <android/hardware/identity/IIdentityCredentialStore.h>
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <cppbor.h>
#include <cppbor_parse.h>
#include <gtest/gtest.h>
#include <future>
#include <map>
#include <utility>
#include "Util.h"
namespace android::hardware::identity {
using std::endl;
using std::make_pair;
using std::map;
using std::optional;
using std::pair;
using std::string;
using std::tie;
using std::vector;
using ::android::sp;
using ::android::String16;
using ::android::binder::Status;
using ::android::hardware::keymaster::HardwareAuthToken;
using ::android::hardware::keymaster::VerificationToken;
class TestCredentialTests : public testing::TestWithParam<string> {
public:
virtual void SetUp() override {
string halInstanceName = GetParam();
credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
String16(halInstanceName.c_str()));
ASSERT_NE(credentialStore_, nullptr);
halApiVersion_ = credentialStore_->getInterfaceVersion();
}
sp<IIdentityCredentialStore> credentialStore_;
int halApiVersion_;
};
TEST_P(TestCredentialTests, testCredential) {
string docType = "org.iso.18013-5.2019.mdl";
sp<IWritableIdentityCredential> wc;
ASSERT_TRUE(credentialStore_
->createCredential(docType,
true, // testCredential
&wc)
.isOk());
vector<uint8_t> attestationApplicationId = {};
vector<uint8_t> attestationChallenge = {1};
vector<Certificate> certChain;
ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
&certChain)
.isOk());
optional<vector<uint8_t>> optCredentialPubKey =
support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
ASSERT_TRUE(optCredentialPubKey);
vector<uint8_t> credentialPubKey;
credentialPubKey = optCredentialPubKey.value();
size_t proofOfProvisioningSize = 112;
// Not in v1 HAL, may fail
wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
{1} /* numDataElementsPerNamespace */)
.isOk());
// Access control profile 0: open access - don't care about the returned SACP
SecureAccessControlProfile sacp;
ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
// Single entry - don't care about the returned encrypted data
vector<uint8_t> encryptedData;
vector<uint8_t> tstrLastName = cppbor::Tstr("Turing").encode();
ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Last name", tstrLastName.size()).isOk());
ASSERT_TRUE(wc->addEntryValue(tstrLastName, &encryptedData).isOk());
vector<uint8_t> proofOfProvisioningSignature;
vector<uint8_t> credentialData;
Status status = wc->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
optional<vector<uint8_t>> proofOfProvisioning =
support::coseSignGetPayload(proofOfProvisioningSignature);
ASSERT_TRUE(proofOfProvisioning);
string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {});
EXPECT_EQ(
"[\n"
" 'ProofOfProvisioning',\n"
" 'org.iso.18013-5.2019.mdl',\n"
" [\n"
" {\n"
" 'id' : 1,\n"
" },\n"
" ],\n"
" {\n"
" 'ns' : [\n"
" {\n"
" 'name' : 'Last name',\n"
" 'value' : 'Turing',\n"
" 'accessControlProfiles' : [1, ],\n"
" },\n"
" ],\n"
" },\n"
" true,\n"
"]",
cborPretty);
// Make sure it's signed by the CredentialKey in the returned cert chain.
EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
{}, // Additional data
credentialPubKey));
// Now analyze credentialData..
auto [item, _, message] = cppbor::parse(credentialData);
ASSERT_NE(item, nullptr);
const cppbor::Array* arrayItem = item->asArray();
ASSERT_NE(arrayItem, nullptr);
ASSERT_EQ(arrayItem->size(), 3);
const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
const cppbor::Bool* testCredentialItem =
((*arrayItem)[1]->asSimple() != nullptr ? ((*arrayItem)[1]->asSimple()->asBool())
: nullptr);
EXPECT_EQ(docTypeItem->value(), docType);
EXPECT_EQ(testCredentialItem->value(), true);
vector<uint8_t> hardwareBoundKey = support::getTestHardwareBoundKey();
const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
const vector<uint8_t> docTypeVec(docType.begin(), docType.end());
optional<vector<uint8_t>> decryptedCredentialKeys =
support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
ASSERT_TRUE(decryptedCredentialKeys);
auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
ASSERT_NE(dckItem, nullptr) << dckMessage;
const cppbor::Array* dckArrayItem = dckItem->asArray();
ASSERT_NE(dckArrayItem, nullptr);
// In HAL API version 1 and 2 this array has two items, in version 3 and later it has three.
if (halApiVersion_ < 3) {
ASSERT_EQ(dckArrayItem->size(), 2);
} else {
ASSERT_EQ(dckArrayItem->size(), 3);
}
const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
const vector<uint8_t> storageKey = storageKeyItem->value();
// const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
// const vector<uint8_t> credentialPrivKey = credentialPrivKeyItem->value();
// Check storageKey can be used to decrypt |encryptedData| to |tstrLastName|
vector<uint8_t> additionalData = cppbor::Map()
.add("Namespace", "ns")
.add("Name", "Last name")
.add("AccessControlProfileIds", cppbor::Array().add(1))
.encode();
optional<vector<uint8_t>> decryptedDataItemValue =
support::decryptAes128Gcm(storageKey, encryptedData, additionalData);
ASSERT_TRUE(decryptedDataItemValue);
EXPECT_EQ(decryptedDataItemValue.value(), tstrLastName);
// Check that SHA-256(ProofOfProvisioning) matches (only in HAL API version 3)
if (halApiVersion_ >= 3) {
const cppbor::Bstr* popSha256Item = (*dckArrayItem)[2]->asBstr();
const vector<uint8_t> popSha256 = popSha256Item->value();
ASSERT_EQ(popSha256, support::sha256(proofOfProvisioning.value()));
}
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TestCredentialTests);
INSTANTIATE_TEST_SUITE_P(
Identity, TestCredentialTests,
testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
android::PrintInstanceNameToString);
} // namespace android::hardware::identity

View File

@@ -0,0 +1,232 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "UpdateCredentialTests"
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
#include <aidl/android/hardware/keymaster/VerificationToken.h>
#include <android-base/logging.h>
#include <android/hardware/identity/IIdentityCredentialStore.h>
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <cppbor.h>
#include <cppbor_parse.h>
#include <gtest/gtest.h>
#include <future>
#include <map>
#include <utility>
#include "Util.h"
namespace android::hardware::identity {
using std::endl;
using std::make_pair;
using std::map;
using std::optional;
using std::pair;
using std::string;
using std::tie;
using std::vector;
using ::android::sp;
using ::android::String16;
using ::android::binder::Status;
using ::android::hardware::keymaster::HardwareAuthToken;
using ::android::hardware::keymaster::VerificationToken;
class UpdateCredentialTests : public testing::TestWithParam<string> {
public:
virtual void SetUp() override {
credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
String16(GetParam().c_str()));
ASSERT_NE(credentialStore_, nullptr);
halApiVersion_ = credentialStore_->getInterfaceVersion();
}
void provisionData();
// Set by provisionData
vector<uint8_t> credentialData_;
vector<uint8_t> credentialPubKey_;
sp<IIdentityCredentialStore> credentialStore_;
int halApiVersion_;
};
void UpdateCredentialTests::provisionData() {
string docType = "org.iso.18013-5.2019.mdl";
bool testCredential = true;
sp<IWritableIdentityCredential> wc;
ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
vector<uint8_t> attestationApplicationId = {};
vector<uint8_t> attestationChallenge = {1};
vector<Certificate> certChain;
ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
&certChain)
.isOk());
optional<vector<uint8_t>> optCredentialPubKey =
support::certificateChainGetTopMostKey(certChain[0].encodedCertificate);
ASSERT_TRUE(optCredentialPubKey);
credentialPubKey_ = optCredentialPubKey.value();
size_t proofOfProvisioningSize = 112;
// Not in v1 HAL, may fail
wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
{1} /* numDataElementsPerNamespace */)
.isOk());
// Access control profile 0: open access - don't care about the returned SACP
SecureAccessControlProfile sacp;
ASSERT_TRUE(wc->addAccessControlProfile(1, {}, false, 0, 0, &sacp).isOk());
// Single entry - don't care about the returned encrypted data
vector<uint8_t> encryptedData;
vector<uint8_t> tstrLastName = cppbor::Tstr("Prince").encode();
ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Last name", tstrLastName.size()).isOk());
ASSERT_TRUE(wc->addEntryValue(tstrLastName, &encryptedData).isOk());
vector<uint8_t> proofOfProvisioningSignature;
Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
optional<vector<uint8_t>> proofOfProvisioning =
support::coseSignGetPayload(proofOfProvisioningSignature);
ASSERT_TRUE(proofOfProvisioning);
string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {});
EXPECT_EQ(
"[\n"
" 'ProofOfProvisioning',\n"
" 'org.iso.18013-5.2019.mdl',\n"
" [\n"
" {\n"
" 'id' : 1,\n"
" },\n"
" ],\n"
" {\n"
" 'ns' : [\n"
" {\n"
" 'name' : 'Last name',\n"
" 'value' : 'Prince',\n"
" 'accessControlProfiles' : [1, ],\n"
" },\n"
" ],\n"
" },\n"
" true,\n"
"]",
cborPretty);
// Make sure it's signed by the CredentialKey in the returned cert chain.
EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
{}, // Additional data
credentialPubKey_));
}
TEST_P(UpdateCredentialTests, updateCredential) {
if (halApiVersion_ < 3) {
GTEST_SKIP() << "Need HAL API version 3, have " << halApiVersion_;
}
provisionData();
sp<IIdentityCredential> credential;
ASSERT_TRUE(credentialStore_
->getCredential(
CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
credentialData_, &credential)
.isOk());
sp<IWritableIdentityCredential> wc;
ASSERT_TRUE(credential->updateCredential(&wc).isOk());
// Getting an attestation cert should fail (because it's an update).
vector<uint8_t> attestationApplicationId = {};
vector<uint8_t> attestationChallenge = {1};
vector<Certificate> certChain;
Status result = wc->getAttestationCertificate(attestationApplicationId, attestationChallenge,
&certChain);
ASSERT_FALSE(result.isOk());
EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
// Now provision some new data...
//
size_t proofOfProvisioningSize = 117;
// Not in v1 HAL, may fail
wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize);
ASSERT_TRUE(wc->startPersonalization(1 /* numAccessControlProfiles */,
{1} /* numDataElementsPerNamespace */)
.isOk());
// Access control profile 0: open access - don't care about the returned SACP
SecureAccessControlProfile sacp;
ASSERT_TRUE(wc->addAccessControlProfile(2, {}, false, 0, 0, &sacp).isOk());
// Single entry - don't care about the returned encrypted data
vector<uint8_t> encryptedData;
vector<uint8_t> tstrLastName = cppbor::Tstr("T.A.F.K.A.P").encode();
ASSERT_TRUE(wc->beginAddEntry({2}, "ns", "Last name", tstrLastName.size()).isOk());
ASSERT_TRUE(wc->addEntryValue(tstrLastName, &encryptedData).isOk());
vector<uint8_t> proofOfProvisioningSignature;
Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature);
EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
optional<vector<uint8_t>> proofOfProvisioning =
support::coseSignGetPayload(proofOfProvisioningSignature);
ASSERT_TRUE(proofOfProvisioning);
string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {});
EXPECT_EQ(
"[\n"
" 'ProofOfProvisioning',\n"
" 'org.iso.18013-5.2019.mdl',\n"
" [\n"
" {\n"
" 'id' : 2,\n"
" },\n"
" ],\n"
" {\n"
" 'ns' : [\n"
" {\n"
" 'name' : 'Last name',\n"
" 'value' : 'T.A.F.K.A.P',\n"
" 'accessControlProfiles' : [2, ],\n"
" },\n"
" ],\n"
" },\n"
" true,\n"
"]",
cborPretty);
// Make sure it's signed by the same CredentialKey we originally provisioned with.
EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
{}, // Additional data
credentialPubKey_));
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(UpdateCredentialTests);
INSTANTIATE_TEST_SUITE_P(
Identity, UpdateCredentialTests,
testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
android::PrintInstanceNameToString);
} // namespace android::hardware::identity

View File

@@ -32,7 +32,7 @@
#include <map>
#include <utility>
#include "VtsIdentityTestUtils.h"
#include "Util.h"
namespace android::hardware::identity {
@@ -145,7 +145,7 @@ void UserAuthTests::provisionData() {
EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
}
// From ReaderAuthTest.cpp - TODO: consolidate with VtsIdentityTestUtils.h
// From ReaderAuthTest.cpp - TODO: consolidate with Util.h
pair<vector<uint8_t>, vector<uint8_t>> generateReaderKey();
vector<uint8_t> generateReaderCert(const vector<uint8_t>& publicKey,
const vector<uint8_t>& signingKey);

View File

@@ -14,15 +14,18 @@
* limitations under the License.
*/
#define LOG_TAG "VtsIdentityTestUtils"
#define LOG_TAG "Util"
#include "VtsIdentityTestUtils.h"
#include "Util.h"
#include <android-base/logging.h>
#include <aidl/Gtest.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <keymaster/km_openssl/openssl_utils.h>
#include <keymasterV4_1/attestation_record.h>
#include <charconv>
#include <map>
namespace android::hardware::identity::test_utils {
@@ -35,6 +38,7 @@ using std::vector;
using ::android::sp;
using ::android::String16;
using ::android::base::StringPrintf;
using ::android::binder::Status;
using ::keymaster::X509_Ptr;
@@ -86,7 +90,7 @@ optional<vector<uint8_t>> generateReaderCertificate(string serialDecimal,
return support::ecPublicKeyGenerateCertificate(readerPublicKey.value(), readerKey.value(),
serialDecimal, issuer, subject,
validityNotBefore, validityNotAfter);
validityNotBefore, validityNotAfter, {});
}
optional<vector<SecureAccessControlProfile>> addAccessControlProfiles(

View File

@@ -21,6 +21,7 @@
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <cppbor.h>
#include <cppbor_parse.h>
#include <gtest/gtest.h>
namespace android::hardware::identity::test_utils {

View File

@@ -29,7 +29,7 @@
#include <future>
#include <map>
#include "VtsIdentityTestUtils.h"
#include "Util.h"
namespace android::hardware::identity {

View File

@@ -29,7 +29,7 @@
#include <future>
#include <map>
#include "VtsIdentityTestUtils.h"
#include "Util.h"
namespace android::hardware::identity {

View File

@@ -18,6 +18,7 @@
#define IDENTITY_SUPPORT_INCLUDE_IDENTITY_CREDENTIAL_UTILS_H_
#include <cstdint>
#include <map>
#include <optional>
#include <string>
#include <tuple>
@@ -29,11 +30,12 @@ namespace hardware {
namespace identity {
namespace support {
using ::std::map;
using ::std::optional;
using ::std::pair;
using ::std::string;
using ::std::tuple;
using ::std::vector;
using ::std::pair;
// The semantic tag for a bstr which includes Encoded CBOR (RFC 7049, section 2.4)
const int kSemanticTagEncodedCbor = 24;
@@ -221,6 +223,11 @@ optional<pair<size_t, size_t>> certificateFindSignature(const vector<uint8_t>& x
//
optional<pair<time_t, time_t>> certificateGetValidity(const vector<uint8_t>& x509Certificate);
// Looks for an extension with OID in |oidStr| which must be an stored as an OCTET STRING.
//
optional<vector<uint8_t>> certificateGetExtension(const vector<uint8_t>& x509Certificate,
const string& oidStr);
// Generates a X.509 certificate for |publicKey| (which must be in the format
// returned by ecKeyPairGetPublicKey()).
//
@@ -230,7 +237,8 @@ optional<pair<time_t, time_t>> certificateGetValidity(const vector<uint8_t>& x50
optional<vector<uint8_t>> ecPublicKeyGenerateCertificate(
const vector<uint8_t>& publicKey, const vector<uint8_t>& signingKey,
const string& serialDecimal, const string& issuer, const string& subject,
time_t validityNotBefore, time_t validityNotAfter);
time_t validityNotBefore, time_t validityNotAfter,
const map<string, vector<uint8_t>>& extensions);
// Performs Elliptic-curve Diffie-Helman using |publicKey| (which must be in the
// format returned by ecKeyPairGetPublicKey()) and |privateKey| (which must be

View File

@@ -344,15 +344,22 @@ string cborPrettyPrint(const vector<uint8_t>& encodedCbor, size_t maxBStrSize,
// Crypto functionality / abstraction.
// ---------------------------------------------------------------------------
struct EVP_CIPHER_CTX_Deleter {
void operator()(EVP_CIPHER_CTX* ctx) const {
if (ctx != nullptr) {
EVP_CIPHER_CTX_free(ctx);
}
}
};
using EvpCipherCtxPtr = unique_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_Deleter>;
using EvpCipherCtxPtr = bssl::UniquePtr<EVP_CIPHER_CTX>;
using EC_KEY_Ptr = bssl::UniquePtr<EC_KEY>;
using EVP_PKEY_Ptr = bssl::UniquePtr<EVP_PKEY>;
using EVP_PKEY_CTX_Ptr = bssl::UniquePtr<EVP_PKEY_CTX>;
using EC_GROUP_Ptr = bssl::UniquePtr<EC_GROUP>;
using EC_POINT_Ptr = bssl::UniquePtr<EC_POINT>;
using ECDSA_SIG_Ptr = bssl::UniquePtr<ECDSA_SIG>;
using X509_Ptr = bssl::UniquePtr<X509>;
using PKCS12_Ptr = bssl::UniquePtr<PKCS12>;
using BIGNUM_Ptr = bssl::UniquePtr<BIGNUM>;
using ASN1_INTEGER_Ptr = bssl::UniquePtr<ASN1_INTEGER>;
using ASN1_TIME_Ptr = bssl::UniquePtr<ASN1_TIME>;
using ASN1_OCTET_STRING_Ptr = bssl::UniquePtr<ASN1_OCTET_STRING>;
using ASN1_OBJECT_Ptr = bssl::UniquePtr<ASN1_OBJECT>;
using X509_NAME_Ptr = bssl::UniquePtr<X509_NAME>;
using X509_EXTENSION_Ptr = bssl::UniquePtr<X509_EXTENSION>;
// bool getRandom(size_t numBytes, vector<uint8_t>& output) {
optional<vector<uint8_t>> getRandom(size_t numBytes) {
@@ -534,115 +541,6 @@ optional<vector<uint8_t>> encryptAes128Gcm(const vector<uint8_t>& key, const vec
return encryptedData;
}
struct EC_KEY_Deleter {
void operator()(EC_KEY* key) const {
if (key != nullptr) {
EC_KEY_free(key);
}
}
};
using EC_KEY_Ptr = unique_ptr<EC_KEY, EC_KEY_Deleter>;
struct EVP_PKEY_Deleter {
void operator()(EVP_PKEY* key) const {
if (key != nullptr) {
EVP_PKEY_free(key);
}
}
};
using EVP_PKEY_Ptr = unique_ptr<EVP_PKEY, EVP_PKEY_Deleter>;
struct EVP_PKEY_CTX_Deleter {
void operator()(EVP_PKEY_CTX* ctx) const {
if (ctx != nullptr) {
EVP_PKEY_CTX_free(ctx);
}
}
};
using EVP_PKEY_CTX_Ptr = unique_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_Deleter>;
struct EC_GROUP_Deleter {
void operator()(EC_GROUP* group) const {
if (group != nullptr) {
EC_GROUP_free(group);
}
}
};
using EC_GROUP_Ptr = unique_ptr<EC_GROUP, EC_GROUP_Deleter>;
struct EC_POINT_Deleter {
void operator()(EC_POINT* point) const {
if (point != nullptr) {
EC_POINT_free(point);
}
}
};
using EC_POINT_Ptr = unique_ptr<EC_POINT, EC_POINT_Deleter>;
struct ECDSA_SIG_Deleter {
void operator()(ECDSA_SIG* sig) const {
if (sig != nullptr) {
ECDSA_SIG_free(sig);
}
}
};
using ECDSA_SIG_Ptr = unique_ptr<ECDSA_SIG, ECDSA_SIG_Deleter>;
struct X509_Deleter {
void operator()(X509* x509) const {
if (x509 != nullptr) {
X509_free(x509);
}
}
};
using X509_Ptr = unique_ptr<X509, X509_Deleter>;
struct PKCS12_Deleter {
void operator()(PKCS12* pkcs12) const {
if (pkcs12 != nullptr) {
PKCS12_free(pkcs12);
}
}
};
using PKCS12_Ptr = unique_ptr<PKCS12, PKCS12_Deleter>;
struct BIGNUM_Deleter {
void operator()(BIGNUM* bignum) const {
if (bignum != nullptr) {
BN_free(bignum);
}
}
};
using BIGNUM_Ptr = unique_ptr<BIGNUM, BIGNUM_Deleter>;
struct ASN1_INTEGER_Deleter {
void operator()(ASN1_INTEGER* value) const {
if (value != nullptr) {
ASN1_INTEGER_free(value);
}
}
};
using ASN1_INTEGER_Ptr = unique_ptr<ASN1_INTEGER, ASN1_INTEGER_Deleter>;
struct ASN1_TIME_Deleter {
void operator()(ASN1_TIME* value) const {
if (value != nullptr) {
ASN1_TIME_free(value);
}
}
};
using ASN1_TIME_Ptr = unique_ptr<ASN1_TIME, ASN1_TIME_Deleter>;
struct X509_NAME_Deleter {
void operator()(X509_NAME* value) const {
if (value != nullptr) {
X509_NAME_free(value);
}
}
};
using X509_NAME_Ptr = unique_ptr<X509_NAME, X509_NAME_Deleter>;
vector<uint8_t> certificateChainJoin(const vector<vector<uint8_t>>& certificateChain) {
vector<uint8_t> ret;
for (const vector<uint8_t>& certificate : certificateChain) {
@@ -1221,8 +1119,19 @@ optional<vector<uint8_t>> ecKeyPairGetPrivateKey(const vector<uint8_t>& keyPair)
return {};
}
vector<uint8_t> privateKey;
privateKey.resize(BN_num_bytes(bignum));
BN_bn2bin(bignum, privateKey.data());
// Note that this may return fewer than 32 bytes so pad with zeroes since we
// want to always return 32 bytes.
size_t numBytes = BN_num_bytes(bignum);
if (numBytes > 32) {
LOG(ERROR) << "Size is " << numBytes << ", expected this to be 32 or less";
return {};
}
privateKey.resize(32);
for (size_t n = 0; n < 32 - numBytes; n++) {
privateKey[n] = 0x00;
}
BN_bn2bin(bignum, privateKey.data() + 32 - numBytes);
return privateKey;
}
@@ -1379,7 +1288,8 @@ optional<vector<uint8_t>> ecKeyPairGetPkcs12(const vector<uint8_t>& keyPair, con
optional<vector<uint8_t>> ecPublicKeyGenerateCertificate(
const vector<uint8_t>& publicKey, const vector<uint8_t>& signingKey,
const string& serialDecimal, const string& issuer, const string& subject,
time_t validityNotBefore, time_t validityNotAfter) {
time_t validityNotBefore, time_t validityNotAfter,
const map<string, vector<uint8_t>>& extensions) {
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) !=
@@ -1482,6 +1392,32 @@ optional<vector<uint8_t>> ecPublicKeyGenerateCertificate(
return {};
}
for (auto const& [oidStr, blob] : extensions) {
ASN1_OBJECT_Ptr oid(
OBJ_txt2obj(oidStr.c_str(), 1)); // accept numerical dotted string form only
if (!oid.get()) {
LOG(ERROR) << "Error setting OID";
return {};
}
ASN1_OCTET_STRING_Ptr octetString(ASN1_OCTET_STRING_new());
if (!ASN1_OCTET_STRING_set(octetString.get(), blob.data(), blob.size())) {
LOG(ERROR) << "Error setting octet string for extension";
return {};
}
X509_EXTENSION_Ptr extension = X509_EXTENSION_Ptr(X509_EXTENSION_new());
extension.reset(X509_EXTENSION_create_by_OBJ(nullptr, oid.get(), 0 /* not critical */,
octetString.get()));
if (!extension.get()) {
LOG(ERROR) << "Error setting extension";
return {};
}
if (!X509_add_ext(x509.get(), extension.get(), -1)) {
LOG(ERROR) << "Error adding extension";
return {};
}
}
if (X509_sign(x509.get(), privPkey.get(), EVP_sha256()) == 0) {
LOG(ERROR) << "Error signing X509 certificate";
return {};
@@ -1650,6 +1586,44 @@ optional<vector<uint8_t>> certificateChainGetTopMostKey(const vector<uint8_t>& c
return publicKey;
}
optional<vector<uint8_t>> certificateGetExtension(const vector<uint8_t>& x509Certificate,
const string& oidStr) {
vector<X509_Ptr> certs;
if (!parseX509Certificates(x509Certificate, certs)) {
return {};
}
if (certs.size() < 1) {
LOG(ERROR) << "No certificates in chain";
return {};
}
ASN1_OBJECT_Ptr oid(
OBJ_txt2obj(oidStr.c_str(), 1)); // accept numerical dotted string form only
if (!oid.get()) {
LOG(ERROR) << "Error setting OID";
return {};
}
int location = X509_get_ext_by_OBJ(certs[0].get(), oid.get(), -1 /* search from beginning */);
if (location == -1) {
return {};
}
X509_EXTENSION* ext = X509_get_ext(certs[0].get(), location);
if (ext == nullptr) {
return {};
}
ASN1_OCTET_STRING* octetString = X509_EXTENSION_get_data(ext);
if (octetString == nullptr) {
return {};
}
vector<uint8_t> result;
result.resize(octetString->length);
memcpy(result.data(), octetString->data, octetString->length);
return result;
}
optional<pair<size_t, size_t>> certificateFindPublicKey(const vector<uint8_t>& x509Certificate) {
vector<X509_Ptr> certs;
if (!parseX509Certificates(x509Certificate, certs)) {

View File

@@ -271,7 +271,7 @@ vector<uint8_t> generateCertChain(size_t numCerts) {
optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate(
pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0, {});
certs.push_back(cert.value());
}
return support::certificateChainJoin(certs);
@@ -338,7 +338,7 @@ TEST(IdentityCredentialSupport, CertificateChain) {
ASSERT_TRUE(pubKey);
optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate(
pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0, {});
optional<vector<uint8_t>> extractedPubKey =
support::certificateChainGetTopMostKey(cert.value());
@@ -358,7 +358,7 @@ TEST(IdentityCredentialSupport, CertificateChain) {
optional<vector<uint8_t>> otherPubKey = support::ecKeyPairGetPublicKey(keyPair.value());
ASSERT_TRUE(otherPubKey);
optional<vector<uint8_t>> otherCert = support::ecPublicKeyGenerateCertificate(
otherPubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
otherPubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0, {});
// Now both cert and otherCert are two distinct certificates. Let's make a
// chain and check that certificateChainSplit() works as expected.