blob: 55588368ecf29d50c9ecbec2a3ef4c62a80305a1 [file] [log] [blame]
// Copyright 2023, 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.
//! Handles the encryption and decryption of the key blob.
use crate::cbor;
use alloc::vec;
use alloc::vec::Vec;
use bssl_avf::{hkdf, rand_bytes, Aead, AeadCtx, Digester, AES_GCM_NONCE_LENGTH};
use core::result;
use serde::{Deserialize, Serialize};
use service_vm_comm::RequestProcessingError;
// TODO(b/241428146): This will be used once the retrieval mechanism is available.
#[cfg(test)]
use zeroize::Zeroizing;
type Result<T> = result::Result<T, RequestProcessingError>;
/// The KEK (Key Encryption Key) info is used as information to derive the KEK using HKDF.
const KEK_INFO: &[u8] = b"rialto keyblob kek";
/// An all-zero nonce is utilized to encrypt the private key. This is because each key
/// undergoes encryption using a distinct KEK, which is derived from a secret and a random
/// salt. Since the uniqueness of the IV/key combination is already guaranteed by the uniqueness
/// of the KEK, there is no need for an additional random nonce.
const PRIVATE_KEY_NONCE: &[u8; AES_GCM_NONCE_LENGTH] = &[0; AES_GCM_NONCE_LENGTH];
/// Since Rialto functions as both the sender and receiver of the message, no additional data is
/// needed.
const PRIVATE_KEY_AD: &[u8] = &[];
// Encrypted key blob.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub(crate) enum EncryptedKeyBlob {
/// Version 1 key blob.
V1(EncryptedKeyBlobV1),
}
/// Encrypted key blob version 1.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub(crate) struct EncryptedKeyBlobV1 {
/// Salt used to derive the KEK.
kek_salt: [u8; 32],
/// Private key encrypted with AES-256-GCM.
encrypted_private_key: Vec<u8>,
}
impl EncryptedKeyBlob {
pub(crate) fn new(private_key: &[u8], kek_secret: &[u8]) -> Result<Self> {
EncryptedKeyBlobV1::new(private_key, kek_secret).map(Self::V1)
}
// TODO(b/241428146): Use this function to decrypt the retrieved keyblob once the retrieval
// mechanism is available.
#[cfg(test)]
pub(crate) fn decrypt_private_key(&self, kek_secret: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
match self {
Self::V1(blob) => blob.decrypt_private_key(kek_secret),
}
}
// TODO(b/241428146): This function will be used once the retrieval mechanism is available.
#[cfg(test)]
pub(crate) fn from_cbor_slice(slice: &[u8]) -> coset::Result<Self> {
cbor::deserialize(slice)
}
pub(crate) fn to_cbor_vec(&self) -> coset::Result<Vec<u8>> {
cbor::serialize(&self)
}
}
impl EncryptedKeyBlobV1 {
fn new(private_key: &[u8], kek_secret: &[u8]) -> Result<Self> {
let mut kek_salt = [0u8; 32];
rand_bytes(&mut kek_salt)?;
let kek = hkdf::<32>(kek_secret, &kek_salt, KEK_INFO, Digester::sha512())?;
let tag_len = None;
let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), kek.as_slice(), tag_len)?;
let mut out = vec![0u8; private_key.len() + aead_ctx.aead().max_overhead()];
let ciphertext = aead_ctx.seal(private_key, PRIVATE_KEY_NONCE, PRIVATE_KEY_AD, &mut out)?;
Ok(Self { kek_salt, encrypted_private_key: ciphertext.to_vec() })
}
#[cfg(test)]
fn decrypt_private_key(&self, kek_secret: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
let kek = hkdf::<32>(kek_secret, &self.kek_salt, KEK_INFO, Digester::sha512())?;
let mut out = Zeroizing::new(vec![0u8; self.encrypted_private_key.len()]);
let tag_len = None;
let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), kek.as_slice(), tag_len)?;
let plaintext = aead_ctx.open(
&self.encrypted_private_key,
PRIVATE_KEY_NONCE,
PRIVATE_KEY_AD,
&mut out,
)?;
Ok(Zeroizing::new(plaintext.to_vec()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use bssl_avf::{ApiName, CipherError, Error};
/// The test data are generated randomly with /dev/urandom.
const TEST_KEY: [u8; 32] = [
0x76, 0xf7, 0xd5, 0x36, 0x1f, 0x78, 0x58, 0x2e, 0x55, 0x2f, 0x88, 0x9d, 0xa3, 0x3e, 0xba,
0xfb, 0xc1, 0x2b, 0x17, 0x85, 0x24, 0xdc, 0x0e, 0xc4, 0xbf, 0x6d, 0x2e, 0xe8, 0xa8, 0x36,
0x93, 0x62,
];
const TEST_SECRET1: [u8; 32] = [
0xac, 0xb1, 0x6b, 0xdf, 0x45, 0x30, 0x20, 0xa5, 0x60, 0x6d, 0x81, 0x07, 0x30, 0x68, 0x6e,
0x01, 0x3d, 0x5e, 0x86, 0xd6, 0xc6, 0x17, 0xfa, 0xd6, 0xe0, 0xff, 0xd4, 0xf0, 0xb0, 0x7c,
0x5c, 0x8f,
];
const TEST_SECRET2: [u8; 32] = [
0x04, 0x6e, 0xca, 0x30, 0x5e, 0x6c, 0x8f, 0xe5, 0x1a, 0x47, 0x12, 0xbc, 0x45, 0xd7, 0xa8,
0x38, 0xfb, 0x06, 0xc6, 0x44, 0xa1, 0x21, 0x40, 0x0b, 0x48, 0x88, 0xe2, 0x31, 0x64, 0x42,
0x9d, 0x1c,
];
#[test]
fn decrypting_keyblob_succeeds_with_the_same_kek() -> Result<()> {
let encrypted_key_blob = EncryptedKeyBlob::new(&TEST_KEY, &TEST_SECRET1)?.to_cbor_vec()?;
let encrypted_key_blob = EncryptedKeyBlob::from_cbor_slice(&encrypted_key_blob)?;
let decrypted_key = encrypted_key_blob.decrypt_private_key(&TEST_SECRET1)?;
assert_eq!(TEST_KEY, decrypted_key.as_slice());
Ok(())
}
#[test]
fn decrypting_keyblob_fails_with_a_different_kek() -> Result<()> {
let encrypted_key_blob = EncryptedKeyBlob::new(&TEST_KEY, &TEST_SECRET1)?.to_cbor_vec()?;
let encrypted_key_blob = EncryptedKeyBlob::from_cbor_slice(&encrypted_key_blob)?;
let err = encrypted_key_blob.decrypt_private_key(&TEST_SECRET2).unwrap_err();
let expected_err: RequestProcessingError =
Error::CallFailed(ApiName::EVP_AEAD_CTX_open, CipherError::BadDecrypt.into()).into();
assert_eq!(expected_err, err);
Ok(())
}
}