core/attestation/aux_attestation.c (401 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "aux_attestation.h"
#include "platform_api.h"
#include "common/buffer_util.h"
#include "common/unused.h"
#include "crypto/kdf.h"
#include "riot/riot_core.h"
/**
* The label to use when deriving the encryption key.
*/
static const char AUX_ATTESTATION_ENCRYPTION_LABEL[] = "encryption key";
/**
* The label to use when deriving the signing key.
*/
static const char AUX_ATTESTATION_SIGNING_LABEL[] = "signing key";
/**
* Initialize the handler for auxiliary attestation requests.
*
* @param aux The attestation handler to initialize.
* @param keystore The keystore used to store the RSA private key. This can be null if RSA is not
* supported.
* @param rsa The RSA engine to use with the private key. Set to null if RSA is not supported.
* @param riot The RIoT keys to use for ECC operations. This can be null if ECC is not supported.
* @param ecc The ECC engine to use with RIoT keys. Set to null if ECC is not supported.
*
* @return 0 if the attestation handler was successfully initialized or an error code.
*/
int aux_attestation_init (struct aux_attestation *aux, const struct keystore *keystore,
const struct rsa_engine *rsa, const struct riot_key_manager *riot, const struct ecc_engine *ecc)
{
if ((aux == NULL) || ((rsa != NULL) && (keystore == NULL)) ||
((ecc != NULL) && (riot == NULL))) {
return AUX_ATTESTATION_INVALID_ARGUMENT;
}
memset (aux, 0, sizeof (struct aux_attestation));
aux->keystore = keystore;
aux->rsa = rsa;
aux->riot = riot;
aux->ecc = ecc;
return 0;
}
/**
* Release the stored certificate for the attestation key.
*
* @param aux The attestation handler for the certificate to release.
*/
static void aux_attestation_free_cert (struct aux_attestation *aux)
{
if (aux) {
if (!aux->is_static) {
platform_free ((void*) aux->cert.cert);
}
aux->cert.cert = NULL;
aux->cert.length = 0;
aux->is_static = false;
}
}
/**
* Release the resources used by the auxiliary attestation handler.
*
* @param aux The attestation handler to release.
*/
void aux_attestation_release (struct aux_attestation *aux)
{
aux_attestation_free_cert (aux);
}
/**
* Generate a new RSA key to use for attestation and save it in the keystore. Since the
* attestation key is an RSA-3072 key, this function could take a very long time to complete.
*
* No certificate will be generated for the key.
*
* @param aux The attestation handler.
*
* @return 0 if the key was generated and stored successfully or an error code.
*/
int aux_attestation_generate_key (struct aux_attestation *aux)
{
#ifdef ATTESTATION_SUPPORT_RSA_UNSEAL
struct rsa_private_key rsa_key;
uint8_t *priv;
size_t length;
int status;
if (aux == NULL) {
return AUX_ATTESTATION_INVALID_ARGUMENT;
}
if (aux->rsa == NULL) {
return AUX_ATTESTATION_UNSUPPORTED_CRYPTO;
}
status = aux->rsa->generate_key (aux->rsa, &rsa_key, AUX_ATTESTATION_KEY_BITS);
if (status != 0) {
return status;
}
status = aux->rsa->get_private_key_der (aux->rsa, &rsa_key, &priv, &length);
aux->rsa->release_key (aux->rsa, &rsa_key);
if (status != 0) {
return status;
}
status = aux->keystore->save_key (aux->keystore, 0, priv, length);
riot_core_clear (priv, length);
platform_free (priv);
return status;
#else
UNUSED (aux);
return AUX_ATTESTATION_UNSUPPORTED_CRYPTO;
#endif
}
/**
* Erase the RSA private key in the keystore. The certificate for the key will also be invalidated.
*
* @param aux The attestation handler to update.
*
* @return 0 if the key was erased successfully or an error code.
*/
int aux_attestation_erase_key (struct aux_attestation *aux)
{
if (aux == NULL) {
return AUX_ATTESTATION_INVALID_ARGUMENT;
}
if (aux->rsa == NULL) {
return AUX_ATTESTATION_UNSUPPORTED_CRYPTO;
}
/* There is no synchronization on these calls, but that shouldn't be an issue. This will only
* get called in very rare scenarios and/or development situations. The certificate and key
* are also only rarely used, reducing the chance of conflict. */
aux_attestation_free_cert (aux);
return aux->keystore->erase_key (aux->keystore, 0);
}
#ifdef X509_ENABLE_CREATE_CERTIFICATES
/**
* Generate the signed attestation certificate for the attestation RSA key in the keystore.
*
* @param aux The attestation handler.
* @param x509 The X.509 engine to use for certificate generation.
* @param rng Generator for a random serial number.
* @param ca DER encoded data of the CA certificate that will sign the attestation certificate.
* @param ca_length The length of the CA certificate.
* @param ca_key DER encoded data of private key for the CA certificate.
* @param key_length The length of the CA private key.
*
* @return 0 if the certificate was create successfully or an error code. The certificate itself
* can be retrieved from aux_attestation_get_certificate.
*/
int aux_attestation_create_certificate (struct aux_attestation *aux, const struct x509_engine *x509,
const struct rng_engine *rng, const uint8_t *ca, size_t ca_length, const uint8_t *ca_key,
size_t key_length)
{
uint8_t *priv;
size_t length;
struct x509_certificate ca_cert;
struct x509_certificate attestation_cert;
int status;
uint8_t serial_num[8];
int i;
if ((aux == NULL) || (x509 == NULL) || (rng == NULL) || (ca == NULL) || (ca_length == 0) ||
(ca_key == NULL) || (key_length == 0)) {
return AUX_ATTESTATION_INVALID_ARGUMENT;
}
if (aux->rsa == NULL) {
return AUX_ATTESTATION_UNSUPPORTED_CRYPTO;
}
status = aux->keystore->load_key (aux->keystore, 0, &priv, &length);
if (status != 0) {
return status;
}
status = x509->load_certificate (x509, &ca_cert, ca, ca_length);
if (status != 0) {
goto exit_free_key;
}
do {
status = rng->generate_random_buffer (rng, sizeof (serial_num), serial_num);
if (status != 0) {
goto exit_free_ca;
}
/* If the RNG gives us all 0's, we want to try again. */
for (i = 0; i < (int) sizeof (serial_num); i++) {
if (serial_num[i] != 0) {
break;
}
}
} while (i == sizeof (serial_num));
status = x509->create_ca_signed_certificate (x509, &attestation_cert, priv, length, serial_num,
sizeof (serial_num), "AUX", X509_CERT_END_ENTITY, ca_key, key_length, HASH_TYPE_SHA256,
&ca_cert, NULL, 0);
if (status != 0) {
goto exit_free_ca;
}
if (aux->cert.cert) {
aux_attestation_free_cert (aux);
}
status = x509->get_certificate_der (x509, &attestation_cert, (uint8_t**) &aux->cert.cert,
&aux->cert.length);
x509->release_certificate (x509, &attestation_cert);
exit_free_ca:
x509->release_certificate (x509, &ca_cert);
exit_free_key:
riot_core_clear (priv, length);
platform_free (priv);
return status;
}
#endif
/**
* Provide the signed certificate for the attestation RSA key to the handler. If the handler
* already has a certificate, this request will be rejected.
*
* @param aux The attestation handler to update.
* @param cert The DER encoded certificate for the attestation key. The memory for this data must
* be dynamically allocated and is owned by the attestation handler upon successful return.
* @param length The length of the certificate data.
*
* @return 0 if the certificate was taken by the handler or an error code.
*/
int aux_attestation_set_certificate (struct aux_attestation *aux, uint8_t *cert, size_t length)
{
if ((aux == NULL) || (cert == NULL) || (length == 0)) {
return AUX_ATTESTATION_INVALID_ARGUMENT;
}
if (aux->rsa == NULL) {
return AUX_ATTESTATION_UNSUPPORTED_CRYPTO;
}
if (aux->cert.cert) {
return AUX_ATTESTATION_HAS_CERTIFICATE;
}
aux->cert.cert = cert;
aux->cert.length = length;
return 0;
}
/**
* Provide the signed certificate for the attestation RSA key to the handler. If the handler
* already has a certificate, this request will be rejected.
*
* @param aux The attestation handler to update.
* @param cert The DER encoded certificate for the attestation key. The memory for this data must
* be statically allocated.
* @param length The length of the certificate data.
*
* @return 0 if the certificate was taken by the handler or an error code.
*/
int aux_attestation_set_static_certificate (struct aux_attestation *aux, const uint8_t *cert,
size_t length)
{
int status;
status = aux_attestation_set_certificate (aux, (uint8_t*) cert, length);
if (status != 0) {
return status;
}
aux->is_static = true;
return 0;
}
/**
* Get the certificate for the attestation key.
*
* @param aux The attestion handler to query.
*
* @return The attestation certificate or null if there is no certificate. The memory for this
* certificate is owned by the attestation handler and must not be freed.
*/
const struct der_cert* aux_attestation_get_certificate (struct aux_attestation *aux)
{
if (aux && aux->cert.cert) {
return &aux->cert;
}
else {
return NULL;
}
}
/**
* Process an attestation request to unseal the encrypted attestation data.
*
* @param aux The attestation handler to run.
* @param hash The hash engine to use for unsealing.
* @param pcr Local PCRs to use for sealing verification.
* @param key_type The length of the encryption and signing keys that will be generated.
* @param seed The obfuscated seed to use for key derivation.
* @param seed_length The length of the obfuscated seed.
* @param seed_type The method to use for determining the KDF seed.
* @param seed_param Details about the method used to determine the KDF seed.
* @param hmac HMAC of the ciphertext and sealing data using the signing key.
* @param hmac_type The type of HMAC used.
* @param ciphertext The encrypted attestation data.
* @param cipher_length Length of the encrypted data.
* @param sealing A list of 64-byte sealing values for the attestation data.
* @param pcr_count The number of PCRs used for sealing.
* @param key Output for the unsealed encryption key that will decrypt the attestation data.
* @param key_length Length of the encryption key buffer. This must be large enough to support the
* requested key length.
*
* @return 0 if the unsealing was successful or an error code.
*/
int aux_attestation_unseal (struct aux_attestation *aux, const struct hash_engine *hash,
struct pcr_store *pcr, enum aux_attestation_key_length key_type, const uint8_t *seed,
size_t seed_length, enum aux_attestation_seed_type seed_type,
enum aux_attestation_seed_param seed_param, const uint8_t *hmac, enum hmac_hash hmac_type,
const uint8_t *ciphertext, size_t cipher_length, const uint8_t sealing[][64], size_t pcr_count,
uint8_t *key, size_t key_length)
{
uint8_t secret[AUX_ATTESTATION_KEY_BYTES];
int secret_length = 0;
struct hmac_engine run_hmac;
uint8_t signing_key[AUX_ATTESTATION_KEY_256BIT];
uint8_t payload_hmac[SHA256_HASH_LENGTH];
uint8_t pcr_value[PCR_MAX_DIGEST_LENGTH];
int pcr_length;
bool bypass;
size_t i;
int j;
int status;
if ((aux == NULL) || (hash == NULL) || (pcr == NULL) || (seed == NULL) || (seed_length == 0) ||
(hmac == NULL) || (ciphertext == NULL) || (cipher_length == 0) || (sealing == NULL) ||
(pcr_count == 0) || (key == NULL)) {
return AUX_ATTESTATION_INVALID_ARGUMENT;
}
if (key_type != AUX_ATTESTATION_KEY_256BIT) {
return AUX_ATTESTATION_UNSUPPORTED_KEY_LENGTH;
}
if (hmac_type != HMAC_SHA256) {
return AUX_ATTESTATION_UNSUPPORTED_HMAC;
}
if (key_length < AUX_ATTESTATION_KEY_256BIT) {
return AUX_ATTESTATION_BUFFER_TOO_SMALL;
}
/* Get the key derivation seed. */
switch (seed_type) {
case AUX_ATTESTATION_SEED_RSA: {
enum hash_type padding;
switch (seed_param) {
case AUX_ATTESTATION_PARAM_OAEP_SHA1:
padding = HASH_TYPE_SHA1;
break;
case AUX_ATTESTATION_PARAM_OAEP_SHA256:
padding = HASH_TYPE_SHA256;
break;
default:
return AUX_ATTESTATION_BAD_SEED_PARAM;
}
secret_length = aux_attestation_decrypt (aux, seed, seed_length, NULL, 0, padding,
secret, sizeof (secret));
break;
}
case AUX_ATTESTATION_SEED_ECDH:
if ((seed_param != AUX_ATTESTATION_PARAM_ECDH_RAW) &&
(seed_param != AUX_ATTESTATION_PARAM_ECDH_SHA256)) {
return AUX_ATTESTATION_BAD_SEED_PARAM;
}
secret_length = aux_attestation_generate_ecdh_seed (aux, seed, seed_length,
(seed_param == AUX_ATTESTATION_PARAM_ECDH_SHA256) ? hash : NULL, secret,
sizeof (secret));
break;
default:
return AUX_ATTESTATION_UNKNOWN_SEED;
}
if (ROT_IS_ERROR (secret_length)) {
return secret_length;
}
/* Derive the signing key. */
status = kdf_nist800_108_counter_mode (hash, HMAC_SHA256, secret, secret_length,
(const uint8_t*) AUX_ATTESTATION_SIGNING_LABEL, sizeof (AUX_ATTESTATION_SIGNING_LABEL) - 1,
NULL, 0, signing_key, sizeof (signing_key));
if (status != 0) {
return status;
}
/* Validate the payload. */
status = hash_hmac_init (&run_hmac, hash, HMAC_SHA256, signing_key, SHA256_HASH_LENGTH);
if (status != 0) {
return status;
}
status = hash_hmac_update (&run_hmac, ciphertext, cipher_length);
if (status != 0) {
goto hmac_error;
}
status = hash_hmac_update (&run_hmac, sealing[0], 64 * pcr_count);
if (status != 0) {
goto hmac_error;
}
status = hash_hmac_finish (&run_hmac, payload_hmac, sizeof (payload_hmac));
if (status != 0) {
return status;
}
if (buffer_compare (hmac, payload_hmac, SHA256_HASH_LENGTH) != 0) {
return AUX_ATTESTATION_HMAC_MISMATCH;
}
for (i = 0; i < pcr_count; i++) {
j = 0;
bypass = true;
pcr_length = pcr_store_get_pcr_digest_length (pcr, i);
if (pcr_length == PCR_INVALID_PCR) {
/* The PCR is not supported by the device, but is present in the sealing data. This
* is fine as long as the unsupported PCR is bypassed by the sealing. If it's not, this
* same error will get triggered when attempting to calculate the PCR value.
*
* For now, just use the maximum length, which skips the zero-padding check on this PCR,
* since it's not meaningful. There are going to either be all zeros and bypassed or
* not all zeros and trigger an invalid PCR error (which is more descriptive than a
* length mismatch error). */
pcr_length = 64;
}
while (bypass && (j < 64)) {
if (sealing[i][j] != 0) {
if (j < (64 - pcr_length)) {
/* When the PCR length is shorter than the sealing policy, the first sealing
* bytes are unused and must be 0. */
return AUX_ATTESTATION_PCR_LENGTH_MISMATCH;
}
bypass = false;
}
j++;
}
if (!bypass) {
status = pcr_store_compute_pcr (pcr, hash, i, pcr_value, sizeof (pcr_value));
if (ROT_IS_ERROR (status)) {
return status;
}
if (buffer_compare (pcr_value, &sealing[i][(64 - pcr_length)], pcr_length) != 0) {
return AUX_ATTESTATION_PCR_MISMATCH;
}
}
}
/* Derive the encryption key. */
status = kdf_nist800_108_counter_mode (hash, HMAC_SHA256, secret, secret_length,
(const uint8_t*) AUX_ATTESTATION_ENCRYPTION_LABEL,
sizeof (AUX_ATTESTATION_ENCRYPTION_LABEL) - 1, NULL, 0, key, SHA256_HASH_LENGTH);
return status;
hmac_error:
hash_hmac_cancel (&run_hmac);
return status;
}
/**
* Decrypt a payload using the attestation key.
*
* @param aux The attestation handler to run.
* @param encrypted Payload to decrypt.
* @param len_encrypted Length of payload to decrypt.
* @param label Optional label to use during decryption.
* @param len_label Length of the optional label.
* @param pad_hash Algorithm used for padding generation.
* @param decrypted Output for the decrypted payload.
* @param len_decrypted Length of decrypted payload buffer.
*
* @return Decrypted payload length if the decryption was successful or an error code.
*/
int aux_attestation_decrypt (struct aux_attestation *aux, const uint8_t *encrypted,
size_t len_encrypted, const uint8_t *label, size_t len_label, enum hash_type pad_hash,
uint8_t *decrypted, size_t len_decrypted)
{
#ifdef ATTESTATION_SUPPORT_RSA_UNSEAL
struct rsa_private_key priv;
uint8_t *priv_der;
size_t priv_length;
int status;
if ((aux == NULL) || (encrypted == NULL) || (decrypted == NULL)) {
return AUX_ATTESTATION_INVALID_ARGUMENT;
}
if (aux->rsa == NULL) {
return AUX_ATTESTATION_UNSUPPORTED_CRYPTO;
}
status = aux->keystore->load_key (aux->keystore, 0, &priv_der, &priv_length);
if (status != 0) {
return status;
}
status = aux->rsa->init_private_key (aux->rsa, &priv, priv_der, priv_length);
if (status != 0) {
goto rsa_init_error;
}
status = aux->rsa->decrypt (aux->rsa, &priv, encrypted, len_encrypted, label, len_label,
pad_hash, decrypted, len_decrypted);
aux->rsa->release_key (aux->rsa, &priv);
rsa_init_error:
riot_core_clear (priv_der, priv_length);
platform_free (priv_der);
return status;
#else
UNUSED (aux);
UNUSED (encrypted);
UNUSED (len_encrypted);
UNUSED (label);
UNUSED (len_label);
UNUSED (pad_hash);
UNUSED (decrypted);
UNUSED (len_decrypted);
return AUX_ATTESTATION_UNSUPPORTED_CRYPTO;
#endif
}
/**
* Generate an ECDH seed to be used for auxiliary attestation flows.
*
* @param aux The attestation handler to run.
* @param ecc_key A DER encoded ECC public key to use for seed generation.
* @param key_length Length of the ECC public key.
* @param hash Optional hash engine. If provided, the seed will be the SHA256 hash of the ECDH
* output.
* @param seed Output buffer for the attestation seed.
* @param seed_length Length of the seed buffer.
*
* @return Length of generated seed or an error code. Use ROT_IS_ERROR to check the return value.
*/
int aux_attestation_generate_ecdh_seed (struct aux_attestation *aux, const uint8_t *ecc_key,
size_t key_length, const struct hash_engine *hash, uint8_t *seed, size_t seed_length)
{
#ifdef ATTESTATION_SUPPORT_ECDH_UNSEAL
struct ecc_private_key priv;
struct ecc_public_key pub;
const struct riot_keys *keys;
int status;
if ((aux == NULL) || (ecc_key == NULL) || (key_length == 0) || (seed == NULL)) {
return AUX_ATTESTATION_INVALID_ARGUMENT;
}
if (aux->ecc == NULL) {
return AUX_ATTESTATION_UNSUPPORTED_CRYPTO;
}
status = aux->ecc->init_public_key (aux->ecc, ecc_key, key_length, &pub);
if (status != 0) {
return status;
}
keys = riot_key_manager_get_riot_keys (aux->riot);
status = aux->ecc->init_key_pair (aux->ecc, keys->alias_key, keys->alias_key_length, &priv,
NULL);
riot_key_manager_release_riot_keys (aux->riot, keys);
if (status != 0) {
goto ecc_init_error;
}
status = aux->ecc->get_shared_secret_max_length (aux->ecc, &priv);
if (ROT_IS_ERROR (status)) {
goto error;
}
if (((int) seed_length < status) || (hash && (seed_length < SHA256_HASH_LENGTH))) {
status = AUX_ATTESTATION_BUFFER_TOO_SMALL;
goto error;
}
status = aux->ecc->compute_shared_secret (aux->ecc, &priv, &pub, seed, seed_length);
if (ROT_IS_ERROR (status)) {
goto error;
}
if (hash) {
uint8_t secret_hash[SHA256_HASH_LENGTH];
status = hash->calculate_sha256 (hash, seed, status, secret_hash, sizeof (secret_hash));
if (status == 0) {
memcpy (seed, secret_hash, SHA256_HASH_LENGTH);
status = SHA256_HASH_LENGTH;
}
}
error:
aux->ecc->release_key_pair (aux->ecc, &priv, NULL);
ecc_init_error:
aux->ecc->release_key_pair (aux->ecc, NULL, &pub);
return status;
#else
UNUSED (aux);
UNUSED (ecc_key);
UNUSED (key_length);
UNUSED (hash);
UNUSED (seed);
UNUSED (seed_length);
return AUX_ATTESTATION_UNSUPPORTED_CRYPTO;
#endif
}