Cindy Lin | 6ec3c2b | 2024-05-16 07:39:23 +0000 | [diff] [blame] | 1 | // Copyright 2024, The Android Open Source Project |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | //! Edwards-curve digital signature algorithm. |
| 16 | |
| 17 | use bssl_crypto::{ed25519, InvalidSignatureError}; |
| 18 | use mls_rs_core::crypto::{CipherSuite, SignaturePublicKey, SignatureSecretKey}; |
| 19 | use mls_rs_crypto_traits::Curve; |
| 20 | |
| 21 | use core::array::TryFromSliceError; |
| 22 | use thiserror::Error; |
| 23 | |
| 24 | /// Errors returned from EdDSA. |
| 25 | #[derive(Debug, Error)] |
| 26 | pub enum EdDsaError { |
| 27 | /// Error returned when conversion from slice to array fails. |
| 28 | #[error(transparent)] |
| 29 | TryFromSliceError(#[from] TryFromSliceError), |
| 30 | /// Error returned on an invalid signature. |
| 31 | #[error("invalid signature")] |
| 32 | InvalidSig(InvalidSignatureError), |
| 33 | /// Error returned when the private key length is invalid. |
| 34 | #[error("EdDSA private key of invalid length {len}, expected length {expected_len}")] |
| 35 | InvalidPrivKeyLen { |
| 36 | /// Invalid key length. |
| 37 | len: usize, |
| 38 | /// Expected key length. |
| 39 | expected_len: usize, |
| 40 | }, |
| 41 | /// Error returned when the public key length is invalid. |
| 42 | #[error("EdDSA public key of invalid length {len}, expected length {expected_len}")] |
| 43 | InvalidPubKeyLen { |
| 44 | /// Invalid key length. |
| 45 | len: usize, |
| 46 | /// Expected key length. |
| 47 | expected_len: usize, |
| 48 | }, |
| 49 | /// Error returned when the signature length is invalid. |
| 50 | #[error("EdDSA signature of invalid length {len}, expected length {expected_len}")] |
| 51 | InvalidSigLen { |
| 52 | /// Invalid signature length. |
| 53 | len: usize, |
| 54 | /// Expected signature length. |
| 55 | expected_len: usize, |
| 56 | }, |
| 57 | /// Error returned when unsupported cipher suite is requested. |
| 58 | #[error("unsupported cipher suite")] |
| 59 | UnsupportedCipherSuite, |
| 60 | } |
| 61 | |
| 62 | // Explicitly implemented as InvalidSignatureError's as_dyn_error does not satisfy trait bounds. |
| 63 | impl From<InvalidSignatureError> for EdDsaError { |
| 64 | fn from(e: InvalidSignatureError) -> Self { |
| 65 | EdDsaError::InvalidSig(e) |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | /// EdDSA implementation backed by BoringSSL. |
| 70 | #[derive(Clone, Debug, Copy, PartialEq, Eq)] |
| 71 | pub struct EdDsa(Curve); |
| 72 | |
| 73 | impl EdDsa { |
| 74 | /// Creates a new EdDsa. |
| 75 | pub fn new(cipher_suite: CipherSuite) -> Option<Self> { |
| 76 | Curve::from_ciphersuite(cipher_suite, /*for_sig=*/ true).map(Self) |
| 77 | } |
| 78 | |
| 79 | /// Generates a key pair. |
| 80 | pub fn signature_key_generate( |
| 81 | &self, |
| 82 | ) -> Result<(SignatureSecretKey, SignaturePublicKey), EdDsaError> { |
| 83 | if self.0 != Curve::Ed25519 { |
| 84 | return Err(EdDsaError::UnsupportedCipherSuite); |
| 85 | } |
| 86 | |
| 87 | let private_key = ed25519::PrivateKey::generate(); |
| 88 | let public_key = private_key.to_public(); |
| 89 | Ok((private_key.to_seed().to_vec().into(), public_key.as_bytes().to_vec().into())) |
| 90 | } |
| 91 | |
| 92 | /// Derives the public key from the private key. |
| 93 | pub fn signature_key_derive_public( |
| 94 | &self, |
| 95 | secret_key: &SignatureSecretKey, |
| 96 | ) -> Result<SignaturePublicKey, EdDsaError> { |
| 97 | if self.0 != Curve::Ed25519 { |
| 98 | return Err(EdDsaError::UnsupportedCipherSuite); |
| 99 | } |
| 100 | if secret_key.len() != ed25519::SEED_LEN { |
| 101 | return Err(EdDsaError::InvalidPrivKeyLen { |
| 102 | len: secret_key.len(), |
| 103 | expected_len: ed25519::SEED_LEN, |
| 104 | }); |
| 105 | } |
| 106 | |
| 107 | let private_key = |
| 108 | ed25519::PrivateKey::from_seed(secret_key[..ed25519::SEED_LEN].try_into()?); |
| 109 | Ok(private_key.to_public().as_bytes().to_vec().into()) |
| 110 | } |
| 111 | |
| 112 | /// Signs `data` using `secret_key`. |
| 113 | pub fn sign( |
| 114 | &self, |
| 115 | secret_key: &SignatureSecretKey, |
| 116 | data: &[u8], |
| 117 | ) -> Result<Vec<u8>, EdDsaError> { |
| 118 | if self.0 != Curve::Ed25519 { |
| 119 | return Err(EdDsaError::UnsupportedCipherSuite); |
| 120 | } |
| 121 | if secret_key.len() != ed25519::SEED_LEN { |
| 122 | return Err(EdDsaError::InvalidPrivKeyLen { |
| 123 | len: secret_key.len(), |
| 124 | expected_len: ed25519::SEED_LEN, |
| 125 | }); |
| 126 | } |
| 127 | |
| 128 | let private_key = |
| 129 | ed25519::PrivateKey::from_seed(secret_key[..ed25519::SEED_LEN].try_into()?); |
| 130 | Ok(private_key.sign(data).to_vec()) |
| 131 | } |
| 132 | |
| 133 | /// Verifies `signature` is a valid signature of `data` using `public_key`. |
| 134 | pub fn verify( |
| 135 | &self, |
| 136 | public_key: &SignaturePublicKey, |
| 137 | signature: &[u8], |
| 138 | data: &[u8], |
| 139 | ) -> Result<(), EdDsaError> { |
| 140 | if self.0 != Curve::Ed25519 { |
| 141 | return Err(EdDsaError::UnsupportedCipherSuite); |
| 142 | } |
| 143 | if public_key.len() != ed25519::PUBLIC_KEY_LEN { |
| 144 | return Err(EdDsaError::InvalidPubKeyLen { |
| 145 | len: public_key.len(), |
| 146 | expected_len: ed25519::PUBLIC_KEY_LEN, |
| 147 | }); |
| 148 | } |
| 149 | if signature.len() != ed25519::SIGNATURE_LEN { |
| 150 | return Err(EdDsaError::InvalidSigLen { |
| 151 | len: signature.len(), |
| 152 | expected_len: ed25519::SIGNATURE_LEN, |
| 153 | }); |
| 154 | } |
| 155 | |
| 156 | let public_key = ed25519::PublicKey::from_bytes( |
| 157 | public_key.as_bytes()[..ed25519::PUBLIC_KEY_LEN].try_into()?, |
| 158 | ); |
| 159 | match public_key.verify(data, signature[..ed25519::SIGNATURE_LEN].try_into()?) { |
| 160 | Ok(_) => Ok(()), |
| 161 | Err(e) => Err(EdDsaError::InvalidSig(e)), |
| 162 | } |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | #[cfg(all(not(mls_build_async), test))] |
| 167 | mod test { |
| 168 | use super::{EdDsa, EdDsaError}; |
| 169 | use crate::test_helpers::decode_hex; |
| 170 | use assert_matches::assert_matches; |
| 171 | use mls_rs_core::crypto::{CipherSuite, SignaturePublicKey, SignatureSecretKey}; |
| 172 | |
| 173 | #[test] |
| 174 | fn signature_key_generate() { |
| 175 | let ed25519 = EdDsa::new(CipherSuite::CURVE25519_AES128).unwrap(); |
| 176 | assert!(ed25519.signature_key_generate().is_ok()); |
| 177 | } |
| 178 | |
| 179 | #[test] |
| 180 | fn signature_key_derive_public() { |
| 181 | // Test 1 from https://www.rfc-editor.org/rfc/rfc8032#section-7.1 |
| 182 | let private_key = SignatureSecretKey::from( |
| 183 | decode_hex::<32>("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60") |
| 184 | .to_vec(), |
| 185 | ); |
| 186 | let expected_public_key = SignaturePublicKey::from( |
| 187 | decode_hex::<32>("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a") |
| 188 | .to_vec(), |
| 189 | ); |
| 190 | |
| 191 | let ed25519 = EdDsa::new(CipherSuite::CURVE25519_CHACHA).unwrap(); |
| 192 | assert_eq!(ed25519.signature_key_derive_public(&private_key).unwrap(), expected_public_key); |
| 193 | } |
| 194 | |
| 195 | #[test] |
| 196 | fn signature_key_derive_public_invalid_key() { |
| 197 | let private_key_short = |
| 198 | SignatureSecretKey::from(decode_hex::<16>("9d61b19deffd5a60ba844af492ec2cc4").to_vec()); |
| 199 | |
| 200 | let ed25519 = EdDsa::new(CipherSuite::CURVE25519_CHACHA).unwrap(); |
| 201 | assert_matches!( |
| 202 | ed25519.signature_key_derive_public(&private_key_short), |
| 203 | Err(EdDsaError::InvalidPrivKeyLen { .. }) |
| 204 | ); |
| 205 | } |
| 206 | |
| 207 | #[test] |
| 208 | fn sign_verify() { |
| 209 | // Test 3 from https://www.rfc-editor.org/rfc/rfc8032#section-7.1 |
| 210 | let private_key = SignatureSecretKey::from( |
| 211 | decode_hex::<32>("c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7") |
| 212 | .to_vec(), |
| 213 | ); |
| 214 | let data: [u8; 2] = decode_hex("af82"); |
| 215 | let expected_sig = decode_hex::<64>("6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a").to_vec(); |
| 216 | |
| 217 | let ed25519 = EdDsa::new(CipherSuite::CURVE25519_AES128).unwrap(); |
| 218 | let sig = ed25519.sign(&private_key, &data).unwrap(); |
| 219 | assert_eq!(sig, expected_sig); |
| 220 | |
| 221 | let public_key = ed25519.signature_key_derive_public(&private_key).unwrap(); |
| 222 | assert!(ed25519.verify(&public_key, &sig, &data).is_ok()); |
| 223 | } |
| 224 | |
| 225 | #[test] |
| 226 | fn sign_invalid_key() { |
| 227 | let private_key_short = |
| 228 | SignatureSecretKey::from(decode_hex::<16>("c5aa8df43f9f837bedb7442f31dcb7b1").to_vec()); |
| 229 | |
| 230 | let ed25519 = EdDsa::new(CipherSuite::CURVE25519_AES128).unwrap(); |
| 231 | assert_matches!( |
| 232 | ed25519.sign(&private_key_short, &decode_hex::<2>("af82")), |
| 233 | Err(EdDsaError::InvalidPrivKeyLen { .. }) |
| 234 | ); |
| 235 | } |
| 236 | |
| 237 | #[test] |
| 238 | fn verify_invalid_key() { |
| 239 | let public_key_short = |
| 240 | SignaturePublicKey::from(decode_hex::<16>("fc51cd8e6218a1a38da47ed00230f058").to_vec()); |
| 241 | let sig = decode_hex::<64>("6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a").to_vec(); |
| 242 | let data: [u8; 2] = decode_hex("af82"); |
| 243 | |
| 244 | let ed25519 = EdDsa::new(CipherSuite::CURVE25519_AES128).unwrap(); |
| 245 | assert_matches!( |
| 246 | ed25519.verify(&public_key_short, &sig, &data), |
| 247 | Err(EdDsaError::InvalidPubKeyLen { .. }) |
| 248 | ); |
| 249 | } |
| 250 | |
| 251 | #[test] |
| 252 | fn verify_invalid_sig() { |
| 253 | let public_key = SignaturePublicKey::from( |
| 254 | decode_hex::<32>("fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025") |
| 255 | .to_vec(), |
| 256 | ); |
| 257 | let sig_short = |
| 258 | decode_hex::<32>("6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac") |
| 259 | .to_vec(); |
| 260 | let data: [u8; 2] = decode_hex("af82"); |
| 261 | |
| 262 | let ed25519 = EdDsa::new(CipherSuite::CURVE25519_AES128).unwrap(); |
| 263 | assert_matches!( |
| 264 | ed25519.verify(&public_key, &sig_short, &data), |
| 265 | Err(EdDsaError::InvalidSigLen { .. }) |
| 266 | ); |
| 267 | } |
| 268 | |
| 269 | #[test] |
| 270 | fn unsupported_cipher_suites() { |
| 271 | for suite in vec![ |
| 272 | CipherSuite::P256_AES128, |
| 273 | CipherSuite::P384_AES256, |
| 274 | CipherSuite::P521_AES256, |
| 275 | CipherSuite::CURVE448_CHACHA, |
| 276 | CipherSuite::CURVE448_AES256, |
| 277 | ] { |
| 278 | assert_matches!( |
| 279 | EdDsa::new(suite).unwrap().signature_key_generate(), |
| 280 | Err(EdDsaError::UnsupportedCipherSuite) |
| 281 | ); |
| 282 | } |
| 283 | } |
| 284 | } |