auths_core/crypto/ssh/
signatures.rs1use sha2::{Digest, Sha512};
4use ssh_key::private::{Ed25519Keypair as SshEd25519Keypair, KeypairData};
5use ssh_key::{HashAlg, LineEnding, PrivateKey as SshPrivateKey, SshSig};
6
7use super::CryptoError;
8use super::SecureSeed;
9use super::encoding::{encode_ssh_pubkey, encode_ssh_signature};
10
11pub fn create_sshsig(
27 seed: &SecureSeed,
28 data: &[u8],
29 namespace: &str,
30 curve: auths_crypto::CurveType,
31) -> Result<String, CryptoError> {
32 match curve {
33 auths_crypto::CurveType::Ed25519 => create_sshsig_ed25519(seed, data, namespace),
34 auths_crypto::CurveType::P256 => create_sshsig_p256(seed, data, namespace),
35 }
36}
37
38fn create_sshsig_ed25519(
39 seed: &SecureSeed,
40 data: &[u8],
41 namespace: &str,
42) -> Result<String, CryptoError> {
43 let ssh_keypair = SshEd25519Keypair::from_seed(seed.as_bytes());
44 let keypair_data = KeypairData::Ed25519(ssh_keypair);
45 let private_key = SshPrivateKey::new(keypair_data, "auths-sign")
46 .map_err(|e| CryptoError::SshKeyConstruction(e.to_string()))?;
47
48 let sshsig = SshSig::sign(&private_key, namespace, HashAlg::Sha512, data)
49 .map_err(|e| CryptoError::SigningFailed(e.to_string()))?;
50
51 sshsig
52 .to_pem(LineEnding::LF)
53 .map_err(|e| CryptoError::PemEncoding(e.to_string()))
54}
55
56fn create_sshsig_p256(
57 seed: &SecureSeed,
58 data: &[u8],
59 namespace: &str,
60) -> Result<String, CryptoError> {
61 use ssh_key::private::EcdsaKeypair;
62
63 use p256::elliptic_curve::sec1::ToEncodedPoint;
64
65 let secret_key = p256::SecretKey::from_slice(seed.as_bytes())
66 .map_err(|e| CryptoError::SigningFailed(format!("P-256 seed: {e}")))?;
67 let public_key = secret_key.public_key();
68
69 let ecdsa_keypair = EcdsaKeypair::NistP256 {
70 public: public_key.to_encoded_point(false), private: ssh_key::private::EcdsaPrivateKey::from(secret_key),
72 };
73
74 let keypair_data = KeypairData::Ecdsa(ecdsa_keypair);
75 let private_key = SshPrivateKey::new(keypair_data, "auths-sign")
76 .map_err(|e| CryptoError::SshKeyConstruction(e.to_string()))?;
77
78 let sshsig = SshSig::sign(&private_key, namespace, HashAlg::Sha256, data)
79 .map_err(|e| CryptoError::SigningFailed(e.to_string()))?;
80
81 sshsig
82 .to_pem(LineEnding::LF)
83 .map_err(|e| CryptoError::PemEncoding(e.to_string()))
84}
85
86pub fn construct_sshsig_signed_data(data: &[u8], namespace: &str) -> Result<Vec<u8>, CryptoError> {
105 let mut blob = Vec::new();
106
107 blob.extend_from_slice(b"SSHSIG");
109
110 blob.extend_from_slice(&(namespace.len() as u32).to_be_bytes());
111 blob.extend_from_slice(namespace.as_bytes());
112
113 blob.extend_from_slice(&0u32.to_be_bytes());
115
116 let hash_algo = b"sha512";
117 blob.extend_from_slice(&(hash_algo.len() as u32).to_be_bytes());
118 blob.extend_from_slice(hash_algo);
119
120 let mut hasher = Sha512::new();
121 hasher.update(data);
122 let hash = hasher.finalize();
123 blob.extend_from_slice(&(hash.len() as u32).to_be_bytes());
124 blob.extend_from_slice(&hash);
125
126 Ok(blob)
127}
128
129pub fn construct_sshsig_pem(
147 pubkey: &[u8],
148 signature: &[u8],
149 namespace: &str,
150 curve: auths_crypto::CurveType,
151) -> Result<String, CryptoError> {
152 let mut blob = Vec::new();
153
154 blob.extend_from_slice(b"SSHSIG");
155
156 blob.extend_from_slice(&1u32.to_be_bytes());
158
159 let pubkey_blob = encode_ssh_pubkey(pubkey, curve);
161 blob.extend_from_slice(&(pubkey_blob.len() as u32).to_be_bytes());
162 blob.extend_from_slice(&pubkey_blob);
163
164 blob.extend_from_slice(&(namespace.len() as u32).to_be_bytes());
166 blob.extend_from_slice(namespace.as_bytes());
167
168 blob.extend_from_slice(&0u32.to_be_bytes());
170
171 let hash_algo = b"sha512";
173 blob.extend_from_slice(&(hash_algo.len() as u32).to_be_bytes());
174 blob.extend_from_slice(hash_algo);
175
176 let sig_blob = encode_ssh_signature(signature, curve);
178 blob.extend_from_slice(&(sig_blob.len() as u32).to_be_bytes());
179 blob.extend_from_slice(&sig_blob);
180
181 let b64 = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &blob);
182
183 let wrapped: String = b64
184 .chars()
185 .collect::<Vec<_>>()
186 .chunks(70)
187 .map(|c| c.iter().collect::<String>())
188 .collect::<Vec<_>>()
189 .join("\n");
190
191 Ok(format!(
192 "-----BEGIN SSH SIGNATURE-----\n{}\n-----END SSH SIGNATURE-----\n",
193 wrapped
194 ))
195}