zxcrypt_crypt/
lib.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use aes_gcm_siv::aead::{Aead as _, Payload};
6use aes_gcm_siv::{Aes128GcmSiv, Key, KeyInit as _, Nonce};
7use anyhow::Error;
8use crypt_policy::{KeyConsumer, KeySource, Policy, unseal_sources};
9use fidl::endpoints::{ClientEnd, create_request_stream};
10use fidl_fuchsia_fxfs::CryptRequest;
11use futures::{FutureExt, TryStreamExt};
12use hkdf::Hkdf;
13use std::future::Future;
14use std::pin::pin;
15use uuid::Uuid;
16use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
17
18#[repr(C, packed)]
19#[derive(Clone, Copy, Debug, FromBytes, Immutable, IntoBytes, KnownLayout)]
20struct ZxcryptHeader {
21    magic: u128,
22    guid: [u8; 16],
23    version: u32,
24}
25
26const ZXCRYPT_MAGIC: u128 = 0x74707972_63787a80_e7116db3_00f8e85f;
27const ZXCRYPT_VERSION: u32 = 0x01000000;
28
29async fn unwrap_zxcrypt_key(policy: Policy, wrapped_key: &[u8]) -> Result<Vec<u8>, zx::Status> {
30    if wrapped_key.len() != 132 {
31        return Err(zx::Status::INVALID_ARGS);
32    }
33    let sources = unseal_sources(policy);
34
35    let (header, _) = ZxcryptHeader::read_from_prefix(wrapped_key).unwrap();
36
37    for source in sources {
38        let key = match source {
39            KeySource::Null(null) => null.get_key(KeyConsumer::Zxcrypt),
40            KeySource::TeeDerived(tee) => tee.get_key().await.map_err(|_| zx::Status::INTERNAL)?,
41            // zxcrypt is deprecated, so don't bother supporting any new key sources
42            _ => return Err(zx::Status::NOT_SUPPORTED),
43        };
44        let hk = Hkdf::<sha2::Sha256>::new(Some(&header.guid), &key);
45        let mut wrap_key = [0; 16];
46        let mut wrap_iv = [0; 12];
47        hk.expand("wrap key 0".as_bytes(), &mut wrap_key).unwrap();
48        hk.expand("wrap iv 0".as_bytes(), &mut wrap_iv).unwrap();
49
50        let header_size = std::mem::size_of::<ZxcryptHeader>();
51
52        if let Ok(unwrapped) = Aes128GcmSiv::new(Key::<Aes128GcmSiv>::from_slice(&wrap_key))
53            .decrypt(
54                &Nonce::from_slice(&wrap_iv),
55                Payload { msg: &wrapped_key[header_size..], aad: &wrapped_key[..header_size] },
56            )
57        {
58            return Ok(unwrapped);
59        }
60    }
61    log::warn!("Failed to unwrap zxcrypt key!");
62    Err(zx::Status::IO_DATA_INTEGRITY)
63}
64
65async fn create_zxcrypt_key(policy: Policy) -> Result<([u8; 16], Vec<u8>, Vec<u8>), zx::Status> {
66    let sources = unseal_sources(policy);
67
68    let header = ZxcryptHeader {
69        magic: ZXCRYPT_MAGIC,
70        guid: *Uuid::new_v4().as_bytes(),
71        version: ZXCRYPT_VERSION,
72    };
73
74    let mut unwrapped_key = vec![0; 80];
75    zx::cprng_draw(&mut unwrapped_key);
76
77    if let Some(source) = sources.first() {
78        let key = match source {
79            KeySource::Null(null) => null.get_key(KeyConsumer::Zxcrypt),
80            KeySource::TeeDerived(tee) => tee.get_key().await.map_err(|_| zx::Status::INTERNAL)?,
81            _ => return Err(zx::Status::NOT_SUPPORTED),
82        };
83        let hk = Hkdf::<sha2::Sha256>::new(Some(&header.guid), &key);
84        let mut wrap_key = [0; 16];
85        let mut wrap_iv = [0; 12];
86        hk.expand("wrap key 0".as_bytes(), &mut wrap_key).unwrap();
87        hk.expand("wrap iv 0".as_bytes(), &mut wrap_iv).unwrap();
88
89        let wrapped = Aes128GcmSiv::new(Key::<Aes128GcmSiv>::from_slice(&wrap_key))
90            .encrypt(
91                &Nonce::from_slice(&wrap_iv),
92                Payload { msg: &unwrapped_key, aad: &header.as_bytes() },
93            )
94            .unwrap();
95
96        let mut header_and_key = header.as_bytes().to_vec();
97        header_and_key.extend(wrapped);
98
99        Ok(([0; 16], header_and_key, unwrapped_key))
100    } else {
101        log::warn!("No keys sources to create zxcrypt key");
102        Err(zx::Status::INTERNAL)
103    }
104}
105
106pub async fn run_crypt_service(
107    policy: Policy,
108    mut stream: fidl_fuchsia_fxfs::CryptRequestStream,
109) -> Result<(), Error> {
110    while let Some(request) = stream.try_next().await? {
111        match request {
112            CryptRequest::CreateKey { responder, .. } => responder.send(
113                create_zxcrypt_key(policy)
114                    .await
115                    .as_ref()
116                    .map(|(id, w, u)| (id, &w[..], &u[..]))
117                    .map_err(|s| s.into_raw()),
118            )?,
119            CryptRequest::CreateKeyWithId { responder, .. } => {
120                responder.send(Err(zx::Status::BAD_PATH.into_raw()))?
121            }
122            CryptRequest::UnwrapKey { responder, wrapped_key, .. } => {
123                let response;
124                responder.send(match &wrapped_key {
125                    fidl_fuchsia_fxfs::WrappedKey::Zxcrypt(key) => {
126                        response = unwrap_zxcrypt_key(policy, key).await;
127                        match &response {
128                            Ok(v) => Ok(&v[..]),
129                            Err(e) => Err(e.into_raw()),
130                        }
131                    }
132                    _ => Err(zx::Status::INTERNAL.into_raw()),
133                })?;
134            }
135        }
136    }
137    Ok::<(), Error>(())
138}
139
140/// Runs `f` with a scoped crypt service instance.  The instance will be automatically terminated on
141/// completion.
142pub async fn with_crypt_service<R, Fut: Future<Output = Result<R, Error>>>(
143    policy: Policy,
144    f: impl FnOnce(ClientEnd<fidl_fuchsia_fxfs::CryptMarker>) -> Fut,
145) -> Result<R, Error> {
146    let (crypt, stream) = create_request_stream::<fidl_fuchsia_fxfs::CryptMarker>();
147    let mut crypt_service = pin!(async { run_crypt_service(policy, stream).await }.fuse());
148    let mut fut = pin!(f(crypt).fuse());
149
150    loop {
151        futures::select! {
152            _ = crypt_service => {}
153            result = fut => return result,
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::{ZXCRYPT_MAGIC, ZXCRYPT_VERSION, ZxcryptHeader, with_crypt_service};
161    use crypt_policy::Policy;
162    use fidl_fuchsia_fxfs::WrappedKey;
163    use zerocopy::FromBytes;
164
165    fn entropy(data: &[u8]) -> f64 {
166        let mut frequencies = [0; 256];
167        for b in data {
168            frequencies[*b as usize] += 1;
169        }
170        -frequencies
171            .into_iter()
172            .map(|f| {
173                if f > 0 {
174                    let p = f as f64 / data.len() as f64;
175                    p * p.log2()
176                } else {
177                    0.0
178                }
179            })
180            .sum::<f64>()
181            / (data.len() as f64).log2()
182    }
183
184    #[fuchsia::test]
185    async fn test_keys() {
186        with_crypt_service(Policy::Null, |crypt| async {
187            let crypt = crypt.into_proxy();
188            let (_, wrapped_key, unwrapped_key) = crypt
189                .create_key(0, fidl_fuchsia_fxfs::KeyPurpose::Data)
190                .await
191                .unwrap()
192                .expect("create_key failed");
193
194            // Check that unwrapped_key has high entropy.
195            assert!(entropy(&unwrapped_key) > 0.5);
196
197            // Check that key has the correct fields set.
198            let (header, _) = ZxcryptHeader::read_from_prefix(&wrapped_key).unwrap();
199
200            let magic = header.magic;
201            assert_eq!(magic, ZXCRYPT_MAGIC);
202            assert!(entropy(&header.guid) > 0.5);
203            let version = header.version;
204            assert_eq!(version, ZXCRYPT_VERSION);
205
206            // Check that we can unwrap the returned key.
207            let unwrapped_key2 = crypt
208                .unwrap_key(0, &WrappedKey::Zxcrypt(wrapped_key))
209                .await
210                .unwrap()
211                .expect("unwrap_key failed");
212
213            assert_eq!(unwrapped_key, unwrapped_key2);
214            Ok(())
215        })
216        .await
217        .unwrap();
218    }
219}