nautilus_cryptography/
signing.rs1use base64::prelude::*;
17use hex;
18use ring::{
19 hmac,
20 rand::SystemRandom,
21 signature::{Ed25519KeyPair, RSA_PKCS1_SHA256, RsaKeyPair, Signature},
22};
23
24#[must_use]
25pub fn hmac_signature(secret: &str, data: &str) -> String {
26 let key = hmac::Key::new(hmac::HMAC_SHA256, secret.as_bytes());
27 let signature = hmac::sign(&key, data.as_bytes());
28 hex::encode(signature.as_ref())
29}
30
31pub fn rsa_signature(private_key_pem: &str, data: &str) -> anyhow::Result<String> {
40 if data.is_empty() {
41 anyhow::bail!("Query string cannot be empty");
42 }
43
44 let pem = pem::parse(private_key_pem)?;
45 let private_key =
46 RsaKeyPair::from_pkcs8(pem.contents()).map_err(|e| anyhow::anyhow!("{e:?}"))?;
47 let mut signature = vec![0; private_key.public().modulus_len()];
48 let rng = SystemRandom::new();
49
50 private_key
51 .sign(&RSA_PKCS1_SHA256, &rng, data.as_bytes(), &mut signature)
52 .map_err(|e| anyhow::anyhow!("{e:?}"))?;
53
54 Ok(BASE64_STANDARD.encode(&signature))
55}
56
57pub fn ed25519_signature(private_key: &[u8], data: &str) -> anyhow::Result<String> {
63 let key_pair =
64 Ed25519KeyPair::from_seed_unchecked(private_key).map_err(|e| anyhow::anyhow!("{e:?}"))?;
65 let signature: Signature = key_pair.sign(data.as_bytes());
66 Ok(hex::encode(signature.as_ref()))
67}
68
69#[cfg(test)]
73mod tests {
74 use rstest::rstest;
75
76 use super::*;
77
78 #[rstest]
79 #[case(
80 "mysecretkey",
81 "data-to-sign",
82 "19ed21a8b2a6b847d7d7aea059ab3134cd58f13c860cfbe89338c718685fe077"
83 )]
84 #[case(
85 "anothersecretkey",
86 "somedata",
87 "fb44dab41435775b44a96aa008af58cbf1fa1cea32f4605562c586b98f7326c5"
88 )]
89 #[case(
90 "",
91 "data-without-secret",
92 "740c92f9c332fbb22d80aa6a3c9c10197a3e9dc61ca7e3c298c21597e4672133"
93 )]
94 #[case(
95 "mysecretkey",
96 "",
97 "bb4e89236de3b03c17e36d48ca059fa277b88165cb14813a49f082ed8974b9f4"
98 )]
99 #[case(
100 "",
101 "",
102 "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"
103 )]
104 fn test_hmac_signature(
105 #[case] secret: &str,
106 #[case] data: &str,
107 #[case] expected_signature: &str,
108 ) {
109 let result = hmac_signature(secret, data);
110 assert_eq!(
111 result, expected_signature,
112 "Expected signature did not match"
113 );
114 }
115
116 #[rstest]
117 #[case(
118 r"-----BEGIN TEST KEY-----
119MIIBVwIBADANBgkqhkiG9w0BAQEFAASCATswggE3AgEAAkEAu/...
120-----END PRIVATE KEY-----",
121 ""
122 )]
123 fn test_rsa_signature_empty_query(#[case] private_key_pem: &str, #[case] query_string: &str) {
124 let result = rsa_signature(private_key_pem, query_string);
125 assert!(
126 result.is_err(),
127 "Expected an error with empty query string, but got Ok"
128 );
129 }
130
131 #[rstest]
132 #[case(
133 r"-----BEGIN INVALID KEY-----
134INVALID_KEY_DATA
135-----END INVALID KEY-----",
136 "This is a test query"
137 )]
138 fn test_rsa_signature_invalid_key(#[case] private_key_pem: &str, #[case] query_string: &str) {
139 let result = rsa_signature(private_key_pem, query_string);
140 assert!(
141 result.is_err(),
142 "Expected an error due to invalid key, but got Ok"
143 );
144 }
145
146 const fn valid_ed25519_private_key() -> [u8; 32] {
147 [
148 0x0c, 0x74, 0x18, 0x92, 0x6b, 0x5d, 0xe9, 0x8f, 0xe2, 0xb6, 0x47, 0x8a, 0x51, 0xf9,
149 0x97, 0x31, 0x9a, 0xcd, 0x2d, 0xbc, 0xf9, 0x94, 0xea, 0x8f, 0xc3, 0x1b, 0x65, 0x24,
150 0x1f, 0x91, 0xd8, 0x6f,
151 ]
152 }
153
154 #[rstest]
155 #[case(valid_ed25519_private_key(), "This is a test query")]
156 #[case(valid_ed25519_private_key(), "")]
157 fn test_ed25519_signature(#[case] private_key_bytes: [u8; 32], #[case] query_string: &str) {
158 let result = ed25519_signature(&private_key_bytes, query_string);
159 assert!(
160 result.is_ok(),
161 "Expected valid signature but got an error: {result:?}"
162 );
163 assert!(!result.unwrap().is_empty(), "Signature should not be empty");
164 }
165}