doge_runtime/stdlib/
crypto.rs1use hmac::{Hmac, Mac};
10use sha2::{Digest, Sha256};
11use subtle::ConstantTimeEq;
12
13use crate::error::{DogeError, DogeResult};
14use crate::stdlib::int_arg;
15use crate::value::Value;
16
17fn str_or_bytes<'a>(fname: &str, v: &'a Value) -> DogeResult<&'a [u8]> {
20 match v {
21 Value::Str(s) => Ok(s.as_bytes()),
22 Value::Bytes(b) => Ok(b),
23 _ => Err(DogeError::type_error(format!(
24 "crypto.{fname} needs a Str or Bytes, got {}",
25 v.describe()
26 ))),
27 }
28}
29
30pub fn crypto_sha256(data: &Value) -> DogeResult {
31 let digest = Sha256::digest(str_or_bytes("sha256", data)?);
32 Ok(Value::bytes(digest))
33}
34
35pub fn crypto_hmac_sha256(key: &Value, data: &Value) -> DogeResult {
36 let key = str_or_bytes("hmac_sha256", key)?;
37 let data = str_or_bytes("hmac_sha256", data)?;
38 let mut mac = Hmac::<Sha256>::new_from_slice(key)
39 .map_err(|_| DogeError::value_error("crypto.hmac_sha256 could not build the HMAC key"))?;
40 mac.update(data);
41 Ok(Value::bytes(mac.finalize().into_bytes()))
42}
43
44pub fn crypto_token(n: &Value) -> DogeResult {
45 let n = int_arg("crypto", "token", n)?;
46 if n <= 0 {
47 return Err(DogeError::value_error(format!(
48 "crypto.token needs a positive length, got {n}"
49 )));
50 }
51 let mut buf = vec![0u8; n as usize];
52 getrandom::getrandom(&mut buf)
53 .map_err(|_| DogeError::io_error("crypto.token could not read the OS random source"))?;
54 Ok(Value::bytes(buf))
55}
56
57pub fn crypto_same(a: &Value, b: &Value) -> DogeResult {
58 let a = str_or_bytes("same", a)?;
59 let b = str_or_bytes("same", b)?;
60 let equal = a.len() == b.len() && bool::from(a.ct_eq(b));
61 Ok(Value::Bool(equal))
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::error::ErrorKind;
68
69 fn hex(v: &Value) -> String {
70 match v {
71 Value::Bytes(b) => b.iter().map(|byte| format!("{byte:02x}")).collect(),
72 _ => panic!("expected Bytes"),
73 }
74 }
75
76 fn as_bool(v: &Value) -> bool {
77 match v {
78 Value::Bool(b) => *b,
79 _ => panic!("expected Bool"),
80 }
81 }
82
83 #[test]
84 fn sha256_matches_known_vector() {
85 let empty = crypto_sha256(&Value::str("")).unwrap();
86 assert_eq!(
87 hex(&empty),
88 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
89 );
90 let abc = crypto_sha256(&Value::str("abc")).unwrap();
91 assert_eq!(
92 hex(&abc),
93 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
94 );
95 }
96
97 #[test]
98 fn sha256_str_and_bytes_agree() {
99 let from_str = crypto_sha256(&Value::str("doge")).unwrap();
100 let from_bytes = crypto_sha256(&Value::bytes(b"doge")).unwrap();
101 assert_eq!(hex(&from_str), hex(&from_bytes));
102 }
103
104 #[test]
105 fn hmac_sha256_matches_rfc4231_vector() {
106 let mac = crypto_hmac_sha256(
108 &Value::str("Jefe"),
109 &Value::str("what do ya want for nothing?"),
110 )
111 .unwrap();
112 assert_eq!(
113 hex(&mac),
114 "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
115 );
116 }
117
118 #[test]
119 fn token_has_requested_length_and_varies() {
120 let a = crypto_token(&Value::int(16)).unwrap();
121 let b = crypto_token(&Value::int(16)).unwrap();
122 match (&a, &b) {
123 (Value::Bytes(x), Value::Bytes(y)) => {
124 assert_eq!(x.len(), 16);
125 assert_eq!(y.len(), 16);
126 assert_ne!(x, y, "two secure draws must differ");
127 }
128 _ => panic!("expected Bytes"),
129 }
130 }
131
132 #[test]
133 fn token_rejects_non_positive_length() {
134 assert_eq!(
135 crypto_token(&Value::int(0)).unwrap_err().kind,
136 ErrorKind::ValueError
137 );
138 assert_eq!(
139 crypto_token(&Value::int(-1)).unwrap_err().kind,
140 ErrorKind::ValueError
141 );
142 }
143
144 #[test]
145 fn same_compares_content_and_accepts_str_or_bytes() {
146 assert!(as_bool(
147 &crypto_same(&Value::str("secret"), &Value::str("secret")).unwrap()
148 ));
149 assert!(!as_bool(
150 &crypto_same(&Value::str("secret"), &Value::str("secreT")).unwrap()
151 ));
152 assert!(!as_bool(
153 &crypto_same(&Value::str("ab"), &Value::str("abc")).unwrap()
154 ));
155 assert!(as_bool(
156 &crypto_same(&Value::str("hi"), &Value::bytes(b"hi")).unwrap()
157 ));
158 }
159
160 #[test]
161 fn wrong_types_are_type_errors() {
162 assert_eq!(
163 crypto_sha256(&Value::int(123)).unwrap_err().kind,
164 ErrorKind::TypeError
165 );
166 assert_eq!(
167 crypto_hmac_sha256(&Value::int(1), &Value::str("x"))
168 .unwrap_err()
169 .kind,
170 ErrorKind::TypeError
171 );
172 assert_eq!(
173 crypto_same(&Value::int(1), &Value::str("x"))
174 .unwrap_err()
175 .kind,
176 ErrorKind::TypeError
177 );
178 assert_eq!(
179 crypto_token(&Value::str("16")).unwrap_err().kind,
180 ErrorKind::TypeError
181 );
182 }
183}