Merge "Add RemotelyProvisionedComponent HAL." am: 0ace84a193
am: 2a325dff91
Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/1367566 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: I9889d30d121dac83712e3be1af02095459f6fb9d
This commit is contained in:
commit
cb8d07c4ae
19 changed files with 2619 additions and 3 deletions
|
@ -323,6 +323,13 @@
|
|||
<instance>strongbox</instance>
|
||||
</interface>
|
||||
</hal>
|
||||
<hal format="aidl" optional="true">
|
||||
<name>android.hardware.security.keymint</name>
|
||||
<interface>
|
||||
<name>IRemotelyProvisionedComponent</name>
|
||||
<instance>default</instance>
|
||||
</interface>
|
||||
</hal>
|
||||
<hal format="aidl" optional="true">
|
||||
<name>android.hardware.light</name>
|
||||
<version>1</version>
|
||||
|
|
|
@ -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 <name>-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;
|
||||
}
|
|
@ -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 <name>-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;
|
||||
}
|
|
@ -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 <name>-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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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",
|
||||
],
|
||||
}
|
||||
|
|
430
security/keymint/aidl/default/RemotelyProvisionedComponent.cpp
Normal file
430
security/keymint/aidl/default/RemotelyProvisionedComponent.cpp
Normal file
|
@ -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 <assert.h>
|
||||
#include <variant>
|
||||
|
||||
#include <cppbor.h>
|
||||
#include <cppbor_parse.h>
|
||||
|
||||
#include <KeyMintUtils.h>
|
||||
#include <cppcose/cppcose.h>
|
||||
#include <keymaster/keymaster_configuration.h>
|
||||
#include <remote_prov/remote_prov_utils.h>
|
||||
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
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<uint8_t>;
|
||||
|
||||
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<AStatus, AStatusDeleter> status_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
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<T> value_;
|
||||
};
|
||||
|
||||
StatusOr<std::pair<bytevec /* EEK pub */, bytevec /* EEK ID */>> 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<bytevec /* pubkeys */> validateAndExtractPubkeys(bool testMode,
|
||||
const vector<MacedPublicKey>& 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<bool>(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<std::pair<bytevec, bytevec>> 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::AndroidKeyMintDevice> 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<MacedPublicKey>& 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<const uint8_t*>(context.data()), context.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bytevec RemotelyProvisionedComponent::createDeviceInfo() const {
|
||||
return cppbor::Map().encode();
|
||||
}
|
||||
|
||||
std::pair<bytevec /* privKey */, cppbor::Array /* BCC */>
|
||||
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<uint8_t>(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
|
57
security/keymint/aidl/default/RemotelyProvisionedComponent.h
Normal file
57
security/keymint/aidl/default/RemotelyProvisionedComponent.h
Normal file
|
@ -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 <AndroidKeyMintDevice.h>
|
||||
#include <aidl/android/hardware/security/keymint/BnRemotelyProvisionedComponent.h>
|
||||
#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
|
||||
#include <cppbor.h>
|
||||
#include <keymaster/UniquePtr.h>
|
||||
#include <keymaster/android_keymaster.h>
|
||||
|
||||
namespace aidl::android::hardware::security::keymint {
|
||||
|
||||
using ::ndk::ScopedAStatus;
|
||||
|
||||
class RemotelyProvisionedComponent : public BnRemotelyProvisionedComponent {
|
||||
public:
|
||||
explicit RemotelyProvisionedComponent(std::shared_ptr<keymint::AndroidKeyMintDevice> keymint);
|
||||
virtual ~RemotelyProvisionedComponent();
|
||||
|
||||
ScopedAStatus generateEcdsaP256KeyPair(bool testMode, MacedPublicKey* macedPublicKey,
|
||||
std::vector<uint8_t>* privateKeyHandle) override;
|
||||
|
||||
ScopedAStatus generateCertificateRequest(bool testMode,
|
||||
const std::vector<MacedPublicKey>& keysToSign,
|
||||
const std::vector<uint8_t>& endpointEncCertChain,
|
||||
const std::vector<uint8_t>& challenge,
|
||||
std::vector<uint8_t>* keysToSignMac,
|
||||
ProtectedData* protectedData) override;
|
||||
|
||||
private:
|
||||
// TODO(swillden): Move these into an appropriate Context class.
|
||||
std::vector<uint8_t> deriveBytesFromHbk(const std::string& context, size_t numBytes) const;
|
||||
std::vector<uint8_t> createDeviceInfo() const;
|
||||
std::pair<std::vector<uint8_t>, cppbor::Array> generateBcc();
|
||||
|
||||
std::vector<uint8_t> macKey_ = deriveBytesFromHbk("Key to MAC public keys", 32);
|
||||
std::vector<uint8_t> devicePrivKey_;
|
||||
cppbor::Array bcc_;
|
||||
std::shared_ptr<::keymaster::AndroidKeymaster> impl_;
|
||||
};
|
||||
|
||||
} // namespace aidl::android::hardware::security::keymint
|
|
@ -3,4 +3,8 @@
|
|||
<name>android.hardware.security.keymint</name>
|
||||
<fqname>IKeyMintDevice/default</fqname>
|
||||
</hal>
|
||||
<hal format="aidl">
|
||||
<name>android.hardware.security.keymint</name>
|
||||
<fqname>IRemotelyProvisionedComponent/default</fqname>
|
||||
</hal>
|
||||
</manifest>
|
||||
|
|
|
@ -25,7 +25,10 @@
|
|||
#include <AndroidSharedSecret.h>
|
||||
#include <keymaster/soft_keymaster_logger.h>
|
||||
|
||||
#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<AndroidKeyMintDevice> keyMint =
|
||||
addService<AndroidKeyMintDevice>(SecurityLevel::SOFTWARE);
|
||||
|
@ -53,6 +55,8 @@ int main() {
|
|||
addService<AndroidSecureClock>(keyMint);
|
||||
// Add Shared Secret Service
|
||||
addService<AndroidSharedSecret>(keyMint);
|
||||
// Add Remotely Provisioned Component Service
|
||||
addService<RemotelyProvisionedComponent>(keyMint);
|
||||
ABinderProcess_joinThreadPool();
|
||||
return EXIT_FAILURE; // should not reach
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
}
|
||||
|
|
|
@ -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 <RemotelyProvisionedComponent.h>
|
||||
#include <aidl/Gtest.h>
|
||||
#include <aidl/Vintf.h>
|
||||
#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
|
||||
#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
|
||||
#include <android/binder_manager.h>
|
||||
#include <cppbor_parse.h>
|
||||
#include <cppcose/cppcose.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <keymaster/keymaster_configuration.h>
|
||||
#include <remote_prov/remote_prov_utils.h>
|
||||
|
||||
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<uint8_t>;
|
||||
using testing::MatchesRegex;
|
||||
using namespace remote_prov;
|
||||
using namespace keymaster;
|
||||
|
||||
bytevec string_to_bytevec(const char* s) {
|
||||
const uint8_t* p = reinterpret_cast<const uint8_t*>(s);
|
||||
return bytevec(p, p + strlen(s));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam<std::string> {
|
||||
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<string> build_params() {
|
||||
auto params = ::android::getAidlHalInstanceNames(IRemotelyProvisionedComponent::descriptor);
|
||||
return params;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<IRemotelyProvisionedComponent> 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<MacedPublicKey>(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<MacedPublicKey> 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
|
|
@ -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",
|
||||
],
|
||||
}
|
||||
|
|
467
security/keymint/support/cppcose.cpp
Normal file
467
security/keymint/support/cppcose.cpp
Normal file
|
@ -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 <cppcose/cppcose.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
|
||||
#include <cppbor.h>
|
||||
#include <cppbor_parse.h>
|
||||
|
||||
#include <openssl/err.h>
|
||||
|
||||
namespace cppcose {
|
||||
|
||||
namespace {
|
||||
|
||||
ErrMsgOr<bssl::UniquePtr<EVP_CIPHER_CTX>> aesGcmInitAndProcessAad(const bytevec& key,
|
||||
const bytevec& nonce,
|
||||
const bytevec& aad,
|
||||
bool encrypt) {
|
||||
if (key.size() != kAesGcmKeySize) return "Invalid key size";
|
||||
|
||||
bssl::UniquePtr<EVP_CIPHER_CTX> 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<bytevec> 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<cppbor::Array> 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<bytevec /* payload */> 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<bytevec /* payload */> 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<bytevec> 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<cppbor::Array> 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<cppbor::Array> constructCoseSign1(const bytevec& key, const bytevec& payload,
|
||||
const bytevec& aad) {
|
||||
return constructCoseSign1(key, {} /* protectedParams */, payload, aad);
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec> 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<bytevec> 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<cppbor::Array> 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<std::pair<bytevec /* pubkey */, bytevec /* key ID */>> 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<bytevec> 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<bytevec> 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<bytevec> 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<bytevec> 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
|
288
security/keymint/support/include/cppcose/cppcose.h
Normal file
288
security/keymint/support/include/cppcose/cppcose.h
Normal file
|
@ -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 <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cppbor.h>
|
||||
#include <cppbor_parse.h>
|
||||
|
||||
#include <openssl/cipher.h>
|
||||
#include <openssl/curve25519.h>
|
||||
#include <openssl/digest.h>
|
||||
#include <openssl/hkdf.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/mem.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
namespace cppcose {
|
||||
|
||||
using bytevec = std::vector<uint8_t>;
|
||||
|
||||
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 <typename T>
|
||||
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<T> 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<CoseKey> 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<cppbor::Map*>(parsedKey.release()));
|
||||
}
|
||||
|
||||
static ErrMsgOr<CoseKey> 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<CoseKey> 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<CoseKey> 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<CoseKey> 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<int> getIntValue(Label label) {
|
||||
const auto& value = key_->get(label);
|
||||
if (!value || !value->asInt()) return {};
|
||||
return value->asInt()->value();
|
||||
}
|
||||
|
||||
std::optional<bytevec> 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<cppbor::Map> key_;
|
||||
};
|
||||
|
||||
ErrMsgOr<bytevec> generateCoseMac0Mac(const bytevec& macKey, const bytevec& externalAad,
|
||||
const bytevec& payload);
|
||||
ErrMsgOr<cppbor::Array> constructCoseMac0(const bytevec& macKey, const bytevec& externalAad,
|
||||
const bytevec& payload);
|
||||
ErrMsgOr<bytevec /* payload */> parseCoseMac0(const cppbor::Item* macItem);
|
||||
ErrMsgOr<bytevec /* payload */> verifyAndParseCoseMac0(const cppbor::Item* macItem,
|
||||
const bytevec& macKey);
|
||||
|
||||
ErrMsgOr<bytevec> createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams,
|
||||
const bytevec& payload, const bytevec& aad);
|
||||
ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, const bytevec& payload,
|
||||
const bytevec& aad);
|
||||
ErrMsgOr<cppbor::Array> 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<bytevec /* payload */> verifyAndParseCoseSign1(bool ignoreSignature,
|
||||
const cppbor::Array* coseSign1,
|
||||
const bytevec& signingCoseKey,
|
||||
const bytevec& aad);
|
||||
|
||||
ErrMsgOr<bytevec> createCoseEncryptCiphertext(const bytevec& key, const bytevec& nonce,
|
||||
const bytevec& protectedParams, const bytevec& aad);
|
||||
ErrMsgOr<cppbor::Array> constructCoseEncrypt(const bytevec& key, const bytevec& nonce,
|
||||
const bytevec& plaintextPayload, const bytevec& aad,
|
||||
cppbor::Array recipients);
|
||||
ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>> getSenderPubKeyFromCoseEncrypt(
|
||||
const cppbor::Item* encryptItem);
|
||||
inline ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>>
|
||||
getSenderPubKeyFromCoseEncrypt(const std::unique_ptr<cppbor::Item>& encryptItem) {
|
||||
return getSenderPubKeyFromCoseEncrypt(encryptItem.get());
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec /* plaintextPayload */> decryptCoseEncrypt(const bytevec& key,
|
||||
const cppbor::Item* encryptItem,
|
||||
const bytevec& aad);
|
||||
|
||||
ErrMsgOr<bytevec> x25519_HKDF_DeriveKey(const bytevec& senderPubKey, const bytevec& senderPrivKey,
|
||||
const bytevec& recipientPubKey, bool senderIsA);
|
||||
|
||||
ErrMsgOr<bytevec /* ciphertextWithTag */> aesGcmEncrypt(const bytevec& key, const bytevec& nonce,
|
||||
const bytevec& aad,
|
||||
const bytevec& plaintext);
|
||||
ErrMsgOr<bytevec /* plaintext */> aesGcmDecrypt(const bytevec& key, const bytevec& nonce,
|
||||
const bytevec& aad,
|
||||
const bytevec& ciphertextWithTag);
|
||||
|
||||
} // namespace cppcose
|
|
@ -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 <vector>
|
||||
|
||||
#include <cppcose/cppcose.h>
|
||||
|
||||
namespace aidl::android::hardware::security::keymint::remote_prov {
|
||||
|
||||
using bytevec = std::vector<uint8_t>;
|
||||
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<EekChain> 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<std::vector<BccEntryData>> validateBcc(const cppbor::Array* bcc);
|
||||
|
||||
} // namespace aidl::android::hardware::security::keymint::remote_prov
|
169
security/keymint/support/remote_prov_utils.cpp
Normal file
169
security/keymint/support/remote_prov_utils.cpp
Normal file
|
@ -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 <remote_prov/remote_prov_utils.h>
|
||||
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include <cppbor.h>
|
||||
|
||||
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<EekChain> 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<bytevec> 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<std::vector<BccEntryData>> validateBcc(const cppbor::Array* bcc) {
|
||||
if (!bcc || bcc->size() == 0) return "Invalid BCC";
|
||||
|
||||
std::vector<BccEntryData> 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
|
Loading…
Reference in a new issue