Fix signature key import/export, add tests

Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
Lee Smet 2025-05-16 14:37:10 +02:00
parent d29a8fbb67
commit 365814b424
Signed by untrusted user who does not match committer: lee
GPG Key ID: 72CBFB5FDA7FE025
2 changed files with 82 additions and 7 deletions

View File

@ -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<bincode::error::EncodeError> for Error {
Self::Coding
}
}
impl From<k256::ecdsa::Error> for CryptoError {
fn from(_: k256::ecdsa::Error) -> Self {
Self::InvalidKey
}
}

View File

@ -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<Self, CryptoError> {
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());
}
}