From 274bb55f102d1adaac99f41ae4f6dcde9d2c13d2 Mon Sep 17 00:00:00 2001 From: Shawn Willden Date: Wed, 30 Sep 2020 22:39:22 -0600 Subject: [PATCH] Add RemotelyProvisionedComponent HAL. Test: VtsHalRemotelyProvisionedComponentTargetTest Change-Id: I51fb01f4c52949c81f3ad2d694a4afdf0fa67788 --- .../compatibility_matrix.current.xml | 7 + .../IRemotelyProvisionedComponent.aidl | 43 ++ .../security/keymint/MacedPublicKey.aidl | 37 ++ .../security/keymint/ProtectedData.aidl | 37 ++ .../IRemotelyProvisionedComponent.aidl | 262 ++++++++++ .../security/keymint/MacedPublicKey.aidl | 57 +++ .../security/keymint/ProtectedData.aidl | 169 +++++++ security/keymint/aidl/default/Android.bp | 28 +- .../default/RemotelyProvisionedComponent.cpp | 430 ++++++++++++++++ .../default/RemotelyProvisionedComponent.h | 57 +++ ...roid.hardware.security.keymint-service.xml | 4 + security/keymint/aidl/default/service.cpp | 6 +- .../keymint/aidl/vts/functional/Android.bp | 32 +- .../VtsRemotelyProvisionedComponentTests.cpp | 432 ++++++++++++++++ security/keymint/support/Android.bp | 37 ++ security/keymint/support/cppcose.cpp | 467 ++++++++++++++++++ .../keymint/support/include/cppcose/cppcose.h | 288 +++++++++++ .../include/remote_prov/remote_prov_utils.h | 60 +++ .../keymint/support/remote_prov_utils.cpp | 169 +++++++ 19 files changed, 2619 insertions(+), 3 deletions(-) create mode 100644 security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl create mode 100644 security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/MacedPublicKey.aidl create mode 100644 security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ProtectedData.aidl create mode 100644 security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl create mode 100644 security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl create mode 100644 security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl create mode 100644 security/keymint/aidl/default/RemotelyProvisionedComponent.cpp create mode 100644 security/keymint/aidl/default/RemotelyProvisionedComponent.h create mode 100644 security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp create mode 100644 security/keymint/support/cppcose.cpp create mode 100644 security/keymint/support/include/cppcose/cppcose.h create mode 100644 security/keymint/support/include/remote_prov/remote_prov_utils.h create mode 100644 security/keymint/support/remote_prov_utils.cpp diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index f3edb98af5..5b411d2ad2 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -323,6 +323,13 @@ strongbox + + android.hardware.security.keymint + + IRemotelyProvisionedComponent + default + + android.hardware.light 1 diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl new file mode 100644 index 0000000000..a864c3cca9 --- /dev/null +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl @@ -0,0 +1,43 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// 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 -update-api`. +// +// 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 +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.security.keymint; +@VintfStability +interface IRemotelyProvisionedComponent { + byte[] generateEcdsaP256KeyPair(in boolean testMode, out android.hardware.security.keymint.MacedPublicKey macedPublicKey); + void generateCertificateRequest(in boolean testMode, in android.hardware.security.keymint.MacedPublicKey[] keysToSign, in byte[] endpointEncryptionCertChain, in byte[] challenge, out byte[] keysToSignMac, out android.hardware.security.keymint.ProtectedData protectedData); + const int STATUS_FAILED = 1; + const int STATUS_INVALID_MAC = 2; + const int STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 3; + const int STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 4; + const int STATUS_INVALID_EEK = 5; +} diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/MacedPublicKey.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/MacedPublicKey.aidl new file mode 100644 index 0000000000..b4caeed7e3 --- /dev/null +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/MacedPublicKey.aidl @@ -0,0 +1,37 @@ +/* + * 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. + *//////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// 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 -update-api`. +// +// 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 +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.security.keymint; +@VintfStability +parcelable MacedPublicKey { + byte[] macedKey; +} diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ProtectedData.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ProtectedData.aidl new file mode 100644 index 0000000000..46f602f808 --- /dev/null +++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/ProtectedData.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 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. + *//////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// 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 -update-api`. +// +// 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 +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.security.keymint; +@VintfStability +parcelable ProtectedData { + byte[] protectedData; +} diff --git a/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl new file mode 100644 index 0000000000..1b09e9dfb7 --- /dev/null +++ b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl @@ -0,0 +1,262 @@ +/* + * 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. + */ + +package android.hardware.security.keymint; + +import android.hardware.security.keymint.MacedPublicKey; +import android.hardware.security.keymint.ProtectedData; + +/** + * An IRemotelyProvisionedComponent is a secure-side component for which certificates can be + * remotely provisioned. It provides an interface for generating asymmetric key pairs and then + * creating a CertificateRequest that contains the generated public keys, plus other information to + * authenticate the request origin. The CertificateRequest can be sent to a server, which can + * validate the request and create certificates. + * + * This interface does not provide any way to use the generated and certified key pairs. It's + * intended to be implemented by a HAL service that does other things with keys (e.g. Keymint). + * + * The root of trust for secure provisioning is something called the "Boot Certificate Chain", or + * BCC. The BCC is a chain of public key certificates, represented as COSE_Sign1 objects containing + * COSE_Key representations of the public keys. The "root" of the BCC is a self-signed certificate + * for a device-unique public key, denoted DK_pub. All public keys in the BCC are device-unique. The + * public key from each certificate in the chain is used to sign the next certificate in the + * chain. The final, "leaf" certificate contains a public key, denoted KM_pub, whose corresponding + * private key, denoted KM_priv, is available for use by the IRemotelyProvisionedComponent. + * + * BCC Design + * ========== + * + * The BCC is designed to mirror the boot stages of a device, and to prove the content and integrity + * of each firmware image. In a proper BCC, each boot stage hashes its own private key with the code + * and any relevant configuration parameters of the next stage to produce a key pair for the next + * stage. Each stage also uses its own private key to sign the public key of the next stage, + * including in the certificate the hash of the next firmware stage, then loads the next stage, + * passing the private key and certificate to it in a manner that does not leak the private key to + * later boot stages. The BCC root key pair is generated by immutable code (e.g. ROM), from a + * device-unique secret. After the device-unique secret is used, it must be made unavailable to any + * later boot stage. + * + * In this way, booting the device incrementally builds a certificate chain that (a) identifies and + * validates the integrity of every stage and (b) contains a set of public keys that correspond to + * private keys, one known to each stage. Any stage can compute the secrets of all later stages + * (given the necessary input), but no stage can compute the secret of any preceding stage. Updating + * the firmware or configuration of any stage changes the key pair of that stage, and of all + * subsequent stages, and no attacker who compromised the previous version of the updated firmware + * can know or predict the post-update key pairs. + * + * The first BCC certificate is special because its contained public key, DK_pub, will never change, + * making it a permanent, device-unique identifier. Although the remaining keys in the BCC are also + * device-unique, they are not necessarily permanent, since they can change when the device software + * is updated. + * + * When the provisioning server receives a message signed by KM_priv and containing a BCC that + * chains from DK_pub to KM_pub, it can be certain that (barring vulnerabilities in some boot + * stage), the CertificateRequest came from the device associated with DK_pub, running the specific + * software identified by the certificates in the BCC. If the server has some mechanism for knowing + * which the DK_pub values of "valid" devices, it can determine whether signing certificates is + * appropriate. + * + * Degenerate BCCs + * =============== + * + * While a proper BCC, as described above, reflects the complete boot sequence from boot ROM to the + * secure area image of the IRemotelyProvisionedComponent, it's also possible to use a "degenerate" + * BCC which consists only of a single, self-signed certificate containing the public key of a + * hardware-bound key pair. This is an appopriate solution for devices which haven't implemented + * everything necessary to produce a proper BCC, but can derive a unique key pair in the secure + * area. In this degenerate case, DK_pub is the same as KM_pub. + * + * BCC Privacy + * =========== + * + * Because the BCC constitutes an unspoofable, device-unique identifier, special care is taken to + * prevent its availability to entities who may wish to track devices. Two precautions are taken: + * + * 1. The BCC is never exported from the IRemotelyProvisionedComponent except in encrypted + * form. The portion of the CertificateRequest that contains the BCC is encrypted using an + * Endpoint Encryption Key (EEK). The EEK is provided in the form of a certificate chain whose + * root must be pre-provisioned into the secure area (hardcoding the roots into the secure area + * firmware image is a recommended approach). Multiple roots may be provisioned. If the provided + * EEK does not chain back to this already-known root, the IRemotelyProvisionedComponent must + * reject it. + * + * 2. Precaution 1 above ensures that only an entity with a valid EEK private key can decrypt the + * BCC. To make it feasible to build a provisioning server which cannot use the BCC to track + * devices, the CertificateRequest is structured so that the server can be partitioned into two + * components. The "decrypter" decrypts the BCC, verifies DK_pub and the device's right to + * receive provisioned certificates, but does not see the public keys to be signed or the + * resulting certificates. The "certifier" gets informed of the results of the decrypter's + * validation and sees the public keys to be signed and resulting certificates, but does not see + * the BCC. + * + * Test Mode + * ========= + * + * The IRemotelyProvisionedComponent supports a test mode, allowing the generation of test key pairs + * and test CertificateRequests. Test keys/requests are annotated as such, and the BCC used for test + * CertificateRequests must contain freshly-generated keys, not the real BCC key pairs. + */ +@VintfStability +interface IRemotelyProvisionedComponent { + const int STATUS_FAILED = 1; + const int STATUS_INVALID_MAC = 2; + const int STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 3; + const int STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 4; + const int STATUS_INVALID_EEK = 5; + + /** + * generateKeyPair generates a new ECDSA P-256 key pair that can be certified. Note that this + * method only generates ECDSA P-256 key pairs, but the interface can be extended to add methods + * for generating keys for other algorithms, if necessary. + * + * @param in boolean testMode indicates whether the generated key is for testing only. Test keys + * are marked (see the definition of PublicKey in the MacedPublicKey structure) to + * prevent them from being confused with production keys. + * + * @param out MacedPublicKey macedPublicKey contains the public key of the generated key pair, + * MACed so that generateCertificateRequest can easily verify, without the + * privateKeyHandle, that the contained public key is for remote certification. + * + * @return data representing a handle to the private key. The format is implementation-defined, + * but note that specific services may define a required format. + */ + byte[] generateEcdsaP256KeyPair(in boolean testMode, out MacedPublicKey macedPublicKey); + + /** + * generateCertificateRequest creates a certificate request to be sent to the provisioning + * server. + * + * @param in boolean testMode indicates whether the generated certificate request is for testing + * only. + * + * @param in MacedPublicKey[] keysToSign contains the set of keys to certify. The + * IRemotelyProvisionedComponent must validate the MACs on each key. If any entry in the + * array lacks a valid MAC, the method must return STATUS_INVALID_MAC. + * + * If testMode is true, the keysToCertify array must contain only keys flagged as test + * keys. Otherwise, the method must return STATUS_PRODUCTION_KEY_IN_TEST_REQUEST. + * + * If testMode is false, the keysToCertify array must not contain any keys flagged as + * test keys. Otherwise, the method must return STATUS_TEST_KEY_IN_PRODUCTION_REQUEST. + * + * @param in endpointEncryptionKey contains an X25519 public key which will be used to encrypt + * the BCC. For flexibility, this is represented as a certificate chain, represented as a + * CBOR array of COSE_Sign1 objects, ordered from root to leaf. The leaf contains the + * X25519 encryption key, each other element is an Ed25519 key signing the next in the + * chain. The root is self-signed. + * + * EekChain = [ + SignedSignatureKey, SignedEek ] + * + * SignedSignatureKey = [ // COSE_Sign1 + * protected: bstr .cbor { + * 1 : -8, // Algorithm : EdDSA + * }, + * unprotected: bstr .size 0 + * payload: bstr .cbor SignatureKey, + * signature: bstr PureEd25519(.cbor SignatureKeySignatureInput) + * ] + * + * SignatureKey = { // COSE_Key + * 1 : 1, // Key type : Octet Key Pair + * 3 : -8, // Algorithm : EdDSA + * -1 : 6, // Curve : Ed25519 + * -2 : bstr // Ed25519 public key + * } + * + * SignatureKeySignatureInput = [ + * context: "Signature1", + * body_protected: bstr .cbor { + * 1 : -8, // Algorithm : EdDSA + * }, + * external_aad: bstr .size 0, + * payload: bstr .cbor SignatureKey + * ] + * + * SignedEek = [ // COSE_Sign1 + * protected: bstr .cbor { + * 1 : -8, // Algorithm : EdDSA + * }, + * unprotected: bstr .size 0 + * payload: bstr .cbor Eek, + * signature: bstr PureEd25519(.cbor EekSignatureInput) + * ] + * + * Eek = { // COSE_Key + * 1 : 1, // Key type : Octet Key Pair + * 2 : bstr // KID : EEK ID + * 3 : -25, // Algorithm : ECDH-ES + HKDF-256 + * -1 : 4, // Curve : X25519 + * -2 : bstr // Ed25519 public key + * } + * + * EekSignatureInput = [ + * context: "Signature1", + * body_protected: bstr .cbor { + * 1 : -8, // Algorithm : EdDSA + * }, + * external_aad: bstr .size 0, + * payload: bstr .cbor Eek + * ] + * + * If the contents of endpointEncryptionKey do not match the SignedEek structure above, + * the method must return STATUS_INVALID_EEK. + * + * If testMode is true, the method must ignore the length and content of the signatures + * in the chain, which implies that it must not attempt to validate the signature. + * + * If testMode is false, the method must validate the chain signatures, and must verify + * that the public key in the root certifictate is in its pre-configured set of + * authorized EEK root keys. If the public key is not in the database, or if signature + * verification fails, the method must return STATUS_INVALID_EEK. + * + * @param in challenge contains a byte string from the provisioning server that must be signed + * by the secure area. See the description of the 'signature' output parameter for + * details. + * + * @param out keysToSignMac contains the MAC of KeysToSign in the CertificateRequest + * structure. Specifically, it contains: + * + * HMAC-256(EK_mac, .cbor KeysToMacStructure) + * + * Where EK_mac is an ephemeral MAC key, found in ProtectedData (see below). The MACed + * data is the "tag" field of a COSE_Mac0 structure like: + * + * MacedKeys = [ // COSE_Mac0 + * protected : bstr .cbor { + * 1 : 5, // Algorithm : HMAC-256 + * }, + * unprotected : bstr .size 0, + * // Payload is PublicKeys from keysToSign argument, in provided order. + * payload: bstr .cbor [ * PublicKey ], + * tag: bstr + * ] + * + * KeysToMacStructure = [ + * context : "MAC0", + * protected : bstr .cbor { 1 : 5 }, // Algorithm : HMAC-256 + * external_aad : bstr .size 0, + * // Payload is PublicKeys from keysToSign argument, in provided order. + * payload : bstr .cbor [ * PublicKey ] + * ] + * + * @param out ProtectedData contains the encrypted BCC and the ephemeral MAC key used to + * authenticate the keysToSign (see keysToSignMac output argument). + */ + void generateCertificateRequest(in boolean testMode, in MacedPublicKey[] keysToSign, + in byte[] endpointEncryptionCertChain, in byte[] challenge, out byte[] keysToSignMac, + out ProtectedData protectedData); +} diff --git a/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl new file mode 100644 index 0000000000..da85a5048f --- /dev/null +++ b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl @@ -0,0 +1,57 @@ +/* + * 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. + */ + +package android.hardware.security.keymint; + +/** + * MacedPublicKey contains a CBOR-encoded public key, MACed by an IRemotelyProvisionedComponent, to + * prove that the key pair was generated by that component. + */ +@VintfStability +parcelable MacedPublicKey { + /** + * key is a COSE_Mac0 structure containing the new public key. It's MACed by a key available + * only to the secure environment, as proof that the public key was generated by that + * environment. In CDDL, assuming the contained key is an Ed25519 public key: + * + * MacedPublicKey = [ // COSE_Mac0 + * protected: bstr .cbor { 1 : 5}, // Algorithm : HMAC-256 + * unprotected: bstr .size 0, + * payload : bstr .cbor PublicKey, + * tag : bstr HMAC-256(K_mac, MAC_structure) + * ] + * + * PublicKey = { // COSE_Key + * 1 : 1, // Key type : octet key pair + * 3 : -8 // Algorithm : EdDSA + * -1 : 6, // Curve : Ed25519 + * -2 : bstr // X coordinate, little-endian + * ? -70000 : nil // Presence indicates this is a test key. If set, K_mac is + * // all zeros. + * }, + * + * MAC_structure = [ + * context : "MAC0", + * protected : bstr .cbor { 1 : 5 }, + * external_aad : bstr .size 0, + * payload : bstr .cbor PublicKey + * ] + * + * if a non-Ed25519 public key were contained, the contents of the PublicKey map would change a + * little; see RFC 8152 for details. + */ + byte[] macedKey; +} diff --git a/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl b/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl new file mode 100644 index 0000000000..1ec3bf0718 --- /dev/null +++ b/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl @@ -0,0 +1,169 @@ +/* + * Copyright (C) 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. + */ + +package android.hardware.security.keymint; + +/** + * ProtectedData contains the encrypted BCC and the ephemeral MAC key used to + * authenticate the keysToSign (see keysToSignMac output argument). + */ +@VintfStability +parcelable ProtectedData { + /** + * ProtectedData is a COSE_Encrypt structure, specified by the following CDDL + * + * ProtectedData = [ // COSE_Encrypt + * protected: bstr .cbor { + * 1 : 3 // Algorithm : AES-GCM 256 + * }, + * unprotected: { + * 5 : bstr .size 12 // IV + * }, + * ciphertext: bstr, // AES-GCM-128(K, .cbor ProtectedDataPayload) + * recipients : [ + * [ // COSE_Recipient + * protected : bstr .cbor { + * 1 : -25 // Algorithm : ECDH-ES + HKDF-256 + * }, + * unprotected : { + * -1 : { // COSE_Key + * 1 : 1, // Key type : Octet Key Pair + * -1 : 4, // Curve : X25519 + * -2 : bstr // Sender X25519 public key + * } + * 4 : bstr, // KID : EEK ID + * }, + * ciphertext : nil + * ] + * ] + * ] + * + * K = HKDF-256(ECDH(EEK_pub, Ephemeral_priv), Context) + * + * Context = [ // COSE_KDF_Context + * AlgorithmID : 3 // AES-GCM 256 + * PartyUInfo : [ + * identity : bstr "client" + * nonce : bstr .size 0, + * other : bstr // Ephemeral pubkey + * ], + * PartyVInfo : [ + * identity : bstr "server", + * nonce : bstr .size 0, + * other : bstr // EEK pubkey + * ], + * SuppPubInfo : [ + * 128, // Output key length + * protected : bstr .size 0 + * ] + * ] + * + * ProtectedDataPayload [ + * SignedMac, + * Bcc, + * ] + * + * SignedMac = [ // COSE_Sign1 + * bstr .cbor { // Protected params + * 1 : -8, // Algorithm : EdDSA + * }, + * bstr .size 0, // Unprotected params + * bstr .size 32, // MAC key + * bstr PureEd25519(DK_priv, .cbor SignedMac_structure) + * ] + * + * SignedMac_structure = [ + * "Signature1", + * bstr .cbor { // Protected params + * 1 : -8, // Algorithm : EdDSA + * }, + * bstr .cbor SignedMacAad + * bstr .size 32 // MAC key + * ] + * + * SignedMacAad = [ + * challenge : bstr, + * DeviceInfo + * ] + * + * Bcc = [ + * PubKey, // DK_pub + * + BccEntry, // Root -> leaf (KM_pub) + * ] + * + * BccPayload = { // CWT + * 1 : tstr, // Issuer + * 2 : tstr, // Subject + * // See the Open Profile for DICE for details on these fields. + * ? -4670545 : bstr, // Code Hash + * ? -4670546 : bstr, // Code Descriptor + * ? -4670547 : bstr, // Configuration Hash + * ? -4670548 : bstr .cbor { // Configuration Descriptor + * ? -70002 : tstr, // Component name + * ? -70003 : int, // Firmware version + * ? -70004 : null, // Resettable + * }, + * ? -4670549 : bstr, // Authority Hash + * ? -4670550 : bstr, // Authority Descriptor + * ? -4670551 : bstr, // Mode + * -4670552 : bstr .cbor PubKey // Subject Public Key + * -4670553 : bstr // Key Usage + * } + * + * BccEntry = [ // COSE_Sign1 + * protected: bstr .cbor { + * 1 : -8, // Algorithm : EdDSA + * }, + * unprotected: bstr .size 0, + * payload: bstr .cbor BccPayload, + * // First entry in the chain is signed by DK_pub, the others are each signed by their + * // immediate predecessor. See RFC 8032 for signature representation. + * signature: bstr .cbor PureEd25519(SigningKey, bstr .cbor BccEntryInput) + * ] + * + * PubKey = { // COSE_Key + * 1 : 1, // Key type : octet key pair + * 3 : -8, // Algorithm : EdDSA + * 4 : 2, // Ops: Verify + * -1 : 6, // Curve : Ed25519 + * -2 : bstr // X coordinate, little-endian + * } + * + * BccEntryInput = [ + * context: "Signature1", + * protected: bstr .cbor { + * 1 : -8, // Algorithm : EdDSA + * }, + * external_aad: bstr .size 0, + * payload: bstr .cbor BccPayload + * ] + * + * DeviceInfo = { + * ? "brand" : tstr, + * ? "manufacturer" : tstr, + * ? "product" : tstr, + * ? "model" : tstr, + * ? "board" : tstr, + * ? "vb_state" : "green" / "yellow" / "orange", + * ? "bootloader_state" : "locked" / "unlocked", + * ? "os_version" : tstr, + * ? "system_patch_level" : uint, // YYYYMMDD + * ? "boot_patch_level" : uint, // YYYYMMDD + * ? "vendor_patch_level" : uint, // YYYYMMDD + * } + */ + byte[] protectedData; +} diff --git a/security/keymint/aidl/default/Android.bp b/security/keymint/aidl/default/Android.bp index 9b7e081314..e9f3be0a69 100644 --- a/security/keymint/aidl/default/Android.bp +++ b/security/keymint/aidl/default/Android.bp @@ -18,15 +18,41 @@ cc_binary { "android.hardware.security.secureclock-unstable-ndk_platform", "libbase", "libbinder_ndk", - "libcppbor", + "libcppbor_external", "libcrypto", "libkeymaster_portable", "libkeymint", "liblog", "libpuresoftkeymasterdevice", + "libremote_provisioner", "libutils", ], srcs: [ "service.cpp", ], } + +cc_library { + name: "libremote_provisioner", + vendor_available: true, + static_libs: [ + "libkeymint_remote_prov_support", + ], + shared_libs: [ + "android.hardware.security.keymint-unstable-ndk_platform", + "libbinder_ndk", + "libcppbor_external", + "libcppcose", + "libcrypto", + "libkeymaster_portable", + "libkeymint", + "liblog", + "libpuresoftkeymasterdevice", + ], + export_include_dirs: [ + ".", + ], + srcs: [ + "RemotelyProvisionedComponent.cpp", + ], +} diff --git a/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp new file mode 100644 index 0000000000..f2651fbce7 --- /dev/null +++ b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp @@ -0,0 +1,430 @@ +/* + * Copyright (C) 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. + */ + +#include "RemotelyProvisionedComponent.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace aidl::android::hardware::security::keymint { + +using ::std::string; +using ::std::tuple; +using ::std::unique_ptr; +using ::std::variant; +using ::std::vector; +using bytevec = ::std::vector; + +using namespace cppcose; +using namespace keymaster; + +namespace { + +constexpr auto STATUS_FAILED = RemotelyProvisionedComponent::STATUS_FAILED; +constexpr auto STATUS_INVALID_EEK = RemotelyProvisionedComponent::STATUS_INVALID_EEK; +constexpr auto STATUS_INVALID_MAC = RemotelyProvisionedComponent::STATUS_INVALID_MAC; +constexpr uint32_t kAffinePointLength = 32; +struct AStatusDeleter { + void operator()(AStatus* p) { AStatus_delete(p); } +}; + +// TODO(swillden): Remove the dependency on AStatus stuff. The COSE lib should use something like +// StatusOr, but it shouldn't depend on AStatus. +class Status { + public: + Status() {} + Status(int32_t errCode, const std::string& errMsg) + : status_(AStatus_fromServiceSpecificErrorWithMessage(errCode, errMsg.c_str())) {} + explicit Status(const std::string& errMsg) + : status_(AStatus_fromServiceSpecificErrorWithMessage(STATUS_FAILED, errMsg.c_str())) {} + Status(AStatus* status) : status_(status) {} + Status(Status&&) = default; + Status(const Status&) = delete; + + operator ::ndk::ScopedAStatus() && { return ndk::ScopedAStatus(status_.release()); } + + bool isOk() { return !status_; } + + // Don't call getMessage() unless isOk() returns false; + const char* getMessage() const { return AStatus_getMessage(status_.get()); } + + private: + std::unique_ptr status_; +}; + +template +class StatusOr { + public: + StatusOr(AStatus* status) : status_(status) {} + StatusOr(Status status) : status_(std::move(status)) {} + StatusOr(T val) : value_(std::move(val)) {} + + bool isOk() { return status_.isOk(); } + + T* operator->() & { + assert(isOk()); + return &value_.value(); + } + T& operator*() & { + assert(isOk()); + return value_.value(); + } + T&& operator*() && { + assert(isOk()); + return std::move(value_).value(); + } + + const char* getMessage() const { + assert(!isOk()); + return status_.getMessage(); + } + + Status moveError() { + assert(!isOk()); + return std::move(status_); + } + + T moveValue() { return std::move(value_).value(); } + + private: + Status status_; + std::optional value_; +}; + +StatusOr> validateAndExtractEekPubAndId( + bool testMode, const bytevec& endpointEncryptionCertChain) { + auto [item, newPos, errMsg] = cppbor::parse(endpointEncryptionCertChain); + + if (!item || !item->asArray()) { + return Status("Error parsing EEK chain" + errMsg); + } + + const cppbor::Array* certArr = item->asArray(); + bytevec lastPubKey; + for (int i = 0; i < certArr->size(); ++i) { + auto cosePubKey = verifyAndParseCoseSign1(testMode, certArr->get(i)->asArray(), + std::move(lastPubKey), bytevec{} /* AAD */); + if (!cosePubKey) { + return Status(STATUS_INVALID_EEK, + "Failed to validate EEK chain: " + cosePubKey.moveMessage()); + } + lastPubKey = *std::move(cosePubKey); + } + + auto eek = CoseKey::parseX25519(lastPubKey, true /* requireKid */); + if (!eek) return Status(STATUS_INVALID_EEK, "Failed to get EEK: " + eek.moveMessage()); + + return std::make_pair(eek->getBstrValue(CoseKey::PUBKEY_X).value(), + eek->getBstrValue(CoseKey::KEY_ID).value()); +} + +StatusOr validateAndExtractPubkeys(bool testMode, + const vector& keysToSign, + const bytevec& macKey) { + auto pubKeysToMac = cppbor::Array(); + for (auto& keyToSign : keysToSign) { + auto [macedKeyItem, _, coseMacErrMsg] = cppbor::parse(keyToSign.macedKey); + if (!macedKeyItem || !macedKeyItem->asArray() || + macedKeyItem->asArray()->size() != kCoseMac0EntryCount) { + return Status("Invalid COSE_Mac0 structure"); + } + + auto protectedParms = macedKeyItem->asArray()->get(kCoseMac0ProtectedParams)->asBstr(); + auto unprotectedParms = macedKeyItem->asArray()->get(kCoseMac0UnprotectedParams)->asBstr(); + auto payload = macedKeyItem->asArray()->get(kCoseMac0Payload)->asBstr(); + auto tag = macedKeyItem->asArray()->get(kCoseMac0Tag)->asBstr(); + if (!protectedParms || !unprotectedParms || !payload || !tag) { + return Status("Invalid COSE_Mac0 contents"); + } + + auto [protectedMap, __, errMsg] = cppbor::parse(protectedParms); + if (!protectedMap || !protectedMap->asMap()) { + return Status("Invalid Mac0 protected: " + errMsg); + } + auto& algo = protectedMap->asMap()->get(ALGORITHM); + if (!algo || !algo->asInt() || algo->asInt()->value() != HMAC_256) { + return Status("Unsupported Mac0 algorithm"); + } + + auto pubKey = CoseKey::parse(payload->value(), EC2, ES256, P256); + if (!pubKey) return Status(pubKey.moveMessage()); + + bool testKey = static_cast(pubKey->getMap().get(CoseKey::TEST_KEY)); + if (testMode && !testKey) { + return Status(BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST, + "Production key in test request"); + } else if (!testMode && testKey) { + return Status(BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST, + "Test key in production request"); + } + + auto macTag = generateCoseMac0Mac(macKey, {} /* external_aad */, payload->value()); + if (!macTag) return Status(STATUS_INVALID_MAC, macTag.moveMessage()); + if (macTag->size() != tag->value().size() || + CRYPTO_memcmp(macTag->data(), tag->value().data(), macTag->size()) != 0) { + return Status(STATUS_INVALID_MAC, "MAC tag mismatch"); + } + + pubKeysToMac.add(pubKey->moveMap()); + } + + return pubKeysToMac.encode(); +} + +StatusOr> buildCosePublicKeyFromKmCert( + const keymaster_blob_t* km_cert) { + if (km_cert == nullptr) { + return Status(STATUS_FAILED, "km_cert is a nullptr"); + } + const uint8_t* temp = km_cert->data; + X509* cert = d2i_X509(NULL, &temp, km_cert->data_length); + if (cert == nullptr) { + return Status(STATUS_FAILED, "d2i_X509 returned null when attempting to get the cert."); + } + EVP_PKEY* pubKey = X509_get_pubkey(cert); + if (pubKey == nullptr) { + return Status(STATUS_FAILED, "Boringssl failed to get the public key from the cert"); + } + EC_KEY* ecKey = EVP_PKEY_get0_EC_KEY(pubKey); + if (ecKey == nullptr) { + return Status(STATUS_FAILED, + "The key in the certificate returned from GenerateKey is not " + "an EC key."); + } + const EC_POINT* jacobian_coords = EC_KEY_get0_public_key(ecKey); + BIGNUM x; + BIGNUM y; + BN_CTX* ctx = BN_CTX_new(); + if (ctx == nullptr) { + return Status(STATUS_FAILED, "Memory allocation failure for BN_CTX"); + } + if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ecKey), jacobian_coords, &x, &y, + ctx)) { + return Status(STATUS_FAILED, "Failed to get affine coordinates"); + } + bytevec x_bytestring(kAffinePointLength); + bytevec y_bytestring(kAffinePointLength); + if (BN_bn2binpad(&x, x_bytestring.data(), kAffinePointLength) != kAffinePointLength) { + return Status(STATUS_FAILED, "Wrote incorrect number of bytes for x coordinate"); + } + if (BN_bn2binpad(&y, y_bytestring.data(), kAffinePointLength) != kAffinePointLength) { + return Status(STATUS_FAILED, "Wrote incorrect number of bytes for y coordinate"); + } + BN_CTX_free(ctx); + return std::make_pair(x_bytestring, y_bytestring); +} + +cppbor::Array buildCertReqRecipients(const bytevec& pubkey, const bytevec& kid) { + return cppbor::Array() // Array of recipients + .add(cppbor::Array() // Recipient + .add(cppbor::Map() // Protected + .add(ALGORITHM, ECDH_ES_HKDF_256) + .canonicalize() + .encode()) + .add(cppbor::Map() // Unprotected + .add(COSE_KEY, cppbor::Map() + .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR) + .add(CoseKey::CURVE, cppcose::X25519) + .add(CoseKey::PUBKEY_X, pubkey) + .canonicalize()) + .add(KEY_ID, kid) + .canonicalize()) + .add(cppbor::Null())); // No ciphertext +} + +static keymaster_key_param_t kKeyMintEcdsaP256Params[] = { + Authorization(TAG_PURPOSE, KM_PURPOSE_SIGN), Authorization(TAG_ALGORITHM, KM_ALGORITHM_EC), + Authorization(TAG_KEY_SIZE, 256), Authorization(TAG_DIGEST, KM_DIGEST_SHA_2_256), + Authorization(TAG_EC_CURVE, KM_EC_CURVE_P_256), Authorization(TAG_NO_AUTH_REQUIRED), + // The certificate generated by KM will be discarded, these values don't matter. + Authorization(TAG_CERTIFICATE_NOT_BEFORE, 0), Authorization(TAG_CERTIFICATE_NOT_AFTER, 0)}; + +} // namespace + +RemotelyProvisionedComponent::RemotelyProvisionedComponent( + std::shared_ptr keymint) { + std::tie(devicePrivKey_, bcc_) = generateBcc(); + impl_ = keymint->getKeymasterImpl(); +} + +RemotelyProvisionedComponent::~RemotelyProvisionedComponent() {} + +ScopedAStatus RemotelyProvisionedComponent::generateEcdsaP256KeyPair(bool testMode, + MacedPublicKey* macedPublicKey, + bytevec* privateKeyHandle) { + // TODO(jbires): The following should move from ->GenerateKey to ->GenerateRKPKey and everything + // after the GenerateKey call should basically be moved into that new function call + // as well once the issue with libcppbor in system/keymaster is sorted out + GenerateKeyRequest request(impl_->message_version()); + request.key_description.Reinitialize(kKeyMintEcdsaP256Params, + array_length(kKeyMintEcdsaP256Params)); + GenerateKeyResponse response(impl_->message_version()); + impl_->GenerateKey(request, &response); + if (response.error != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(response.error); + } + + if (response.certificate_chain.entry_count != 1) { + // Error: Need the single non-signed certificate with the public key in it. + return Status(STATUS_FAILED, + "Expected to receive a single certificate from GenerateKey. Instead got: " + + std::to_string(response.certificate_chain.entry_count)); + } + auto affineCoords = buildCosePublicKeyFromKmCert(response.certificate_chain.begin()); + if (!affineCoords.isOk()) return affineCoords.moveError(); + cppbor::Map cosePublicKeyMap = cppbor::Map() + .add(CoseKey::KEY_TYPE, EC2) + .add(CoseKey::ALGORITHM, ES256) + .add(CoseKey::CURVE, cppcose::P256) + .add(CoseKey::PUBKEY_X, affineCoords->first) + .add(CoseKey::PUBKEY_Y, affineCoords->second); + if (testMode) { + cosePublicKeyMap.add(CoseKey::TEST_KEY, cppbor::Null()); + } + + bytevec cosePublicKey = cosePublicKeyMap.canonicalize().encode(); + + auto macedKey = constructCoseMac0(testMode ? remote_prov::kTestMacKey : macKey_, + {} /* externalAad */, cosePublicKey); + if (!macedKey) return Status(macedKey.moveMessage()); + + macedPublicKey->macedKey = macedKey->encode(); + *privateKeyHandle = km_utils::kmBlob2vector(response.key_blob); + return ScopedAStatus::ok(); +} + +ScopedAStatus RemotelyProvisionedComponent::generateCertificateRequest( + bool testMode, const vector& keysToSign, + const bytevec& endpointEncCertChain, const bytevec& challenge, bytevec* keysToSignMac, + ProtectedData* protectedData) { + auto pubKeysToSign = validateAndExtractPubkeys(testMode, keysToSign, + testMode ? remote_prov::kTestMacKey : macKey_); + if (!pubKeysToSign.isOk()) return pubKeysToSign.moveError(); + + bytevec ephemeralMacKey = remote_prov::randomBytes(SHA256_DIGEST_LENGTH); + + auto pubKeysToSignMac = generateCoseMac0Mac(ephemeralMacKey, bytevec{}, *pubKeysToSign); + if (!pubKeysToSignMac) return Status(pubKeysToSignMac.moveMessage()); + *keysToSignMac = *std::move(pubKeysToSignMac); + + bytevec devicePrivKey; + cppbor::Array bcc; + if (testMode) { + std::tie(devicePrivKey, bcc) = generateBcc(); + } else { + devicePrivKey = devicePrivKey_; + bcc = bcc_.clone(); + } + + auto signedMac = constructCoseSign1(devicePrivKey /* Signing key */, // + ephemeralMacKey /* Payload */, + cppbor::Array() /* AAD */ + .add(challenge) + .add(createDeviceInfo()) + .encode()); + if (!signedMac) return Status(signedMac.moveMessage()); + + bytevec ephemeralPrivKey(X25519_PRIVATE_KEY_LEN); + bytevec ephemeralPubKey(X25519_PUBLIC_VALUE_LEN); + X25519_keypair(ephemeralPubKey.data(), ephemeralPrivKey.data()); + + auto eek = validateAndExtractEekPubAndId(testMode, endpointEncCertChain); + if (!eek.isOk()) return eek.moveError(); + + auto sessionKey = x25519_HKDF_DeriveKey(ephemeralPubKey, ephemeralPrivKey, eek->first, + true /* senderIsA */); + if (!sessionKey) return Status(sessionKey.moveMessage()); + + auto coseEncrypted = + constructCoseEncrypt(*sessionKey, remote_prov::randomBytes(kAesGcmNonceLength), + cppbor::Array() // payload + .add(signedMac.moveValue()) + .add(std::move(bcc)) + .encode(), + {}, // aad + buildCertReqRecipients(ephemeralPubKey, eek->second)); + + if (!coseEncrypted) return Status(coseEncrypted.moveMessage()); + protectedData->protectedData = coseEncrypted->encode(); + + return ScopedAStatus::ok(); +} + +bytevec RemotelyProvisionedComponent::deriveBytesFromHbk(const string& context, + size_t numBytes) const { + bytevec fakeHbk(32, 0); + bytevec result(numBytes); + + // TODO(swillden): Figure out if HKDF can fail. It doesn't seem like it should be able to, + // but the function does return an error code. + HKDF(result.data(), numBytes, // + EVP_sha256(), // + fakeHbk.data(), fakeHbk.size(), // + nullptr /* salt */, 0 /* salt len */, // + reinterpret_cast(context.data()), context.size()); + + return result; +} + +bytevec RemotelyProvisionedComponent::createDeviceInfo() const { + return cppbor::Map().encode(); +} + +std::pair +RemotelyProvisionedComponent::generateBcc() { + bytevec privKey(ED25519_PRIVATE_KEY_LEN); + bytevec pubKey(ED25519_PUBLIC_KEY_LEN); + + ED25519_keypair(pubKey.data(), privKey.data()); + + auto coseKey = cppbor::Map() + .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR) + .add(CoseKey::ALGORITHM, EDDSA) + .add(CoseKey::CURVE, ED25519) + .add(CoseKey::KEY_OPS, VERIFY) + .add(CoseKey::PUBKEY_X, pubKey) + .canonicalize() + .encode(); + auto sign1Payload = cppbor::Map() + .add(1 /* Issuer */, "Issuer") + .add(2 /* Subject */, "Subject") + .add(-4670552 /* Subject Pub Key */, coseKey) + .add(-4670553 /* Key Usage */, + std::vector(0x05) /* Big endian order */) + .canonicalize() + .encode(); + auto coseSign1 = constructCoseSign1(privKey, /* signing key */ + cppbor::Map(), /* extra protected */ + sign1Payload, {} /* AAD */); + assert(coseSign1); + + return {privKey, cppbor::Array().add(coseKey).add(coseSign1.moveValue())}; +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/security/keymint/aidl/default/RemotelyProvisionedComponent.h b/security/keymint/aidl/default/RemotelyProvisionedComponent.h new file mode 100644 index 0000000000..e8d2343091 --- /dev/null +++ b/security/keymint/aidl/default/RemotelyProvisionedComponent.h @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace aidl::android::hardware::security::keymint { + +using ::ndk::ScopedAStatus; + +class RemotelyProvisionedComponent : public BnRemotelyProvisionedComponent { + public: + explicit RemotelyProvisionedComponent(std::shared_ptr keymint); + virtual ~RemotelyProvisionedComponent(); + + ScopedAStatus generateEcdsaP256KeyPair(bool testMode, MacedPublicKey* macedPublicKey, + std::vector* privateKeyHandle) override; + + ScopedAStatus generateCertificateRequest(bool testMode, + const std::vector& keysToSign, + const std::vector& endpointEncCertChain, + const std::vector& challenge, + std::vector* keysToSignMac, + ProtectedData* protectedData) override; + + private: + // TODO(swillden): Move these into an appropriate Context class. + std::vector deriveBytesFromHbk(const std::string& context, size_t numBytes) const; + std::vector createDeviceInfo() const; + std::pair, cppbor::Array> generateBcc(); + + std::vector macKey_ = deriveBytesFromHbk("Key to MAC public keys", 32); + std::vector devicePrivKey_; + cppbor::Array bcc_; + std::shared_ptr<::keymaster::AndroidKeymaster> impl_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/security/keymint/aidl/default/android.hardware.security.keymint-service.xml b/security/keymint/aidl/default/android.hardware.security.keymint-service.xml index 73d15a8024..4aa05efe16 100644 --- a/security/keymint/aidl/default/android.hardware.security.keymint-service.xml +++ b/security/keymint/aidl/default/android.hardware.security.keymint-service.xml @@ -3,4 +3,8 @@ android.hardware.security.keymint IKeyMintDevice/default + + android.hardware.security.keymint + IRemotelyProvisionedComponent/default + diff --git a/security/keymint/aidl/default/service.cpp b/security/keymint/aidl/default/service.cpp index 75b394e187..bcebbaf8cf 100644 --- a/security/keymint/aidl/default/service.cpp +++ b/security/keymint/aidl/default/service.cpp @@ -25,7 +25,10 @@ #include #include +#include "RemotelyProvisionedComponent.h" + using aidl::android::hardware::security::keymint::AndroidKeyMintDevice; +using aidl::android::hardware::security::keymint::RemotelyProvisionedComponent; using aidl::android::hardware::security::keymint::SecurityLevel; using aidl::android::hardware::security::secureclock::AndroidSecureClock; using aidl::android::hardware::security::sharedsecret::AndroidSharedSecret; @@ -45,7 +48,6 @@ int main() { // Zero threads seems like a useless pool, but below we'll join this thread to it, increasing // the pool size to 1. ABinderProcess_setThreadPoolMaxThreadCount(0); - // Add Keymint Service std::shared_ptr keyMint = addService(SecurityLevel::SOFTWARE); @@ -53,6 +55,8 @@ int main() { addService(keyMint); // Add Shared Secret Service addService(keyMint); + // Add Remotely Provisioned Component Service + addService(keyMint); ABinderProcess_joinThreadPool(); return EXIT_FAILURE; // should not reach } diff --git a/security/keymint/aidl/vts/functional/Android.bp b/security/keymint/aidl/vts/functional/Android.bp index f4ba9e7362..70f0b8a457 100644 --- a/security/keymint/aidl/vts/functional/Android.bp +++ b/security/keymint/aidl/vts/functional/Android.bp @@ -62,6 +62,36 @@ cc_test_library { static_libs: [ "android.hardware.security.keymint-V1-ndk_platform", "android.hardware.security.secureclock-V1-ndk_platform", - "libcppbor", + "libcppbor_external", + ], +} + +cc_test { + name: "VtsHalRemotelyProvisionedComponentTargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "VtsRemotelyProvisionedComponentTests.cpp", + ], + shared_libs: [ + "libbinder_ndk", + "libcppbor_external", + "libcrypto", + "libkeymaster_portable", + "libpuresoftkeymasterdevice", + ], + static_libs: [ + "android.hardware.security.keymint-unstable-ndk_platform", + "libcppcose", + "libgmock_ndk", + "libremote_provisioner", + "libkeymint", + "libkeymint_remote_prov_support", + ], + test_suites: [ + "general-tests", + "vts", ], } diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp new file mode 100644 index 0000000000..db53a8f8fa --- /dev/null +++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp @@ -0,0 +1,432 @@ +/* + * 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 "VtsRemotelyProvisionableComponentTests" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace aidl::android::hardware::security::keymint::test { + +using ::std::string; +using ::std::vector; + +namespace { + +#define INSTANTIATE_REM_PROV_AIDL_TEST(name) \ + INSTANTIATE_TEST_SUITE_P( \ + PerInstance, name, \ + testing::ValuesIn(VtsRemotelyProvisionedComponentTests::build_params()), \ + ::android::PrintInstanceNameToString) + +using bytevec = std::vector; +using testing::MatchesRegex; +using namespace remote_prov; +using namespace keymaster; + +bytevec string_to_bytevec(const char* s) { + const uint8_t* p = reinterpret_cast(s); + return bytevec(p, p + strlen(s)); +} + +} // namespace + +class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam { + public: + virtual void SetUp() override { + if (AServiceManager_isDeclared(GetParam().c_str())) { + ::ndk::SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str())); + provisionable_ = IRemotelyProvisionedComponent::fromBinder(binder); + } + ASSERT_NE(provisionable_, nullptr); + } + + static vector build_params() { + auto params = ::android::getAidlHalInstanceNames(IRemotelyProvisionedComponent::descriptor); + return params; + } + + protected: + std::shared_ptr provisionable_; +}; + +using GenerateKeyTests = VtsRemotelyProvisionedComponentTests; + +INSTANTIATE_REM_PROV_AIDL_TEST(GenerateKeyTests); + +/** + * Generate and validate a production-mode key. MAC tag can't be verified. + */ +TEST_P(GenerateKeyTests, generateEcdsaP256Key_prodMode) { + MacedPublicKey macedPubKey; + bytevec privateKeyBlob; + bool testMode = false; + auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob); + ASSERT_TRUE(status.isOk()); + + auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey); + ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr; + + ASSERT_NE(coseMac0->asArray(), nullptr); + ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount); + + auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr(); + ASSERT_NE(protParms, nullptr); + ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n 1 : 5,\n}"); + + auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asBstr(); + ASSERT_NE(unprotParms, nullptr); + ASSERT_EQ(unprotParms->value().size(), 0); + + auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr(); + ASSERT_NE(payload, nullptr); + auto [parsedPayload, __, payloadParseErr] = cppbor::parse(payload->value()); + ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr; + EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()), + MatchesRegex("{\n" + " 1 : 2,\n" + " 3 : -7,\n" + " -1 : 1,\n" + // The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a sequence of + // 32 hexadecimal bytes, enclosed in braces and separated by commas. + // In this case, some Ed25519 public key. + " -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" + " -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" + "}")); + + auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr(); + ASSERT_TRUE(coseMac0Tag); + auto extractedTag = coseMac0Tag->value(); + EXPECT_EQ(extractedTag.size(), 32U); + + // Compare with tag generated with kTestMacKey. Shouldn't match. + auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */, + payload->value()); + ASSERT_TRUE(testTag) << "Tag calculation failed: " << testTag.message(); + + EXPECT_NE(*testTag, extractedTag); +} + +/** + * Generate and validate a test-mode key. + */ +TEST_P(GenerateKeyTests, generateEcdsaP256Key_testMode) { + MacedPublicKey macedPubKey; + bytevec privateKeyBlob; + bool testMode = true; + auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob); + ASSERT_TRUE(status.isOk()); + + auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey); + ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr; + + ASSERT_NE(coseMac0->asArray(), nullptr); + ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount); + + auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr(); + ASSERT_NE(protParms, nullptr); + ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n 1 : 5,\n}"); + + auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asBstr(); + ASSERT_NE(unprotParms, nullptr); + ASSERT_EQ(unprotParms->value().size(), 0); + + auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr(); + ASSERT_NE(payload, nullptr); + auto [parsedPayload, __, payloadParseErr] = cppbor::parse(payload->value()); + ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr; + EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()), + MatchesRegex("{\n" + " 1 : 2,\n" + " 3 : -7,\n" + " -1 : 1,\n" + // The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a sequence of + // 32 hexadecimal bytes, enclosed in braces and separated by commas. + // In this case, some Ed25519 public key. + " -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" + " -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" + " -70000 : null,\n" + "}")); + + auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr(); + ASSERT_TRUE(coseMac0); + auto extractedTag = coseMac0Tag->value(); + EXPECT_EQ(extractedTag.size(), 32U); + + // Compare with tag generated with kTestMacKey. Should match. + auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */, + payload->value()); + ASSERT_TRUE(testTag) << testTag.message(); + + EXPECT_EQ(*testTag, extractedTag); +} + +class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { + protected: + CertificateRequestTest() : eekId_(string_to_bytevec("eekid")) { + auto chain = generateEekChain(3, eekId_); + EXPECT_TRUE(chain) << chain.message(); + if (chain) eekChain_ = chain.moveValue(); + } + + void generateKeys(bool testMode, size_t numKeys) { + keysToSign_ = std::vector(numKeys); + cborKeysToSign_ = cppbor::Array(); + + for (auto& key : keysToSign_) { + bytevec privateKeyBlob; + auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &key, &privateKeyBlob); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + auto [parsedMacedKey, _, parseErr] = cppbor::parse(key.macedKey); + ASSERT_TRUE(parsedMacedKey) << "Failed parsing MACed key: " << parseErr; + ASSERT_TRUE(parsedMacedKey->asArray()) << "COSE_Mac0 not an array?"; + ASSERT_EQ(parsedMacedKey->asArray()->size(), kCoseMac0EntryCount); + + auto& payload = parsedMacedKey->asArray()->get(kCoseMac0Payload); + ASSERT_TRUE(payload); + ASSERT_TRUE(payload->asBstr()); + + cborKeysToSign_.add(cppbor::EncodedItem(payload->asBstr()->value())); + } + } + + bytevec eekId_; + EekChain eekChain_; + std::vector keysToSign_; + cppbor::Array cborKeysToSign_; +}; + +/** + * Generate an empty certificate request in test mode, and decrypt and verify the structure and + * content. + */ +TEST_P(CertificateRequestTest, EmptyRequest_testMode) { + bool testMode = true; + bytevec keysToSignMac; + ProtectedData protectedData; + auto challenge = randomBytes(32); + auto status = provisionable_->generateCertificateRequest(testMode, {} /* keysToSign */, + eekChain_.chain, challenge, + &keysToSignMac, &protectedData); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData); + ASSERT_TRUE(parsedProtectedData) << protDataErrMsg; + ASSERT_TRUE(parsedProtectedData->asArray()); + ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount); + + auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData); + ASSERT_TRUE(senderPubkey) << senderPubkey.message(); + EXPECT_EQ(senderPubkey->second, eekId_); + + auto sessionKey = x25519_HKDF_DeriveKey(eekChain_.last_pubkey, eekChain_.last_privkey, + senderPubkey->first, false /* senderIsA */); + ASSERT_TRUE(sessionKey) << sessionKey.message(); + + auto protectedDataPayload = + decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */); + ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message(); + + auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload); + ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg; + ASSERT_TRUE(parsedPayload->asArray()); + EXPECT_EQ(parsedPayload->asArray()->size(), 2U); + + auto& signedMac = parsedPayload->asArray()->get(0); + auto& bcc = parsedPayload->asArray()->get(1); + ASSERT_TRUE(signedMac && signedMac->asArray()); + ASSERT_TRUE(bcc && bcc->asArray()); + + // BCC is [ pubkey, + BccEntry] + auto bccContents = validateBcc(bcc->asArray()); + ASSERT_TRUE(bccContents) << "\n" << bccContents.message() << "\n" << prettyPrint(bcc.get()); + ASSERT_GT(bccContents->size(), 0U); + + auto& signingKey = bccContents->back().pubKey; + auto macKey = verifyAndParseCoseSign1(testMode, signedMac->asArray(), signingKey, + cppbor::Array() // DeviceInfo + .add(challenge) // + .add(cppbor::Map()) + .encode()); + ASSERT_TRUE(macKey) << macKey.message(); + + auto coseMac0 = cppbor::Array() + .add(cppbor::Map() // protected + .add(ALGORITHM, HMAC_256) + .canonicalize() + .encode()) + .add(cppbor::Bstr()) // unprotected + .add(cppbor::Array().encode()) // payload (keysToSign) + .add(std::move(keysToSignMac)); // tag + + auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey); + ASSERT_TRUE(macPayload) << macPayload.message(); +} + +/** + * Generate an empty certificate request in prod mode. Generation will fail because we don't have a + * valid GEEK. + * + * TODO(swillden): Get a valid GEEK and use it so the generation can succeed, though we won't be + * able to decrypt. + */ +TEST_P(CertificateRequestTest, EmptyRequest_prodMode) { + bool testMode = false; + bytevec keysToSignMac; + ProtectedData protectedData; + auto challenge = randomBytes(32); + auto status = provisionable_->generateCertificateRequest(testMode, {} /* keysToSign */, + eekChain_.chain, challenge, + &keysToSignMac, &protectedData); + ASSERT_FALSE(status.isOk()); + ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); +} + +/** + * Generate a non-empty certificate request in test mode. Decrypt, parse and validate the contents. + */ +TEST_P(CertificateRequestTest, NonEmptyRequest_testMode) { + bool testMode = true; + generateKeys(testMode, 4 /* numKeys */); + + bytevec keysToSignMac; + ProtectedData protectedData; + auto challenge = randomBytes(32); + auto status = provisionable_->generateCertificateRequest( + testMode, keysToSign_, eekChain_.chain, challenge, &keysToSignMac, &protectedData); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData); + ASSERT_TRUE(parsedProtectedData) << protDataErrMsg; + ASSERT_TRUE(parsedProtectedData->asArray()); + ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount); + + auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData); + ASSERT_TRUE(senderPubkey) << senderPubkey.message(); + EXPECT_EQ(senderPubkey->second, eekId_); + + auto sessionKey = x25519_HKDF_DeriveKey(eekChain_.last_pubkey, eekChain_.last_privkey, + senderPubkey->first, false /* senderIsA */); + ASSERT_TRUE(sessionKey) << sessionKey.message(); + + auto protectedDataPayload = + decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */); + ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message(); + + auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload); + ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg; + ASSERT_TRUE(parsedPayload->asArray()); + EXPECT_EQ(parsedPayload->asArray()->size(), 2U); + + auto& signedMac = parsedPayload->asArray()->get(0); + auto& bcc = parsedPayload->asArray()->get(1); + ASSERT_TRUE(signedMac && signedMac->asArray()); + ASSERT_TRUE(bcc); + + auto bccContents = validateBcc(bcc->asArray()); + ASSERT_TRUE(bccContents) << "\n" << prettyPrint(bcc.get()); + ASSERT_GT(bccContents->size(), 0U); + + auto& signingKey = bccContents->back().pubKey; + auto macKey = verifyAndParseCoseSign1(testMode, signedMac->asArray(), signingKey, + cppbor::Array() // DeviceInfo + .add(challenge) // + .add(cppbor::Array()) + .encode()); + ASSERT_TRUE(macKey) << macKey.message(); + + auto coseMac0 = cppbor::Array() + .add(cppbor::Map() // protected + .add(ALGORITHM, HMAC_256) + .canonicalize() + .encode()) + .add(cppbor::Bstr()) // unprotected + .add(cborKeysToSign_.encode()) // payload + .add(std::move(keysToSignMac)); // tag + + auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey); + ASSERT_TRUE(macPayload) << macPayload.message(); +} + +/** + * Generate a non-empty certificate request in prod mode. Must fail because we don't have a valid + * GEEK. + * + * TODO(swillden): Get a valid GEEK and use it so the generation can succeed, though we won't be + * able to decrypt. + */ +TEST_P(CertificateRequestTest, NonEmptyRequest_prodMode) { + bool testMode = false; + generateKeys(testMode, 4 /* numKeys */); + + bytevec keysToSignMac; + ProtectedData protectedData; + auto challenge = randomBytes(32); + auto status = provisionable_->generateCertificateRequest( + testMode, keysToSign_, eekChain_.chain, challenge, &keysToSignMac, &protectedData); + ASSERT_FALSE(status.isOk()); + ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); +} + +/** + * Generate a non-empty certificate request in test mode, with prod keys. Must fail with + * STATUS_PRODUCTION_KEY_IN_TEST_REQUEST. + */ +TEST_P(CertificateRequestTest, NonEmptyRequest_prodKeyInTestCert) { + generateKeys(false /* testMode */, 2 /* numKeys */); + + bytevec keysToSignMac; + ProtectedData protectedData; + auto challenge = randomBytes(32); + auto status = provisionable_->generateCertificateRequest(true /* testMode */, keysToSign_, + eekChain_.chain, challenge, + &keysToSignMac, &protectedData); + ASSERT_FALSE(status.isOk()); + ASSERT_EQ(status.getServiceSpecificError(), + BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST); +} + +/** + * Generate a non-empty certificate request in prod mode, with test keys. Must fail with + * STATUS_TEST_KEY_IN_PRODUCTION_REQUEST. + */ +TEST_P(CertificateRequestTest, NonEmptyRequest_testKeyInProdCert) { + generateKeys(true /* testMode */, 2 /* numKeys */); + + bytevec keysToSignMac; + ProtectedData protectedData; + auto status = provisionable_->generateCertificateRequest( + false /* testMode */, keysToSign_, eekChain_.chain, randomBytes(32) /* challenge */, + &keysToSignMac, &protectedData); + ASSERT_FALSE(status.isOk()); + ASSERT_EQ(status.getServiceSpecificError(), + BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST); +} + +INSTANTIATE_REM_PROV_AIDL_TEST(CertificateRequestTest); + +} // namespace aidl::android::hardware::security::keymint::test diff --git a/security/keymint/support/Android.bp b/security/keymint/support/Android.bp index fde6b57683..efdff2bdfc 100644 --- a/security/keymint/support/Android.bp +++ b/security/keymint/support/Android.bp @@ -37,3 +37,40 @@ cc_library { "libutils", ], } + +cc_library { + name: "libkeymint_remote_prov_support", + vendor_available: true, + srcs: [ + "remote_prov_utils.cpp", + ], + export_include_dirs: [ + "include", + ], + shared_libs: [ + "libcppcose", + "libcppbor_external", + "libcrypto", + ], +} + +cc_library { + name: "libcppcose", + vendor_available: true, + srcs: [ + "cppcose.cpp", + ], + export_include_dirs: [ + "include", + ], + shared_libs: [ + "libbinder_ndk", + "libcppbor_external", + "libcrypto", + "liblog", + ], + static_libs: [ + // TODO(swillden): Remove keymint NDK + "android.hardware.security.keymint-unstable-ndk_platform", + ], +} diff --git a/security/keymint/support/cppcose.cpp b/security/keymint/support/cppcose.cpp new file mode 100644 index 0000000000..c626adeccb --- /dev/null +++ b/security/keymint/support/cppcose.cpp @@ -0,0 +1,467 @@ +/* + * 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. + */ + +#include + +#include +#include + +#include +#include + +#include + +namespace cppcose { + +namespace { + +ErrMsgOr> aesGcmInitAndProcessAad(const bytevec& key, + const bytevec& nonce, + const bytevec& aad, + bool encrypt) { + if (key.size() != kAesGcmKeySize) return "Invalid key size"; + + bssl::UniquePtr ctx(EVP_CIPHER_CTX_new()); + if (!ctx) return "Failed to allocate cipher context"; + + if (!EVP_CipherInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr /* engine */, key.data(), + nonce.data(), encrypt ? 1 : 0)) { + return "Failed to initialize cipher"; + } + + int outlen; + if (!aad.empty() && !EVP_CipherUpdate(ctx.get(), nullptr /* out; null means AAD */, &outlen, + aad.data(), aad.size())) { + return "Failed to process AAD"; + } + + return std::move(ctx); +} + +} // namespace + +ErrMsgOr generateCoseMac0Mac(const bytevec& macKey, const bytevec& externalAad, + const bytevec& payload) { + auto macStructure = cppbor::Array() + .add("MAC0") + .add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode()) + .add(externalAad) + .add(payload) + .encode(); + + bytevec macTag(SHA256_DIGEST_LENGTH); + uint8_t* out = macTag.data(); + unsigned int outLen; + out = HMAC(EVP_sha256(), // + macKey.data(), macKey.size(), // + macStructure.data(), macStructure.size(), // + out, &outLen); + + assert(out != nullptr && outLen == macTag.size()); + if (out == nullptr || outLen != macTag.size()) { + return "Error computing public key MAC"; + } + + return macTag; +} + +ErrMsgOr constructCoseMac0(const bytevec& macKey, const bytevec& externalAad, + const bytevec& payload) { + auto tag = generateCoseMac0Mac(macKey, externalAad, payload); + if (!tag) return tag.moveMessage(); + + return cppbor::Array() + .add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode()) + .add(cppbor::Bstr() /* unprotected */) + .add(payload) + .add(tag.moveValue()); +} + +ErrMsgOr parseCoseMac0(const cppbor::Item* macItem) { + auto mac = macItem ? macItem->asArray() : nullptr; + if (!mac || mac->size() != kCoseMac0EntryCount) { + return "Invalid COSE_Mac0"; + } + + auto protectedParms = mac->get(kCoseMac0ProtectedParams)->asBstr(); + auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asBstr(); + auto payload = mac->get(kCoseMac0Payload)->asBstr(); + auto tag = mac->get(kCoseMac0Tag)->asBstr(); + if (!protectedParms || !unprotectedParms || !payload || !tag) { + return "Invalid COSE_Mac0 contents"; + } + + return payload->value(); +} + +ErrMsgOr verifyAndParseCoseMac0(const cppbor::Item* macItem, + const bytevec& macKey) { + auto mac = macItem ? macItem->asArray() : nullptr; + if (!mac || mac->size() != kCoseMac0EntryCount) { + return "Invalid COSE_Mac0"; + } + + auto protectedParms = mac->get(kCoseMac0ProtectedParams)->asBstr(); + auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asBstr(); + auto payload = mac->get(kCoseMac0Payload)->asBstr(); + auto tag = mac->get(kCoseMac0Tag)->asBstr(); + if (!protectedParms || !unprotectedParms || !payload || !tag) { + return "Invalid COSE_Mac0 contents"; + } + + auto [protectedMap, _, errMsg] = cppbor::parse(protectedParms); + if (!protectedMap || !protectedMap->asMap()) { + return "Invalid Mac0 protected: " + errMsg; + } + auto& algo = protectedMap->asMap()->get(ALGORITHM); + if (!algo || !algo->asInt() || algo->asInt()->value() != HMAC_256) { + return "Unsupported Mac0 algorithm"; + } + + auto macTag = generateCoseMac0Mac(macKey, {} /* external_aad */, payload->value()); + if (!macTag) return macTag.moveMessage(); + + if (macTag->size() != tag->value().size() || + CRYPTO_memcmp(macTag->data(), tag->value().data(), macTag->size()) != 0) { + return "MAC tag mismatch"; + } + + return payload->value(); +} + +ErrMsgOr createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams, + const bytevec& payload, const bytevec& aad) { + bytevec signatureInput = cppbor::Array() + .add("Signature1") // + .add(protectedParams) + .add(aad) + .add(payload) + .encode(); + + if (key.size() != ED25519_PRIVATE_KEY_LEN) return "Invalid signing key"; + bytevec signature(ED25519_SIGNATURE_LEN); + if (!ED25519_sign(signature.data(), signatureInput.data(), signatureInput.size(), key.data())) { + return "Signing failed"; + } + + return signature; +} + +ErrMsgOr constructCoseSign1(const bytevec& key, cppbor::Map protectedParams, + const bytevec& payload, const bytevec& aad) { + bytevec protParms = protectedParams.add(ALGORITHM, EDDSA).canonicalize().encode(); + auto signature = createCoseSign1Signature(key, protParms, payload, aad); + if (!signature) return signature.moveMessage(); + + return cppbor::Array() + .add(protParms) + .add(bytevec{} /* unprotected parameters */) + .add(payload) + .add(*signature); +} + +ErrMsgOr constructCoseSign1(const bytevec& key, const bytevec& payload, + const bytevec& aad) { + return constructCoseSign1(key, {} /* protectedParams */, payload, aad); +} + +ErrMsgOr verifyAndParseCoseSign1(bool ignoreSignature, const cppbor::Array* coseSign1, + const bytevec& signingCoseKey, const bytevec& aad) { + if (!coseSign1 || coseSign1->size() != kCoseSign1EntryCount) { + return "Invalid COSE_Sign1"; + } + + const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr(); + const cppbor::Bstr* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asBstr(); + const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr(); + const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr(); + + if (!protectedParams || !unprotectedParams || !payload || !signature) { + return "Invalid COSE_Sign1"; + } + + auto [parsedProtParams, _, errMsg] = cppbor::parse(protectedParams); + if (!parsedProtParams) { + return errMsg + " when parsing protected params."; + } + if (!parsedProtParams->asMap()) { + return "Protected params must be a map"; + } + + auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM); + if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) { + return "Unsupported signature algorithm"; + } + + if (!ignoreSignature) { + bool selfSigned = signingCoseKey.empty(); + auto key = CoseKey::parseEd25519(selfSigned ? payload->value() : signingCoseKey); + if (!key) return "Bad signing key: " + key.moveMessage(); + + bytevec signatureInput = cppbor::Array() + .add("Signature1") + .add(*protectedParams) + .add(aad) + .add(*payload) + .encode(); + + if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(), + key->getBstrValue(CoseKey::PUBKEY_X)->data())) { + return "Signature verification failed"; + } + } + + return payload->value(); +} + +ErrMsgOr createCoseEncryptCiphertext(const bytevec& key, const bytevec& nonce, + const bytevec& protectedParams, + const bytevec& plaintextPayload, const bytevec& aad) { + auto ciphertext = aesGcmEncrypt(key, nonce, + cppbor::Array() // Enc strucure as AAD + .add("Encrypt") // Context + .add(protectedParams) // Protected + .add(aad) // External AAD + .encode(), + plaintextPayload); + + if (!ciphertext) return ciphertext.moveMessage(); + return ciphertext.moveValue(); +} + +ErrMsgOr constructCoseEncrypt(const bytevec& key, const bytevec& nonce, + const bytevec& plaintextPayload, const bytevec& aad, + cppbor::Array recipients) { + auto encryptProtectedHeader = cppbor::Map() // + .add(ALGORITHM, AES_GCM_256) + .canonicalize() + .encode(); + + auto ciphertext = + createCoseEncryptCiphertext(key, nonce, encryptProtectedHeader, plaintextPayload, aad); + if (!ciphertext) return ciphertext.moveMessage(); + + return cppbor::Array() + .add(encryptProtectedHeader) // Protected + .add(cppbor::Map().add(IV, nonce).canonicalize()) // Unprotected + .add(*ciphertext) // Payload + .add(std::move(recipients)); +} + +ErrMsgOr> getSenderPubKeyFromCoseEncrypt( + const cppbor::Item* coseEncrypt) { + if (!coseEncrypt || !coseEncrypt->asArray() || + coseEncrypt->asArray()->size() != kCoseEncryptEntryCount) { + return "Invalid COSE_Encrypt"; + } + + auto& recipients = coseEncrypt->asArray()->get(kCoseEncryptRecipients); + if (!recipients || !recipients->asArray() || recipients->asArray()->size() != 1) { + return "Invalid recipients list"; + } + + auto& recipient = recipients->asArray()->get(0); + if (!recipient || !recipient->asArray() || recipient->asArray()->size() != 3) { + return "Invalid COSE_recipient"; + } + + auto& ciphertext = recipient->asArray()->get(2); + if (!ciphertext->asSimple() || !ciphertext->asSimple()->asNull()) { + return "Unexpected value in recipients ciphertext field " + + cppbor::prettyPrint(ciphertext.get()); + } + + auto& protParms = recipient->asArray()->get(0); + if (!protParms || !protParms->asBstr()) return "Invalid protected params"; + auto [parsedProtParms, _, errMsg] = cppbor::parse(protParms->asBstr()); + if (!parsedProtParms) return "Failed to parse protected params: " + errMsg; + if (!parsedProtParms->asMap()) return "Invalid protected params"; + + auto& algorithm = parsedProtParms->asMap()->get(ALGORITHM); + if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != ECDH_ES_HKDF_256) { + return "Invalid algorithm"; + } + + auto& unprotParms = recipient->asArray()->get(1); + if (!unprotParms || !unprotParms->asMap()) return "Invalid unprotected params"; + + auto& senderCoseKey = unprotParms->asMap()->get(COSE_KEY); + if (!senderCoseKey || !senderCoseKey->asMap()) return "Invalid sender COSE_Key"; + + auto& keyType = senderCoseKey->asMap()->get(CoseKey::KEY_TYPE); + if (!keyType || !keyType->asInt() || keyType->asInt()->value() != OCTET_KEY_PAIR) { + return "Invalid key type"; + } + + auto& curve = senderCoseKey->asMap()->get(CoseKey::CURVE); + if (!curve || !curve->asInt() || curve->asInt()->value() != X25519) { + return "Unsupported curve"; + } + + auto& pubkey = senderCoseKey->asMap()->get(CoseKey::PUBKEY_X); + if (!pubkey || !pubkey->asBstr() || + pubkey->asBstr()->value().size() != X25519_PUBLIC_VALUE_LEN) { + return "Invalid X25519 public key"; + } + + auto& key_id = unprotParms->asMap()->get(KEY_ID); + if (key_id && key_id->asBstr()) { + return std::make_pair(pubkey->asBstr()->value(), key_id->asBstr()->value()); + } + + // If no key ID, just return an empty vector. + return std::make_pair(pubkey->asBstr()->value(), bytevec{}); +} + +ErrMsgOr decryptCoseEncrypt(const bytevec& key, const cppbor::Item* coseEncrypt, + const bytevec& external_aad) { + if (!coseEncrypt || !coseEncrypt->asArray() || + coseEncrypt->asArray()->size() != kCoseEncryptEntryCount) { + return "Invalid COSE_Encrypt"; + } + + auto& protParms = coseEncrypt->asArray()->get(kCoseEncryptProtectedParams); + auto& unprotParms = coseEncrypt->asArray()->get(kCoseEncryptUnprotectedParams); + auto& ciphertext = coseEncrypt->asArray()->get(kCoseEncryptPayload); + auto& recipients = coseEncrypt->asArray()->get(kCoseEncryptRecipients); + + if (!protParms || !protParms->asBstr() || !unprotParms || !ciphertext || !recipients) { + return "Invalid COSE_Encrypt"; + } + + auto [parsedProtParams, _, errMsg] = cppbor::parse(protParms->asBstr()->value()); + if (!parsedProtParams) { + return errMsg + " when parsing protected params."; + } + if (!parsedProtParams->asMap()) { + return "Protected params must be a map"; + } + + auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM); + if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != AES_GCM_256) { + return "Unsupported encryption algorithm"; + } + + if (!unprotParms->asMap() || unprotParms->asMap()->size() != 1) { + return "Invalid unprotected params"; + } + + auto& nonce = unprotParms->asMap()->get(IV); + if (!nonce || !nonce->asBstr() || nonce->asBstr()->value().size() != kAesGcmNonceLength) { + return "Invalid nonce"; + } + + if (!ciphertext->asBstr()) return "Invalid ciphertext"; + + auto aad = cppbor::Array() // Enc strucure as AAD + .add("Encrypt") // Context + .add(protParms->asBstr()->value()) // Protected + .add(external_aad) // External AAD + .encode(); + + return aesGcmDecrypt(key, nonce->asBstr()->value(), aad, ciphertext->asBstr()->value()); +} + +ErrMsgOr x25519_HKDF_DeriveKey(const bytevec& pubKeyA, const bytevec& privKeyA, + const bytevec& pubKeyB, bool senderIsA) { + bytevec rawSharedKey(X25519_SHARED_KEY_LEN); + if (!::X25519(rawSharedKey.data(), privKeyA.data(), pubKeyB.data())) { + return "ECDH operation failed"; + } + + bytevec kdfContext = cppbor::Array() + .add(AES_GCM_256) + .add(cppbor::Array() // Sender Info + .add(cppbor::Bstr("client")) + .add(bytevec{} /* nonce */) + .add(senderIsA ? pubKeyA : pubKeyB)) + .add(cppbor::Array() // Recipient Info + .add(cppbor::Bstr("server")) + .add(bytevec{} /* nonce */) + .add(senderIsA ? pubKeyB : pubKeyA)) + .add(cppbor::Array() // SuppPubInfo + .add(128) // output key length + .add(bytevec{})) // protected + .encode(); + + bytevec retval(SHA256_DIGEST_LENGTH); + bytevec salt{}; + if (!HKDF(retval.data(), retval.size(), // + EVP_sha256(), // + rawSharedKey.data(), rawSharedKey.size(), // + salt.data(), salt.size(), // + kdfContext.data(), kdfContext.size())) { + return "ECDH HKDF failed"; + } + + return retval; +} + +ErrMsgOr aesGcmEncrypt(const bytevec& key, const bytevec& nonce, const bytevec& aad, + const bytevec& plaintext) { + auto ctx = aesGcmInitAndProcessAad(key, nonce, aad, true /* encrypt */); + if (!ctx) return ctx.moveMessage(); + + bytevec ciphertext(plaintext.size() + kAesGcmTagSize); + int outlen; + if (!EVP_CipherUpdate(ctx->get(), ciphertext.data(), &outlen, plaintext.data(), + plaintext.size())) { + return "Failed to encrypt plaintext"; + } + assert(plaintext.size() == outlen); + + if (!EVP_CipherFinal_ex(ctx->get(), ciphertext.data() + outlen, &outlen)) { + return "Failed to finalize encryption"; + } + assert(outlen == 0); + + if (!EVP_CIPHER_CTX_ctrl(ctx->get(), EVP_CTRL_GCM_GET_TAG, kAesGcmTagSize, + ciphertext.data() + plaintext.size())) { + return "Failed to retrieve tag"; + } + + return ciphertext; +} + +ErrMsgOr aesGcmDecrypt(const bytevec& key, const bytevec& nonce, const bytevec& aad, + const bytevec& ciphertextWithTag) { + auto ctx = aesGcmInitAndProcessAad(key, nonce, aad, false /* encrypt */); + if (!ctx) return ctx.moveMessage(); + + if (ciphertextWithTag.size() < kAesGcmTagSize) return "Missing tag"; + + bytevec plaintext(ciphertextWithTag.size() - kAesGcmTagSize); + int outlen; + if (!EVP_CipherUpdate(ctx->get(), plaintext.data(), &outlen, ciphertextWithTag.data(), + ciphertextWithTag.size() - kAesGcmTagSize)) { + return "Failed to decrypt plaintext"; + } + assert(plaintext.size() == outlen); + + bytevec tag(ciphertextWithTag.end() - kAesGcmTagSize, ciphertextWithTag.end()); + if (!EVP_CIPHER_CTX_ctrl(ctx->get(), EVP_CTRL_GCM_SET_TAG, kAesGcmTagSize, tag.data())) { + return "Failed to set tag: " + std::to_string(ERR_peek_last_error()); + } + + if (!EVP_CipherFinal_ex(ctx->get(), nullptr, &outlen)) { + return "Failed to finalize encryption"; + } + assert(outlen == 0); + + return plaintext; +} + +} // namespace cppcose diff --git a/security/keymint/support/include/cppcose/cppcose.h b/security/keymint/support/include/cppcose/cppcose.h new file mode 100644 index 0000000000..a936bfdb5a --- /dev/null +++ b/security/keymint/support/include/cppcose/cppcose.h @@ -0,0 +1,288 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace cppcose { + +using bytevec = std::vector; + +constexpr int kCoseSign1EntryCount = 4; +constexpr int kCoseSign1ProtectedParams = 0; +constexpr int kCoseSign1UnprotectedParams = 1; +constexpr int kCoseSign1Payload = 2; +constexpr int kCoseSign1Signature = 3; + +constexpr int kCoseMac0EntryCount = 4; +constexpr int kCoseMac0ProtectedParams = 0; +constexpr int kCoseMac0UnprotectedParams = 1; +constexpr int kCoseMac0Payload = 2; +constexpr int kCoseMac0Tag = 3; + +constexpr int kCoseEncryptEntryCount = 4; +constexpr int kCoseEncryptProtectedParams = 0; +constexpr int kCoseEncryptUnprotectedParams = 1; +constexpr int kCoseEncryptPayload = 2; +constexpr int kCoseEncryptRecipients = 3; + +enum Label : int { + ALGORITHM = 1, + KEY_ID = 4, + IV = 5, + COSE_KEY = -1, +}; + +enum CoseKeyAlgorithm : int { + AES_GCM_256 = 3, + HMAC_256 = 5, + ES256 = -7, // ECDSA with SHA-256 + EDDSA = -8, + ECDH_ES_HKDF_256 = -25, +}; + +enum CoseKeyCurve : int { P256 = 1, X25519 = 4, ED25519 = 6 }; +enum CoseKeyType : int { OCTET_KEY_PAIR = 1, EC2 = 2, SYMMETRIC_KEY = 4 }; +enum CoseKeyOps : int { SIGN = 1, VERIFY = 2, ENCRYPT = 3, DECRYPT = 4 }; + +constexpr int kAesGcmNonceLength = 12; +constexpr int kAesGcmTagSize = 16; +constexpr int kAesGcmKeySize = 32; + +template +class ErrMsgOr { + public: + ErrMsgOr(std::string errMsg) : errMsg_(std::move(errMsg)) {} + ErrMsgOr(const char* errMsg) : errMsg_(errMsg) {} + ErrMsgOr(T val) : value_(std::move(val)) {} + + operator bool() const { return value_.has_value(); } + + T* operator->() & { + assert(value_); + return &value_.value(); + } + T& operator*() & { + assert(value_); + return value_.value(); + }; + T&& operator*() && { + assert(value_); + return std::move(value_).value(); + }; + + const std::string& message() { return errMsg_; } + std::string moveMessage() { return std::move(errMsg_); } + + T moveValue() { + assert(value_); + return std::move(value_).value(); + } + + private: + std::string errMsg_; + std::optional value_; +}; + +class CoseKey { + public: + CoseKey() {} + CoseKey(const CoseKey&) = delete; + CoseKey(CoseKey&&) = default; + + enum Label : int { + KEY_TYPE = 1, + KEY_ID = 2, + ALGORITHM = 3, + KEY_OPS = 4, + CURVE = -1, + PUBKEY_X = -2, + PUBKEY_Y = -3, + PRIVATE_KEY = -4, + TEST_KEY = -70000 // Application-defined + }; + + static ErrMsgOr parse(const bytevec& coseKey) { + auto [parsedKey, _, errMsg] = cppbor::parse(coseKey); + if (!parsedKey) return errMsg + " when parsing key"; + if (!parsedKey->asMap()) return "CoseKey must be a map"; + return CoseKey(static_cast(parsedKey.release())); + } + + static ErrMsgOr parse(const bytevec& coseKey, CoseKeyType expectedKeyType, + CoseKeyAlgorithm expectedAlgorithm, CoseKeyCurve expectedCurve) { + auto key = parse(coseKey); + if (!key) return key; + + if (!key->checkIntValue(CoseKey::KEY_TYPE, expectedKeyType) || + !key->checkIntValue(CoseKey::ALGORITHM, expectedAlgorithm) || + !key->checkIntValue(CoseKey::CURVE, expectedCurve)) { + return "Unexpected key type:"; + } + + return key; + } + + static ErrMsgOr parseEd25519(const bytevec& coseKey) { + auto key = parse(coseKey, OCTET_KEY_PAIR, EDDSA, ED25519); + if (!key) return key; + + auto& pubkey = key->getMap().get(PUBKEY_X); + if (!pubkey || !pubkey->asBstr() || + pubkey->asBstr()->value().size() != ED25519_PUBLIC_KEY_LEN) { + return "Invalid Ed25519 public key"; + } + + return key; + } + + static ErrMsgOr parseX25519(const bytevec& coseKey, bool requireKid) { + auto key = parse(coseKey, OCTET_KEY_PAIR, ECDH_ES_HKDF_256, X25519); + if (!key) return key; + + auto& pubkey = key->getMap().get(PUBKEY_X); + if (!pubkey || !pubkey->asBstr() || + pubkey->asBstr()->value().size() != X25519_PUBLIC_VALUE_LEN) { + return "Invalid X25519 public key"; + } + + auto& kid = key->getMap().get(KEY_ID); + if (requireKid && (!kid || !kid->asBstr())) { + return "Missing KID"; + } + + return key; + } + + static ErrMsgOr parseP256(const bytevec& coseKey) { + auto key = parse(coseKey, EC2, ES256, P256); + if (!key) return key; + + auto& pubkey_x = key->getMap().get(PUBKEY_X); + auto& pubkey_y = key->getMap().get(PUBKEY_Y); + if (!pubkey_x || !pubkey_y || !pubkey_x->asBstr() || !pubkey_y->asBstr() || + pubkey_x->asBstr()->value().size() != 32 || pubkey_y->asBstr()->value().size() != 32) { + return "Invalid P256 public key"; + } + + return key; + } + + std::optional getIntValue(Label label) { + const auto& value = key_->get(label); + if (!value || !value->asInt()) return {}; + return value->asInt()->value(); + } + + std::optional getBstrValue(Label label) { + const auto& value = key_->get(label); + if (!value || !value->asBstr()) return {}; + return value->asBstr()->value(); + } + + const cppbor::Map& getMap() const { return *key_; } + cppbor::Map&& moveMap() { return std::move(*key_); } + + bool checkIntValue(Label label, int expectedValue) { + const auto& value = key_->get(label); + return value && value->asInt() && value->asInt()->value() == expectedValue; + } + + void add(Label label, int value) { key_->add(label, value); } + void add(Label label, bytevec value) { key_->add(label, std::move(value)); } + + bytevec encode() { return key_->canonicalize().encode(); } + + private: + CoseKey(cppbor::Map* parsedKey) : key_(parsedKey) {} + + // This is the full parsed key structure. + std::unique_ptr key_; +}; + +ErrMsgOr generateCoseMac0Mac(const bytevec& macKey, const bytevec& externalAad, + const bytevec& payload); +ErrMsgOr constructCoseMac0(const bytevec& macKey, const bytevec& externalAad, + const bytevec& payload); +ErrMsgOr parseCoseMac0(const cppbor::Item* macItem); +ErrMsgOr verifyAndParseCoseMac0(const cppbor::Item* macItem, + const bytevec& macKey); + +ErrMsgOr createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams, + const bytevec& payload, const bytevec& aad); +ErrMsgOr constructCoseSign1(const bytevec& key, const bytevec& payload, + const bytevec& aad); +ErrMsgOr constructCoseSign1(const bytevec& key, cppbor::Map extraProtectedFields, + const bytevec& payload, const bytevec& aad); +/** + * Verify and parse a COSE_Sign1 message, returning the payload. + * + * @param ignoreSignature indicates whether signature verification should be skipped. If true, no + * verification of the signature will be done. + * + * @param coseSign1 is the COSE_Sign1 to verify and parse. + * + * @param signingCoseKey is a CBOR-encoded COSE_Key to use to verify the signature. The bytevec may + * be empty, in which case the function assumes that coseSign1's payload is the COSE_Key to + * use, i.e. that coseSign1 is a self-signed "certificate". + */ +ErrMsgOr verifyAndParseCoseSign1(bool ignoreSignature, + const cppbor::Array* coseSign1, + const bytevec& signingCoseKey, + const bytevec& aad); + +ErrMsgOr createCoseEncryptCiphertext(const bytevec& key, const bytevec& nonce, + const bytevec& protectedParams, const bytevec& aad); +ErrMsgOr constructCoseEncrypt(const bytevec& key, const bytevec& nonce, + const bytevec& plaintextPayload, const bytevec& aad, + cppbor::Array recipients); +ErrMsgOr> getSenderPubKeyFromCoseEncrypt( + const cppbor::Item* encryptItem); +inline ErrMsgOr> +getSenderPubKeyFromCoseEncrypt(const std::unique_ptr& encryptItem) { + return getSenderPubKeyFromCoseEncrypt(encryptItem.get()); +} + +ErrMsgOr decryptCoseEncrypt(const bytevec& key, + const cppbor::Item* encryptItem, + const bytevec& aad); + +ErrMsgOr x25519_HKDF_DeriveKey(const bytevec& senderPubKey, const bytevec& senderPrivKey, + const bytevec& recipientPubKey, bool senderIsA); + +ErrMsgOr aesGcmEncrypt(const bytevec& key, const bytevec& nonce, + const bytevec& aad, + const bytevec& plaintext); +ErrMsgOr aesGcmDecrypt(const bytevec& key, const bytevec& nonce, + const bytevec& aad, + const bytevec& ciphertextWithTag); + +} // namespace cppcose diff --git a/security/keymint/support/include/remote_prov/remote_prov_utils.h b/security/keymint/support/include/remote_prov/remote_prov_utils.h new file mode 100644 index 0000000000..5e205a27a9 --- /dev/null +++ b/security/keymint/support/include/remote_prov/remote_prov_utils.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019, 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. + */ + +#pragma once + +#include + +#include + +namespace aidl::android::hardware::security::keymint::remote_prov { + +using bytevec = std::vector; +using namespace cppcose; + +extern bytevec kTestMacKey; + +/** + * Generates random bytes. + */ +bytevec randomBytes(size_t numBytes); + +struct EekChain { + bytevec chain; + bytevec last_pubkey; + bytevec last_privkey; +}; + +/** + * Generates an X25518 EEK with the specified eekId and an Ed25519 chain of the + * specified length. All keys are generated randomly. + */ +ErrMsgOr generateEekChain(size_t length, const bytevec& eekId); + +struct BccEntryData { + bytevec pubKey; +}; + +/** + * Validates the provided CBOR-encoded BCC, returning a vector of BccEntryData + * structs containing the BCC entry contents. If an entry contains no firmware + * digest, the corresponding BccEntryData.firmwareDigest will have length zero + * (there's no way to distinguish between an empty and missing firmware digest, + * which seems fine). + */ +ErrMsgOr> validateBcc(const cppbor::Array* bcc); + +} // namespace aidl::android::hardware::security::keymint::remote_prov diff --git a/security/keymint/support/remote_prov_utils.cpp b/security/keymint/support/remote_prov_utils.cpp new file mode 100644 index 0000000000..111cb309b0 --- /dev/null +++ b/security/keymint/support/remote_prov_utils.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2019, 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. + */ + +#include + +#include + +#include + +namespace aidl::android::hardware::security::keymint::remote_prov { + +bytevec kTestMacKey(32 /* count */, 0 /* byte value */); + +bytevec randomBytes(size_t numBytes) { + bytevec retval(numBytes); + RAND_bytes(retval.data(), numBytes); + return retval; +} + +ErrMsgOr generateEekChain(size_t length, const bytevec& eekId) { + auto eekChain = cppbor::Array(); + + bytevec prev_priv_key; + for (size_t i = 0; i < length - 1; ++i) { + bytevec pub_key(ED25519_PUBLIC_KEY_LEN); + bytevec priv_key(ED25519_PRIVATE_KEY_LEN); + + ED25519_keypair(pub_key.data(), priv_key.data()); + + // The first signing key is self-signed. + if (prev_priv_key.empty()) prev_priv_key = priv_key; + + auto coseSign1 = constructCoseSign1(prev_priv_key, + cppbor::Map() /* payload CoseKey */ + .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR) + .add(CoseKey::ALGORITHM, EDDSA) + .add(CoseKey::CURVE, ED25519) + .add(CoseKey::PUBKEY_X, pub_key) + .canonicalize() + .encode(), + {} /* AAD */); + if (!coseSign1) return coseSign1.moveMessage(); + eekChain.add(coseSign1.moveValue()); + } + + bytevec pub_key(X25519_PUBLIC_VALUE_LEN); + bytevec priv_key(X25519_PRIVATE_KEY_LEN); + X25519_keypair(pub_key.data(), priv_key.data()); + + auto coseSign1 = constructCoseSign1(prev_priv_key, + cppbor::Map() /* payload CoseKey */ + .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR) + .add(CoseKey::KEY_ID, eekId) + .add(CoseKey::ALGORITHM, ECDH_ES_HKDF_256) + .add(CoseKey::CURVE, cppcose::X25519) + .add(CoseKey::PUBKEY_X, pub_key) + .canonicalize() + .encode(), + {} /* AAD */); + if (!coseSign1) return coseSign1.moveMessage(); + eekChain.add(coseSign1.moveValue()); + + return EekChain{eekChain.encode(), pub_key, priv_key}; +} + +ErrMsgOr verifyAndParseCoseSign1Cwt(bool ignoreSignature, const cppbor::Array* coseSign1, + const bytevec& signingCoseKey, const bytevec& aad) { + if (!coseSign1 || coseSign1->size() != kCoseSign1EntryCount) { + return "Invalid COSE_Sign1"; + } + + const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr(); + const cppbor::Bstr* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asBstr(); + const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr(); + const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr(); + + if (!protectedParams || !unprotectedParams || !payload || !signature) { + return "Invalid COSE_Sign1"; + } + + auto [parsedProtParams, _, errMsg] = cppbor::parse(protectedParams); + if (!parsedProtParams) { + return errMsg + " when parsing protected params."; + } + if (!parsedProtParams->asMap()) { + return "Protected params must be a map"; + } + + auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM); + if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) { + return "Unsupported signature algorithm"; + } + + // TODO(jbires): Handle CWTs as the CoseSign1 payload in a less hacky way. Since the CWT payload + // is extremely remote provisioning specific, probably just make a separate + // function there. + auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(payload); + if (!parsedPayload) return payloadErrMsg + " when parsing key"; + if (!parsedPayload->asMap()) return "CWT must be a map"; + auto serializedKey = parsedPayload->asMap()->get(-4670552)->clone(); + if (!serializedKey || !serializedKey->asBstr()) return "Could not find key entry"; + + if (!ignoreSignature) { + bool selfSigned = signingCoseKey.empty(); + auto key = CoseKey::parseEd25519(selfSigned ? serializedKey->asBstr()->value() + : signingCoseKey); + if (!key) return "Bad signing key: " + key.moveMessage(); + + bytevec signatureInput = cppbor::Array() + .add("Signature1") + .add(*protectedParams) + .add(aad) + .add(*payload) + .encode(); + + if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(), + key->getBstrValue(CoseKey::PUBKEY_X)->data())) { + return "Signature verification failed"; + } + } + + return serializedKey->asBstr()->value(); +} +ErrMsgOr> validateBcc(const cppbor::Array* bcc) { + if (!bcc || bcc->size() == 0) return "Invalid BCC"; + + std::vector result; + + bytevec prevKey; + // TODO(jbires): Actually process the pubKey at the start of the new bcc entry + for (size_t i = 1; i < bcc->size(); ++i) { + const cppbor::Array* entry = bcc->get(i)->asArray(); + if (!entry || entry->size() != kCoseSign1EntryCount) { + return "Invalid BCC entry " + std::to_string(i) + ": " + prettyPrint(entry); + } + auto payload = verifyAndParseCoseSign1Cwt(false /* ignoreSignature */, entry, + std::move(prevKey), bytevec{} /* AAD */); + if (!payload) { + return "Failed to verify entry " + std::to_string(i) + ": " + payload.moveMessage(); + } + + auto& certProtParms = entry->get(kCoseSign1ProtectedParams); + if (!certProtParms || !certProtParms->asBstr()) return "Invalid prot params"; + auto [parsedProtParms, _, errMsg] = cppbor::parse(certProtParms->asBstr()->value()); + if (!parsedProtParms || !parsedProtParms->asMap()) return "Invalid prot params"; + + result.push_back(BccEntryData{*payload}); + + // This entry's public key is the signing key for the next entry. + prevKey = payload.moveValue(); + } + + return result; +} + +} // namespace aidl::android::hardware::security::keymint::remote_prov