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(hex::encode(signature.to_bytes()))
91}
92
93#[cfg(test)]
97mod tests {
98 use rstest::rstest;
99
100 use super::*;
101
102 #[rstest]
103 #[case(
104 "mysecretkey",
105 "data-to-sign",
106 "19ed21a8b2a6b847d7d7aea059ab3134cd58f13c860cfbe89338c718685fe077"
107 )]
108 #[case(
109 "anothersecretkey",
110 "somedata",
111 "fb44dab41435775b44a96aa008af58cbf1fa1cea32f4605562c586b98f7326c5"
112 )]
113 #[case(
114 "",
115 "data-without-secret",
116 "740c92f9c332fbb22d80aa6a3c9c10197a3e9dc61ca7e3c298c21597e4672133"
117 )]
118 #[case(
119 "mysecretkey",
120 "",
121 "bb4e89236de3b03c17e36d48ca059fa277b88165cb14813a49f082ed8974b9f4"
122 )]
123 #[case(
124 "",
125 "",
126 "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"
127 )]
128 fn test_hmac_signature(
129 #[case] secret: &str,
130 #[case] data: &str,
131 #[case] expected_signature: &str,
132 ) {
133 let result = hmac_signature(secret, data).unwrap();
134 assert_eq!(
135 result, expected_signature,
136 "Expected signature did not match"
137 );
138 }
139
140 #[rstest]
141 #[case(
142 r"-----BEGIN TEST KEY-----
143MIIBVwIBADANBgkqhkiG9w0BAQEFAASCATswggE3AgEAAkEAu/...
144-----END PRIVATE KEY-----",
145 ""
146 )]
147 fn test_rsa_signature_empty_query(#[case] private_key_pem: &str, #[case] query_string: &str) {
148 let result = rsa_signature(private_key_pem, query_string);
149 assert!(
150 result.is_err(),
151 "Expected an error with empty query string, but got Ok"
152 );
153 }
154
155 #[rstest]
156 #[case(
157 r"-----BEGIN INVALID KEY-----
158INVALID_KEY_DATA
159-----END INVALID KEY-----",
160 "This is a test query"
161 )]
162 fn test_rsa_signature_invalid_key(#[case] private_key_pem: &str, #[case] query_string: &str) {
163 let result = rsa_signature(private_key_pem, query_string);
164 assert!(
165 result.is_err(),
166 "Expected an error due to invalid key, but got Ok"
167 );
168 }
169
170 const fn valid_ed25519_private_key() -> [u8; 32] {
171 [
172 0x0c, 0x74, 0x18, 0x92, 0x6b, 0x5d, 0xe9, 0x8f, 0xe2, 0xb6, 0x47, 0x8a, 0x51, 0xf9,
173 0x97, 0x31, 0x9a, 0xcd, 0x2d, 0xbc, 0xf9, 0x94, 0xea, 0x8f, 0xc3, 0x1b, 0x65, 0x24,
174 0x1f, 0x91, 0xd8, 0x6f,
175 ]
176 }
177
178 #[rstest]
179 #[case(valid_ed25519_private_key(), "This is a test query")]
180 #[case(valid_ed25519_private_key(), "")]
181 fn test_ed25519_signature(#[case] private_key_bytes: [u8; 32], #[case] query_string: &str) {
182 let result = ed25519_signature(&private_key_bytes, query_string);
183 assert!(
184 result.is_ok(),
185 "Expected valid signature but got an error: {result:?}"
186 );
187 assert!(!result.unwrap().is_empty(), "Signature should not be empty");
188 }
189}