diff --git a/vault/src/error.rs b/vault/src/error.rs index 4f4f502..60ec0fd 100644 --- a/vault/src/error.rs +++ b/vault/src/error.rs @@ -44,6 +44,8 @@ pub enum CryptoError { SignatureFailed, /// The signature does not have the expected size InvalidSignatureSize, + /// Trying to load a key which is not the expected format, + InvalidKey, } impl core::fmt::Display for CryptoError { @@ -57,6 +59,7 @@ impl core::fmt::Display for CryptoError { CryptoError::InvalidSignatureSize => { f.write_str("provided signature does not have the expected size") } + CryptoError::InvalidKey => f.write_str("the provided bytes are not a valid key"), } } } @@ -92,3 +95,9 @@ impl From for Error { Self::Coding } } + +impl From for CryptoError { + fn from(_: k256::ecdsa::Error) -> Self { + Self::InvalidKey + } +} diff --git a/vault/src/key/signature.rs b/vault/src/key/signature.rs index 029f47f..e83d364 100644 --- a/vault/src/key/signature.rs +++ b/vault/src/key/signature.rs @@ -12,6 +12,7 @@ pub struct SigningKeypair { vk: VerifyingKey, } +#[derive(Debug, PartialEq, Eq)] pub struct PublicKey(VerifyingKey); impl SigningKeypair { @@ -58,13 +59,14 @@ impl SigningKeypair { impl PublicKey { /// Import a public key from raw bytes pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() == 64 { - Ok(Self( - VerifyingKey::from_sec1_bytes(bytes).expect("Key is of valid size"), - )) - } else { - Err(CryptoError::InvalidKeySize) - } + Ok(Self(VerifyingKey::from_sec1_bytes(bytes)?)) + } + + /// Get the raw bytes of this `PublicKey`, which can be transferred to another party. + /// + /// The public key is SEC-1 encoded and compressed. + pub fn as_bytes(&self) -> Box<[u8]> { + self.0.to_encoded_point(true).to_bytes() } pub fn verify_signature(&self, message: &[u8], sig: &[u8]) -> Result<(), CryptoError> { @@ -74,3 +76,67 @@ impl PublicKey { .map_err(|_| CryptoError::SignatureFailed) } } + +#[cfg(test)] +mod tests { + + /// Generate a key, get the public key, export the bytes of said public key, import them again + /// as a public key, and verify the keys match. This make sure public keys can be exchanged. + #[test] + fn recover_public_key() { + let sk = super::SigningKeypair::new().expect("Can generate new key"); + let pk = sk.public_key(); + let pk_bytes = pk.as_bytes(); + + let pk2 = super::PublicKey::from_bytes(&pk_bytes).expect("Can import public key"); + + assert_eq!(pk, pk2); + } + + /// Sign a message and validate the signature with the public key. Together with the above test + /// this makes sure a remote system can receive our public key and validate messages we sign. + #[test] + fn validate_signature() { + let sk = super::SigningKeypair::new().expect("Can generate new key"); + let pk = sk.public_key(); + + let message = b"this is an arbitrary message we want to sign"; + + let sig = sk.sign(message).expect("Message can be signed"); + + assert!(pk.verify_signature(message, &sig).is_ok()); + } + + /// Make sure a signature which is tampered with does not pass signature validation + #[test] + fn corrupt_signature_does_not_validate() { + let sk = super::SigningKeypair::new().expect("Can generate new key"); + let pk = sk.public_key(); + + let message = b"this is an arbitrary message we want to sign"; + + let mut sig = sk.sign(message).expect("Message can be signed"); + + // Tamper with the sig + sig[0] = sig[0].wrapping_add(1); + + assert!(pk.verify_signature(message, &sig).is_err()); + } + + /// Make sure a valid signature does not work for a message which has been modified + #[test] + fn tampered_message_does_not_validate() { + let sk = super::SigningKeypair::new().expect("Can generate new key"); + let pk = sk.public_key(); + + let message = b"this is an arbitrary message we want to sign"; + let mut message_clone = message.to_vec(); + + let sig = sk.sign(message).expect("Message can be signed"); + + // Modify the message + message_clone[0] = message[0].wrapping_add(1); + + assert!(pk.verify_signature(&message_clone, &sig).is_err()); + } +}