mod_digest/
lib.rs

1use aws_lc_rs::digest::{Algorithm as DigestAlgo, Context as DigestContext};
2use config::{any_err, from_lua_value, get_or_create_sub_module};
3use crc32fast::Hasher;
4use data_encoding::{
5    BASE32, BASE32HEX, BASE32HEX_NOPAD, BASE32_NOPAD, BASE64, BASE64URL, BASE64URL_NOPAD,
6    BASE64_NOPAD, HEXLOWER,
7};
8use data_loader::KeySource;
9use mlua::prelude::LuaUserData;
10use mlua::{Lua, MetaMethod, UserDataFields, UserDataMethods, Value, Variadic};
11
12fn digest_recursive(value: &Value, ctx: &mut DigestContext) -> anyhow::Result<()> {
13    match value {
14        Value::String(s) => {
15            ctx.update(&s.as_bytes());
16        }
17        _ => {
18            let json = serde_json::to_string(value)?;
19            ctx.update(json.as_bytes());
20        }
21    }
22    Ok(())
23}
24
25fn digest_helper(
26    algorithm: &'static DigestAlgo,
27    args: Variadic<Value>,
28) -> anyhow::Result<BinaryResult> {
29    let mut ctx = DigestContext::new(algorithm);
30    for item in args.iter() {
31        digest_recursive(item, &mut ctx)?;
32    }
33    let digest = ctx.finish();
34    Ok(BinaryResult(digest.as_ref().to_vec()))
35}
36
37pub struct BinaryResult(pub Vec<u8>);
38
39impl LuaUserData for BinaryResult {
40    fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
41        fields.add_field_method_get("hex", |_, this| Ok(HEXLOWER.encode(&this.0)));
42
43        fields.add_field_method_get("base32", |_, this| Ok(BASE32.encode(&this.0)));
44        fields.add_field_method_get("base32_nopad", |_, this| Ok(BASE32_NOPAD.encode(&this.0)));
45
46        fields.add_field_method_get("base32hex", |_, this| Ok(BASE32HEX.encode(&this.0)));
47        fields.add_field_method_get("base32hex_nopad", |_, this| {
48            Ok(BASE32HEX_NOPAD.encode(&this.0))
49        });
50
51        fields.add_field_method_get("base64", |_, this| Ok(BASE64.encode(&this.0)));
52        fields.add_field_method_get("base64_nopad", |_, this| Ok(BASE64_NOPAD.encode(&this.0)));
53
54        fields.add_field_method_get("base64url", |_, this| Ok(BASE64URL.encode(&this.0)));
55        fields.add_field_method_get("base64url_nopad", |_, this| {
56            Ok(BASE64URL_NOPAD.encode(&this.0))
57        });
58
59        fields.add_field_method_get("bytes", |lua, this| lua.create_string(&this.0));
60    }
61
62    fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
63        methods.add_meta_method(MetaMethod::ToString, |_, this, _: ()| {
64            Ok(HEXLOWER.encode(&this.0))
65        });
66    }
67}
68
69fn hmac_signer(
70    algo: aws_lc_rs::hmac::Algorithm,
71    key: &[u8],
72    msg: &[u8],
73) -> mlua::Result<BinaryResult> {
74    use aws_lc_rs::hmac::Key;
75
76    let key = Key::new(algo, key);
77    let tag = aws_lc_rs::hmac::sign(&key, msg);
78
79    Ok(BinaryResult(tag.as_ref().to_vec()))
80}
81
82pub fn register(lua: &Lua) -> anyhow::Result<()> {
83    let digest_mod = get_or_create_sub_module(lua, "digest")?;
84
85    macro_rules! digest {
86        ($func_name:literal, $algo:ident) => {
87            digest_mod.set(
88                $func_name,
89                lua.create_function(|_, args: Variadic<Value>| {
90                    digest_helper(&aws_lc_rs::digest::$algo, args).map_err(any_err)
91                })?,
92            )?;
93        };
94    }
95
96    macro_rules! hmac {
97        ($func_name:literal, $algo:ident) => {
98            digest_mod.set(
99                concat!("hmac_", $func_name),
100                lua.create_async_function(
101                    |lua, (key, msg): (mlua::Value, mlua::String)| async move {
102                        let key: KeySource = from_lua_value(&lua, key)?;
103                        let key_bytes = key.get().await.map_err(any_err)?;
104
105                        hmac_signer(aws_lc_rs::hmac::$algo, &key_bytes, &msg.as_bytes())
106                    },
107                )?,
108            )?;
109        };
110    }
111
112    digest!("sha1", SHA1_FOR_LEGACY_USE_ONLY);
113    digest!("sha224", SHA224);
114    digest!("sha256", SHA256);
115    digest!("sha384", SHA384);
116    digest!("sha3_256", SHA3_256);
117    digest!("sha3_384", SHA3_384);
118    digest!("sha3_512", SHA3_512);
119    digest!("sha512", SHA512);
120    digest!("sha512_256", SHA512_256);
121
122    hmac!("sha1", HMAC_SHA1_FOR_LEGACY_USE_ONLY);
123    hmac!("sha224", HMAC_SHA224);
124    hmac!("sha256", HMAC_SHA256);
125    hmac!("sha384", HMAC_SHA384);
126    hmac!("sha512", HMAC_SHA512);
127
128    digest_mod.set(
129        "crc32",
130        lua.create_function(|_, args: Variadic<Value>| {
131            let mut hasher = Hasher::new();
132            for item in args.iter() {
133                match item {
134                    Value::String(s) => {
135                        hasher.update(&s.as_bytes());
136                    }
137                    _ => {
138                        let json = serde_json::to_string(item).map_err(any_err)?;
139                        hasher.update(json.as_bytes());
140                    }
141                }
142            }
143            let crc = hasher.finalize();
144            Ok(BinaryResult(crc.to_be_bytes().to_vec()))
145        })?,
146    )?;
147    Ok(())
148}