Alice Wang | 8b8e6e6 | 2023-10-02 09:10:13 +0000 | [diff] [blame] | 1 | // Copyright 2023, The Android Open Source Project |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | //! Handles the encryption and decryption of the key blob. |
| 16 | |
| 17 | use crate::cbor; |
| 18 | use alloc::vec; |
| 19 | use alloc::vec::Vec; |
Alice Wang | 78b35f8 | 2023-10-06 11:57:35 +0000 | [diff] [blame] | 20 | use bssl_avf::{hkdf, rand_bytes, Aead, AeadContext, Digester, AES_GCM_NONCE_LENGTH}; |
Alice Wang | 8b8e6e6 | 2023-10-02 09:10:13 +0000 | [diff] [blame] | 21 | use core::result; |
| 22 | use serde::{Deserialize, Serialize}; |
| 23 | use service_vm_comm::RequestProcessingError; |
| 24 | // TODO(b/241428146): This will be used once the retrieval mechanism is available. |
| 25 | #[cfg(test)] |
| 26 | use zeroize::Zeroizing; |
| 27 | |
| 28 | type Result<T> = result::Result<T, RequestProcessingError>; |
| 29 | |
| 30 | /// The KEK (Key Encryption Key) info is used as information to derive the KEK using HKDF. |
| 31 | const KEK_INFO: &[u8] = b"rialto keyblob kek"; |
| 32 | |
| 33 | /// An all-zero nonce is utilized to encrypt the private key. This is because each key |
| 34 | /// undergoes encryption using a distinct KEK, which is derived from a secret and a random |
| 35 | /// salt. Since the uniqueness of the IV/key combination is already guaranteed by the uniqueness |
| 36 | /// of the KEK, there is no need for an additional random nonce. |
| 37 | const PRIVATE_KEY_NONCE: &[u8; AES_GCM_NONCE_LENGTH] = &[0; AES_GCM_NONCE_LENGTH]; |
| 38 | |
| 39 | /// Since Rialto functions as both the sender and receiver of the message, no additional data is |
| 40 | /// needed. |
| 41 | const PRIVATE_KEY_AD: &[u8] = &[]; |
| 42 | |
| 43 | // Encrypted key blob. |
| 44 | #[derive(Clone, Debug, Deserialize, Serialize)] |
| 45 | pub(crate) enum EncryptedKeyBlob { |
| 46 | /// Version 1 key blob. |
| 47 | V1(EncryptedKeyBlobV1), |
| 48 | } |
| 49 | |
| 50 | /// Encrypted key blob version 1. |
| 51 | #[derive(Clone, Debug, Deserialize, Serialize)] |
| 52 | pub(crate) struct EncryptedKeyBlobV1 { |
| 53 | /// Salt used to derive the KEK. |
| 54 | kek_salt: [u8; 32], |
| 55 | |
| 56 | /// Private key encrypted with AES-256-GCM. |
| 57 | encrypted_private_key: Vec<u8>, |
| 58 | } |
| 59 | |
| 60 | impl EncryptedKeyBlob { |
| 61 | pub(crate) fn new(private_key: &[u8], kek_secret: &[u8]) -> Result<Self> { |
| 62 | EncryptedKeyBlobV1::new(private_key, kek_secret).map(Self::V1) |
| 63 | } |
| 64 | |
| 65 | // TODO(b/241428146): Use this function to decrypt the retrieved keyblob once the retrieval |
| 66 | // mechanism is available. |
| 67 | #[cfg(test)] |
| 68 | pub(crate) fn decrypt_private_key(&self, kek_secret: &[u8]) -> Result<Zeroizing<Vec<u8>>> { |
| 69 | match self { |
| 70 | Self::V1(blob) => blob.decrypt_private_key(kek_secret), |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | // TODO(b/241428146): This function will be used once the retrieval mechanism is available. |
| 75 | #[cfg(test)] |
| 76 | pub(crate) fn from_cbor_slice(slice: &[u8]) -> coset::Result<Self> { |
| 77 | cbor::deserialize(slice) |
| 78 | } |
| 79 | |
| 80 | pub(crate) fn to_cbor_vec(&self) -> coset::Result<Vec<u8>> { |
| 81 | cbor::serialize(&self) |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | impl EncryptedKeyBlobV1 { |
| 86 | fn new(private_key: &[u8], kek_secret: &[u8]) -> Result<Self> { |
| 87 | let mut kek_salt = [0u8; 32]; |
| 88 | rand_bytes(&mut kek_salt)?; |
| 89 | let kek = hkdf::<32>(kek_secret, &kek_salt, KEK_INFO, Digester::sha512())?; |
| 90 | |
| 91 | let tag_len = None; |
Alice Wang | 78b35f8 | 2023-10-06 11:57:35 +0000 | [diff] [blame] | 92 | let aead_ctx = AeadContext::new(Aead::aes_256_gcm(), kek.as_slice(), tag_len)?; |
Alice Wang | 8b8e6e6 | 2023-10-02 09:10:13 +0000 | [diff] [blame] | 93 | let mut out = vec![0u8; private_key.len() + aead_ctx.aead().max_overhead()]; |
| 94 | let ciphertext = aead_ctx.seal(private_key, PRIVATE_KEY_NONCE, PRIVATE_KEY_AD, &mut out)?; |
| 95 | |
| 96 | Ok(Self { kek_salt, encrypted_private_key: ciphertext.to_vec() }) |
| 97 | } |
| 98 | |
| 99 | #[cfg(test)] |
| 100 | fn decrypt_private_key(&self, kek_secret: &[u8]) -> Result<Zeroizing<Vec<u8>>> { |
| 101 | let kek = hkdf::<32>(kek_secret, &self.kek_salt, KEK_INFO, Digester::sha512())?; |
| 102 | let mut out = Zeroizing::new(vec![0u8; self.encrypted_private_key.len()]); |
| 103 | let tag_len = None; |
Alice Wang | 78b35f8 | 2023-10-06 11:57:35 +0000 | [diff] [blame] | 104 | let aead_ctx = AeadContext::new(Aead::aes_256_gcm(), kek.as_slice(), tag_len)?; |
Alice Wang | 8b8e6e6 | 2023-10-02 09:10:13 +0000 | [diff] [blame] | 105 | let plaintext = aead_ctx.open( |
| 106 | &self.encrypted_private_key, |
| 107 | PRIVATE_KEY_NONCE, |
| 108 | PRIVATE_KEY_AD, |
| 109 | &mut out, |
| 110 | )?; |
| 111 | Ok(Zeroizing::new(plaintext.to_vec())) |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | #[cfg(test)] |
| 116 | mod tests { |
| 117 | use super::*; |
| 118 | use bssl_avf::{ApiName, CipherError, Error}; |
| 119 | |
| 120 | /// The test data are generated randomly with /dev/urandom. |
| 121 | const TEST_KEY: [u8; 32] = [ |
| 122 | 0x76, 0xf7, 0xd5, 0x36, 0x1f, 0x78, 0x58, 0x2e, 0x55, 0x2f, 0x88, 0x9d, 0xa3, 0x3e, 0xba, |
| 123 | 0xfb, 0xc1, 0x2b, 0x17, 0x85, 0x24, 0xdc, 0x0e, 0xc4, 0xbf, 0x6d, 0x2e, 0xe8, 0xa8, 0x36, |
| 124 | 0x93, 0x62, |
| 125 | ]; |
| 126 | const TEST_SECRET1: [u8; 32] = [ |
| 127 | 0xac, 0xb1, 0x6b, 0xdf, 0x45, 0x30, 0x20, 0xa5, 0x60, 0x6d, 0x81, 0x07, 0x30, 0x68, 0x6e, |
| 128 | 0x01, 0x3d, 0x5e, 0x86, 0xd6, 0xc6, 0x17, 0xfa, 0xd6, 0xe0, 0xff, 0xd4, 0xf0, 0xb0, 0x7c, |
| 129 | 0x5c, 0x8f, |
| 130 | ]; |
| 131 | const TEST_SECRET2: [u8; 32] = [ |
| 132 | 0x04, 0x6e, 0xca, 0x30, 0x5e, 0x6c, 0x8f, 0xe5, 0x1a, 0x47, 0x12, 0xbc, 0x45, 0xd7, 0xa8, |
| 133 | 0x38, 0xfb, 0x06, 0xc6, 0x44, 0xa1, 0x21, 0x40, 0x0b, 0x48, 0x88, 0xe2, 0x31, 0x64, 0x42, |
| 134 | 0x9d, 0x1c, |
| 135 | ]; |
| 136 | |
| 137 | #[test] |
| 138 | fn decrypting_keyblob_succeeds_with_the_same_kek() -> Result<()> { |
| 139 | let encrypted_key_blob = EncryptedKeyBlob::new(&TEST_KEY, &TEST_SECRET1)?.to_cbor_vec()?; |
| 140 | let encrypted_key_blob = EncryptedKeyBlob::from_cbor_slice(&encrypted_key_blob)?; |
| 141 | let decrypted_key = encrypted_key_blob.decrypt_private_key(&TEST_SECRET1)?; |
| 142 | |
| 143 | assert_eq!(TEST_KEY, decrypted_key.as_slice()); |
| 144 | Ok(()) |
| 145 | } |
| 146 | |
| 147 | #[test] |
| 148 | fn decrypting_keyblob_fails_with_a_different_kek() -> Result<()> { |
| 149 | let encrypted_key_blob = EncryptedKeyBlob::new(&TEST_KEY, &TEST_SECRET1)?.to_cbor_vec()?; |
| 150 | let encrypted_key_blob = EncryptedKeyBlob::from_cbor_slice(&encrypted_key_blob)?; |
| 151 | let err = encrypted_key_blob.decrypt_private_key(&TEST_SECRET2).unwrap_err(); |
| 152 | |
| 153 | let expected_err: RequestProcessingError = |
| 154 | Error::CallFailed(ApiName::EVP_AEAD_CTX_open, CipherError::BadDecrypt.into()).into(); |
| 155 | assert_eq!(expected_err, err); |
| 156 | Ok(()) |
| 157 | } |
| 158 | } |