Skip to main content

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, KeyInit as _};
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    let mut last_err = None;
38    for source in sources {
39        let key = match source {
40            KeySource::Null(null) => null.get_key(KeyConsumer::Zxcrypt),
41            KeySource::TeeDerived(tee) => tee.get_key().await.map_err(|_| zx::Status::INTERNAL)?,
42            // zxcrypt is deprecated, so don't bother supporting any new key sources
43            _ => return Err(zx::Status::NOT_SUPPORTED),
44        };
45        let hk = Hkdf::<sha2::Sha256>::new(Some(&header.guid), &key);
46        let mut wrap_key = [0; 16];
47        let mut wrap_iv = [0; 12];
48        hk.expand("wrap key 0".as_bytes(), &mut wrap_key).unwrap();
49        hk.expand("wrap iv 0".as_bytes(), &mut wrap_iv).unwrap();
50
51        let header_size = std::mem::size_of::<ZxcryptHeader>();
52
53        match Aes128GcmSiv::new_from_slice(&wrap_key).unwrap().decrypt(
54            (&wrap_iv[..]).try_into().unwrap(),
55            Payload { msg: &wrapped_key[header_size..], aad: &wrapped_key[..header_size] },
56        ) {
57            Ok(unwrapped) => return Ok(unwrapped),
58            Err(e) => last_err = Some(e),
59        }
60    }
61    log::warn!(last_err:?, policy:%; "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_from_slice(&wrap_key)
90            .unwrap()
91            .encrypt(
92                (&wrap_iv[..]).try_into().unwrap(),
93                Payload { msg: &unwrapped_key, aad: &header.as_bytes() },
94            )
95            .unwrap();
96
97        let mut header_and_key = header.as_bytes().to_vec();
98        header_and_key.extend(wrapped);
99
100        Ok(([0; 16], header_and_key, unwrapped_key))
101    } else {
102        log::warn!("No keys sources to create zxcrypt key");
103        Err(zx::Status::INTERNAL)
104    }
105}
106
107pub async fn run_crypt_service(
108    policy: Policy,
109    mut stream: fidl_fuchsia_fxfs::CryptRequestStream,
110) -> Result<(), Error> {
111    while let Some(request) = stream.try_next().await? {
112        match request {
113            CryptRequest::CreateKey { responder, .. } => responder.send(
114                create_zxcrypt_key(policy)
115                    .await
116                    .as_ref()
117                    .map(|(id, w, u)| (id, &w[..], &u[..]))
118                    .map_err(|s| s.into_raw()),
119            )?,
120            CryptRequest::CreateKeyWithId { responder, .. } => {
121                responder.send(Err(zx::Status::BAD_PATH.into_raw()))?
122            }
123            CryptRequest::UnwrapKey { responder, wrapped_key, .. } => {
124                let response;
125                responder.send(match &wrapped_key {
126                    fidl_fuchsia_fxfs::WrappedKey::Zxcrypt(key) => {
127                        response = unwrap_zxcrypt_key(policy, key).await;
128                        match &response {
129                            Ok(v) => Ok(&v[..]),
130                            Err(e) => Err(e.into_raw()),
131                        }
132                    }
133                    _ => Err(zx::Status::INTERNAL.into_raw()),
134                })?;
135            }
136        }
137    }
138    Ok::<(), Error>(())
139}
140
141/// Runs `f` with a scoped crypt service instance.  The instance will be automatically terminated on
142/// completion.
143pub async fn with_crypt_service<R, Fut: Future<Output = Result<R, Error>>>(
144    policy: Policy,
145    f: impl FnOnce(ClientEnd<fidl_fuchsia_fxfs::CryptMarker>) -> Fut,
146) -> Result<R, Error> {
147    let (crypt, stream) = create_request_stream::<fidl_fuchsia_fxfs::CryptMarker>();
148    let mut crypt_service = pin!(async { run_crypt_service(policy, stream).await }.fuse());
149    let mut fut = pin!(f(crypt).fuse());
150
151    loop {
152        futures::select! {
153            _ = crypt_service => {}
154            result = fut => return result,
155        }
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::{ZXCRYPT_MAGIC, ZXCRYPT_VERSION, ZxcryptHeader, with_crypt_service};
162    use crypt_policy::Policy;
163    use fidl_fuchsia_fxfs::WrappedKey;
164    use zerocopy::FromBytes;
165
166    fn entropy(data: &[u8]) -> f64 {
167        let mut frequencies = [0; 256];
168        for b in data {
169            frequencies[*b as usize] += 1;
170        }
171        -frequencies
172            .into_iter()
173            .map(|f| {
174                if f > 0 {
175                    let p = f as f64 / data.len() as f64;
176                    p * p.log2()
177                } else {
178                    0.0
179                }
180            })
181            .sum::<f64>()
182            / (data.len() as f64).log2()
183    }
184
185    #[fuchsia::test]
186    async fn test_keys() {
187        with_crypt_service(Policy::Null, |crypt| async {
188            let crypt = crypt.into_proxy();
189            let (_, wrapped_key, unwrapped_key) = crypt
190                .create_key(0, fidl_fuchsia_fxfs::KeyPurpose::Data)
191                .await
192                .unwrap()
193                .expect("create_key failed");
194
195            // Check that unwrapped_key has high entropy.
196            assert!(entropy(&unwrapped_key) > 0.5);
197
198            // Check that key has the correct fields set.
199            let (header, _) = ZxcryptHeader::read_from_prefix(&wrapped_key).unwrap();
200
201            let magic = header.magic;
202            assert_eq!(magic, ZXCRYPT_MAGIC);
203            assert!(entropy(&header.guid) > 0.5);
204            let version = header.version;
205            assert_eq!(version, ZXCRYPT_VERSION);
206
207            // Check that we can unwrap the returned key.
208            let unwrapped_key2 = crypt
209                .unwrap_key(0, &WrappedKey::Zxcrypt(wrapped_key))
210                .await
211                .unwrap()
212                .expect("unwrap_key failed");
213
214            assert_eq!(unwrapped_key, unwrapped_key2);
215            Ok(())
216        })
217        .await
218        .unwrap();
219    }
220}