wasmtime_vfs_keyfs/
lib.rs

1use std::sync::Arc;
2
3use generate::Generate;
4use trust::Trust;
5
6use wasi_common::Error;
7use wasmtime_vfs_dir::Directory;
8use wasmtime_vfs_memory::Node;
9
10mod generate;
11mod share;
12mod sign;
13mod trust;
14mod verify;
15
16pub const RS256: &[u8] = b"\x00\x00\x00\x00";
17pub const RS384: &[u8] = b"\x00\x00\x00\x01";
18pub const RS512: &[u8] = b"\x00\x00\x00\x02";
19pub const PS256: &[u8] = b"\x00\x00\x00\x03";
20pub const PS384: &[u8] = b"\x00\x00\x00\x04";
21pub const PS512: &[u8] = b"\x00\x00\x00\x05";
22pub const ES256K: &[u8] = b"\x00\x00\x00\x06";
23pub const ES256: &[u8] = b"\x00\x00\x00\x07";
24pub const ES384: &[u8] = b"\x00\x00\x00\x08";
25pub const ES512: &[u8] = b"\x00\x00\x00\x09";
26
27pub async fn new(parent: Arc<dyn Node>) -> Result<Arc<dyn Node>, Error> {
28    let dir = Directory::device(parent, None);
29    dir.attach("generate", Generate::new(dir.clone())).await?;
30    dir.attach("trust", Trust::new(dir.clone())).await?;
31    Ok(dir)
32}
33
34#[cfg(test)]
35mod test {
36    use std::io::{IoSlice, IoSliceMut};
37
38    use signature::{Signature, Signer, Verifier};
39    use uuid::Uuid;
40    use wasi_common::file::{FdFlags, OFlags};
41    use wasi_common::{Error, ErrorKind, WasiDir, WasiFile};
42    use wasmtime_vfs_ledger::Ledger;
43
44    use super::*;
45
46    async fn root(ledger: Arc<Ledger>) -> Result<Arc<dyn Node>, Error> {
47        let dir = Directory::root(ledger, None);
48        dir.attach("generate", Generate::new(dir.clone())).await?;
49        dir.attach("trust", Trust::new(dir.clone())).await?;
50        Ok(dir)
51    }
52
53    async fn open_file(
54        dir: &dyn WasiDir,
55        path: &str,
56        read: bool,
57        write: bool,
58    ) -> Box<dyn WasiFile> {
59        dir.open_file(false, path, OFlags::empty(), read, write, FdFlags::empty())
60            .await
61            .unwrap()
62    }
63
64    async fn write(file: &mut dyn WasiFile, bytes: &[&[u8]], last: bool) -> Result<(), Error> {
65        let slice = bytes.iter().map(|x| IoSlice::new(x)).collect::<Vec<_>>();
66
67        let written = match last {
68            false => file.write_vectored(&slice).await?,
69            true => file.write_vectored_at(&slice, u64::MAX).await?,
70        };
71
72        assert_eq!(written, bytes.iter().map(|x| x.len()).sum::<usize>() as u64);
73        Ok(())
74    }
75
76    async fn read<const N: usize>(file: &mut dyn WasiFile, last: bool) -> [u8; N] {
77        let mut array = [0u8; N];
78        let mut slice = [IoSliceMut::new(&mut array)];
79        let read = match last {
80            false => file.read_vectored(&mut slice).await.unwrap(),
81            true => file.read_vectored_at(&mut slice, u64::MAX).await.unwrap(),
82        };
83        assert_eq!(read, N as u64);
84        array
85    }
86
87    #[tokio::test]
88    async fn sign() {
89        let keys = root(Ledger::new()).await.unwrap().open_dir().await.unwrap();
90
91        // Generate a key.
92        let mut generate = open_file(&*keys, "generate", true, true).await;
93        write(&mut *generate, &[ES256], false).await.unwrap();
94
95        // Read the UUID.
96        let uuid: [u8; 36] = read(&mut *generate, false).await;
97        let uuid: Uuid = std::str::from_utf8(&uuid).unwrap().parse().unwrap();
98
99        // Ensure the new key appears in the directory listing.
100        keys.readdir(0.into())
101            .await
102            .unwrap()
103            .map(|e| e.unwrap().name)
104            .find(|x| &uuid.as_hyphenated().to_string() == x)
105            .unwrap();
106
107        // Open the share file.
108        let mut share = open_file(&*keys, &format!("{}/share", uuid), true, false).await;
109
110        // Export the public key.
111        let pubkey: [u8; 69] = read(&mut *share, false).await;
112        let pubkey = p256::PublicKey::from_sec1_bytes(&pubkey[4..]).unwrap();
113
114        // Open the sign socket.
115        let mut sign = open_file(&*keys, &format!("{}/sign", uuid), true, true).await;
116
117        // Sign the message.
118        write(&mut *sign, &[b"foo"], false).await.unwrap();
119        let signature: [u8; 64] = read(&mut *sign, true).await;
120
121        // Verify the signature.
122        let vkey = p256::ecdsa::VerifyingKey::from(pubkey);
123        let sig = p256::ecdsa::Signature::from_bytes(&signature).unwrap();
124        vkey.verify(b"foo", &sig).unwrap();
125    }
126
127    #[tokio::test]
128    async fn verify() {
129        let sk = p256::ecdsa::SigningKey::random(&mut rand::thread_rng());
130        let ep = p256::ecdsa::VerifyingKey::from(&sk).to_encoded_point(false);
131        let mut sig = sk.sign(b"foo").as_bytes().to_vec();
132
133        let keys = root(Ledger::new()).await.unwrap().open_dir().await.unwrap();
134
135        // Trust the key.
136        let mut trust = open_file(&*keys, "trust", true, true).await;
137        write(&mut *trust, &[ES256, ep.as_bytes()], false)
138            .await
139            .unwrap();
140
141        // Read the UUID.
142        let uuid: [u8; 36] = read(&mut *trust, false).await;
143        let uuid: Uuid = std::str::from_utf8(&uuid).unwrap().parse().unwrap();
144
145        // Ensure the new key appears in the directory listing.
146        keys.readdir(0.into())
147            .await
148            .unwrap()
149            .map(|e| e.unwrap().name)
150            .find(|x| &uuid.as_hyphenated().to_string() == x)
151            .unwrap();
152
153        // Open the verify socket.
154        let mut verify = open_file(&*keys, &format!("{}/verify", uuid), false, true).await;
155
156        // Verify the message.
157        write(&mut *verify, &[b"foo"], false).await.unwrap();
158        write(&mut *verify, &[&sig], true).await.unwrap();
159
160        // Check that a bad signature fails.
161        sig[0] += 1;
162        write(&mut *verify, &[b"foo"], false).await.unwrap();
163        let error = write(&mut *verify, &[&sig], true).await.unwrap_err();
164
165        // Work around the lack of Eq on ErrorKind.
166        // See: https://github.com/bytecodealliance/wasmtime/pull/5006
167        assert_eq!(
168            std::mem::discriminant(&ErrorKind::Ilseq),
169            std::mem::discriminant(&error.downcast::<ErrorKind>().unwrap())
170        );
171    }
172
173    #[tokio::test]
174    async fn remove() {
175        let keys = root(Ledger::new()).await.unwrap().open_dir().await.unwrap();
176
177        // Generate a key.
178        let mut generate = open_file(&*keys, "generate", true, true).await;
179        write(&mut *generate, &[ES256], false).await.unwrap();
180
181        // Read the UUID.
182        let uuid: [u8; 36] = read(&mut *generate, false).await;
183        let uuid: Uuid = std::str::from_utf8(&uuid).unwrap().parse().unwrap();
184
185        // Ensure the new key appears in the directory listing.
186        keys.readdir(0.into())
187            .await
188            .unwrap()
189            .map(|e| e.unwrap().name)
190            .find(|x| &uuid.as_hyphenated().to_string() == x)
191            .unwrap();
192
193        // Remove the key.
194        keys.remove_dir(&uuid.to_string()).await.unwrap();
195
196        // Ensure the key does not appear in the directory listing.
197        let found = keys
198            .readdir(0.into())
199            .await
200            .unwrap()
201            .map(|e| e.unwrap().name)
202            .any(|x| uuid.as_hyphenated().to_string() == x);
203        assert!(!found);
204    }
205}