nautilus_cryptography/
signing.rs1use aws_lc_rs::{hmac, rand as lc_rand, rsa::KeyPair, signature as lc_signature};
17use base64::prelude::*;
18use ed25519_dalek::{Signature as Ed25519Signature, Signer, SigningKey};
19use hex;
20
21pub fn hmac_signature(secret: &str, data: &str) -> anyhow::Result<String> {
31 let key = hmac::Key::new(hmac::HMAC_SHA256, secret.as_bytes());
32 let tag = hmac::sign(&key, data.as_bytes());
33 Ok(hex::encode(tag.as_ref()))
34}
35
36pub fn rsa_signature(private_key_pem: &str, data: &str) -> anyhow::Result<String> {
45 if data.is_empty() {
46 anyhow::bail!("Query string cannot be empty");
47 }
48
49 let pem = pem::parse(private_key_pem.trim())
51 .map_err(|e| anyhow::anyhow!("Failed to parse PEM: {e}"))?;
52
53 if !pem.tag().ends_with("PRIVATE KEY") {
55 anyhow::bail!("PEM does not contain a private key");
56 }
57
58 let key_pair = KeyPair::from_pkcs8(pem.contents())
60 .map_err(|_| anyhow::anyhow!("Failed to decode RSA private key"))?;
61
62 let rng = lc_rand::SystemRandom::new();
64 let mut signature = vec![0u8; key_pair.public_modulus_len()];
65
66 key_pair
67 .sign(
68 &lc_signature::RSA_PKCS1_SHA256,
69 &rng,
70 data.as_bytes(),
71 &mut signature,
72 )
73 .map_err(|_| anyhow::anyhow!("Failed to generate RSA signature"))?;
74
75 Ok(BASE64_STANDARD.encode(signature))
76}
77
78pub fn ed25519_signature(private_key: &[u8], data: &str) -> anyhow::Result<String> {
84 let signing_key = SigningKey::from_bytes(
85 private_key
86 .try_into()
87 .map_err(|_| anyhow::anyhow!("Invalid Ed25519 private key length"))?,
88 );
89 let signature: Ed25519Signature = signing_key.sign(data.as_bytes());
90 Ok(BASE64_STANDARD.encode(signature.to_bytes()))
91}
92
93#[cfg(test)]
94mod tests {
95 use rstest::rstest;
96
97 use super::*;
98
99 #[rstest]
100 #[case(
101 "mysecretkey",
102 "data-to-sign",
103 "19ed21a8b2a6b847d7d7aea059ab3134cd58f13c860cfbe89338c718685fe077"
104 )]
105 #[case(
106 "anothersecretkey",
107 "somedata",
108 "fb44dab41435775b44a96aa008af58cbf1fa1cea32f4605562c586b98f7326c5"
109 )]
110 #[case(
111 "",
112 "data-without-secret",
113 "740c92f9c332fbb22d80aa6a3c9c10197a3e9dc61ca7e3c298c21597e4672133"
114 )]
115 #[case(
116 "mysecretkey",
117 "",
118 "bb4e89236de3b03c17e36d48ca059fa277b88165cb14813a49f082ed8974b9f4"
119 )]
120 #[case(
121 "",
122 "",
123 "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"
124 )]
125 fn test_hmac_signature(
126 #[case] secret: &str,
127 #[case] data: &str,
128 #[case] expected_signature: &str,
129 ) {
130 let result = hmac_signature(secret, data).unwrap();
131 assert_eq!(
132 result, expected_signature,
133 "Expected signature did not match"
134 );
135 }
136
137 #[rstest]
138 #[case(
139 r"-----BEGIN TEST KEY-----
140MIIBVwIBADANBgkqhkiG9w0BAQEFAASCATswggE3AgEAAkEAu/...
141-----END PRIVATE KEY-----",
142 ""
143 )]
144 fn test_rsa_signature_empty_query(#[case] private_key_pem: &str, #[case] query_string: &str) {
145 let result = rsa_signature(private_key_pem, query_string);
146 assert!(
147 result.is_err(),
148 "Expected an error with empty query string, but got Ok"
149 );
150 }
151
152 #[rstest]
153 #[case(
154 r"-----BEGIN INVALID KEY-----
155INVALID_KEY_DATA
156-----END INVALID KEY-----",
157 "This is a test query"
158 )]
159 fn test_rsa_signature_invalid_key(#[case] private_key_pem: &str, #[case] query_string: &str) {
160 let result = rsa_signature(private_key_pem, query_string);
161 assert!(
162 result.is_err(),
163 "Expected an error due to invalid key, but got Ok"
164 );
165 }
166
167 const fn valid_ed25519_private_key() -> [u8; 32] {
168 [
169 0x0c, 0x74, 0x18, 0x92, 0x6b, 0x5d, 0xe9, 0x8f, 0xe2, 0xb6, 0x47, 0x8a, 0x51, 0xf9,
170 0x97, 0x31, 0x9a, 0xcd, 0x2d, 0xbc, 0xf9, 0x94, 0xea, 0x8f, 0xc3, 0x1b, 0x65, 0x24,
171 0x1f, 0x91, 0xd8, 0x6f,
172 ]
173 }
174
175 #[rstest]
176 #[case(valid_ed25519_private_key(), "This is a test query")]
177 #[case(valid_ed25519_private_key(), "")]
178 fn test_ed25519_signature(#[case] private_key_bytes: [u8; 32], #[case] query_string: &str) {
179 let result = ed25519_signature(&private_key_bytes, query_string);
180 assert!(
181 result.is_ok(),
182 "Expected valid signature but got an error: {result:?}"
183 );
184 assert!(!result.unwrap().is_empty(), "Signature should not be empty");
185 }
186}