1use ring::signature;
2use snafu::ResultExt;
3
4pub enum PrivateKey {
6 PEM(String),
8 DER(Vec<u8>),
10 Raw(Vec<u8>),
12}
13
14impl PrivateKey {
15 pub fn read(&self) -> crate::Result<PrivateKeyBytes> {
21 Ok(match self {
22 Self::PEM(s) => {
23 let p = pem::parse(s).context(crate::InvalidPemSnafu)?;
24 PrivateKeyBytes::PKCS8v1v2(p.contents)
25 }
26 Self::DER(b) => PrivateKeyBytes::PKCS8v1v2(b.clone()),
27 Self::Raw(b) => PrivateKeyBytes::PKCS8v2(b.clone()),
28 })
29 }
30}
31
32pub enum PrivateKeyBytes {
34 PKCS8v1v2(Vec<u8>),
36 PKCS8v2(Vec<u8>),
38}
39
40pub enum PublicKey {
42 PEM(String),
44 DER(Vec<u8>),
46 Raw(Vec<u8>),
48}
49
50impl PublicKey {
51 pub fn read(&self) -> crate::Result<PublicKeyBytes> {
57 Ok(PublicKeyBytes::Raw(match self {
58 Self::PEM(s) => {
59 let p = pem::parse(s).context(crate::InvalidPemSnafu)?;
60 p.contents.iter().skip(12).copied().collect::<Vec<_>>()
61 }
62 Self::DER(bs) => bs.iter().skip(12).copied().collect::<Vec<_>>(),
63 Self::Raw(bs) => bs.clone(),
64 }))
65 }
66}
67
68pub enum PublicKeyBytes {
70 Raw(Vec<u8>),
72}
73
74pub fn verify(msg: &[u8], sig: &[u8], pubkey: &PublicKeyBytes) -> crate::Result<()> {
80 let PublicKeyBytes::Raw(key_bytes) = pubkey;
84
85 let peer_public_key = signature::UnparsedPublicKey::new(&signature::ED25519, &key_bytes);
86 peer_public_key
87 .verify(msg, sig)
88 .context(crate::SignatureFailedSnafu)?;
89
90 Ok(())
91}
92
93pub fn sign(msg: &[u8], prkey: &PrivateKeyBytes) -> crate::Result<Vec<u8>> {
99 let key_pair = match prkey {
100 PrivateKeyBytes::PKCS8v1v2(b) => {
101 signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(b).context(crate::InvalidKeySnafu)
102 }
103 PrivateKeyBytes::PKCS8v2(b) => {
104 signature::Ed25519KeyPair::from_pkcs8(b).context(crate::InvalidKeySnafu)
105 }
106 }?;
107
108 let sig = key_pair.sign(msg);
109 Ok(sig.as_ref().to_vec())
110}
111
112#[cfg(feature = "base64")]
118pub fn sign_base64(msg: &[u8], privkey: &PrivateKeyBytes) -> crate::Result<String> {
119 use base64::{prelude::BASE64_STANDARD, Engine};
120 let sig = sign(msg, privkey)?;
121 Ok(BASE64_STANDARD.encode(sig))
122}
123
124#[cfg(feature = "base64")]
130pub fn verify_base64(msg: &[u8], sig: &str, pubkey: &PublicKeyBytes) -> crate::Result<()> {
131 use base64::{prelude::BASE64_STANDARD, Engine};
132 let sig = BASE64_STANDARD
133 .decode(sig)
134 .context(crate::DecodeFailedSnafu)?;
135 verify(msg, &sig, pubkey)
136}
137
138#[cfg(test)]
139mod tests {
140 use ring::{rand, signature::KeyPair};
141
142 use super::*;
143 use std::{fs, path::Path, process::Command};
144
145 #[test]
146 fn test_negatives() {
147 let privkey = PrivateKey::PEM(
148 String::from_utf8_lossy(&fs::read("fixtures/ed.private.pem").unwrap()).to_string(),
149 )
150 .read()
151 .unwrap();
152 let sig = sign(b"hello world", &privkey).expect("should sign");
153 let pubkey = PublicKey::PEM(
154 String::from_utf8_lossy(&fs::read("fixtures/ed.public.pem").unwrap()).to_string(),
155 );
156
157 verify(b"hello world_", &sig, &pubkey.read().unwrap()).expect_err("should fail");
158
159 let pubkey = PublicKey::PEM(
161 String::from_utf8_lossy(&fs::read("fixtures/rsa.public.pem").unwrap()).to_string(),
162 );
163 verify(b"hello world", &sig, &pubkey.read().unwrap()).expect_err("should fail");
164
165 let pubkey = PublicKey::DER(fs::read("fixtures/ed.public.pem").unwrap());
167 verify(b"hello world", &sig, &pubkey.read().unwrap()).expect_err("should fail");
168 }
169
170 #[test]
171 fn test_pem() {
172 let privkey = PrivateKey::PEM(
173 String::from_utf8_lossy(&fs::read("fixtures/ed.private.pem").unwrap()).to_string(),
174 )
175 .read()
176 .unwrap();
177 let sig = sign(b"hello world", &privkey).expect("should sign");
178 let pubkey = PublicKey::PEM(
179 String::from_utf8_lossy(&fs::read("fixtures/ed.public.pem").unwrap()).to_string(),
180 );
181 verify(b"hello world", &sig, &pubkey.read().unwrap()).expect("should verify");
182 }
183
184 #[cfg(feature = "base64")]
185 #[test]
186 fn test_base64() {
187 let privkey = PrivateKey::PEM(
188 String::from_utf8_lossy(&fs::read("fixtures/ed.private.pem").unwrap()).to_string(),
189 )
190 .read()
191 .unwrap();
192 let sig = sign_base64(b"hello world", &privkey).expect("should sign");
193 let pubkey = PublicKey::PEM(
194 String::from_utf8_lossy(&fs::read("fixtures/ed.public.pem").unwrap()).to_string(),
195 );
196 verify_base64(b"hello world", &sig, &pubkey.read().unwrap()).expect("should verify");
197 }
198
199 #[test]
200 fn test_pkcs8v2() {
201 let rng = rand::SystemRandom::new();
203 let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng)
204 .unwrap()
205 .as_ref()
206 .to_vec();
207 let key_pair = signature::Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref()).unwrap();
208
209 let sig = sign(
210 b"hello world",
211 &PrivateKey::Raw(pkcs8_bytes).read().unwrap(),
212 )
213 .expect("should sign");
214 let pubkey = PublicKey::Raw(key_pair.public_key().as_ref().to_vec());
215 verify(b"hello world", &sig, &pubkey.read().unwrap()).expect("should verify");
216 }
217
218 #[test]
219 fn test_nodejs_signed() {
220 let sig = fs::read("fixtures/ed.sign-me.txt.nodejs-sig").expect("file should exist");
221 let pubkey = PublicKey::PEM(
222 String::from_utf8_lossy(&fs::read("fixtures/ed.public.pem").unwrap()).to_string(),
223 );
224 let msg = fs::read("fixtures/sign-me.txt").expect("file should exist");
225 verify(&msg, &sig, &pubkey.read().unwrap()).expect("should verify");
226 }
227
228 #[test]
229 fn test_nodejs_verified() {
230 let privkey = PrivateKey::PEM(
231 String::from_utf8_lossy(&fs::read("fixtures/ed.private.pem").unwrap()).to_string(),
232 )
233 .read()
234 .unwrap();
235 let msg = fs::read("fixtures/sign-me.txt").expect("file should exist");
236 let sig = sign(&msg, &privkey).expect("should sign");
237
238 let sigfile = "fixtures/ed.sign-me.txt.ring-sig";
239 if Path::new(sigfile).exists() {
240 fs::remove_file(sigfile).expect("should remove");
241 }
242 fs::write(sigfile, sig).expect("should write sig file");
243
244 let out = Command::new("node")
246 .args(["fixtures/verify.js"])
247 .output()
248 .expect("failed to execute process");
249 let ok = String::from_utf8_lossy(&out.stdout);
250
251 println!("ok: {ok}");
252 assert!(ok.contains("OK"));
253 }
254}