Skip to content

Commit

Permalink
Add support for signature verification using
Browse files Browse the repository at this point in the history
public key in X509 DER encoding format.
This includes RSA, Ed25519 and EC.  Specifically, the changes include:
* Add signature::UnparsedPublicKey.new_with_x509()
* Add verify_sig_with_x509_pubkey in addition to verify_sig
* Add verify_ed25519_signature
* Check ed25519 X509 public key prefix
  • Loading branch information
Hanson Char committed Nov 16, 2023
1 parent c11af58 commit 1ca187e
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 24 deletions.
1 change: 1 addition & 0 deletions aws-lc-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,4 @@ regex = "1.6.0"
lazy_static = "1.4.0"
clap = { version = "4.1.8", features = ["derive"] }
hex = "0.4.3"
base64 = "0"
117 changes: 104 additions & 13 deletions aws-lc-rs/src/ec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ use crate::ptr::{ConstPointer, DetachableLcPtr, LcPtr, Pointer};

use crate::fips::indicator_check;
use crate::signature::{Signature, VerificationAlgorithm};
use crate::{digest, sealed, test};
use crate::{digest, public_key, sealed, test};
#[cfg(feature = "fips")]
use aws_lc::EC_KEY_check_fips;
#[cfg(not(feature = "fips"))]
use aws_lc::EC_KEY_check_key;
use aws_lc::{
point_conversion_form_t, BN_bn2bin_padded, BN_num_bytes, ECDSA_SIG_from_bytes,
ECDSA_SIG_get0_r, ECDSA_SIG_get0_s, ECDSA_SIG_new, ECDSA_SIG_set0, ECDSA_SIG_to_bytes,
EC_GROUP_get_curve_name, EC_GROUP_new_by_curve_name, EC_KEY_get0_group,
point_conversion_form_t, BIO_new, BIO_s_mem, BN_bn2bin_padded, BN_num_bytes,
ECDSA_SIG_from_bytes, ECDSA_SIG_get0_r, ECDSA_SIG_get0_s, ECDSA_SIG_new, ECDSA_SIG_set0,
ECDSA_SIG_to_bytes, EC_GROUP_get_curve_name, EC_GROUP_new_by_curve_name, EC_KEY_get0_group,
EC_KEY_get0_private_key, EC_KEY_get0_public_key, EC_KEY_new, EC_KEY_set_group,
EC_KEY_set_private_key, EC_KEY_set_public_key, EC_POINT_new, EC_POINT_oct2point,
EC_POINT_point2oct, EVP_DigestVerify, EVP_DigestVerifyInit, EVP_PKEY_CTX_new_id,
Expand All @@ -29,13 +29,15 @@ use aws_lc::{
NID_secp384r1, NID_secp521r1, BIGNUM, ECDSA_SIG, EC_GROUP, EC_POINT, EVP_PKEY, EVP_PKEY_EC,
};

use aws_lc::d2i_PUBKEY_bio;
use aws_lc::BIO_write;
#[cfg(test)]
use aws_lc::EC_POINT_mul;

use std::fmt::{Debug, Formatter};
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::os::raw::{c_int, c_uint};
use std::os::raw::{c_int, c_uint, c_void};
#[cfg(test)]
use std::ptr::null;
use std::ptr::null_mut;
Expand Down Expand Up @@ -181,12 +183,48 @@ impl VerificationAlgorithm for EcdsaVerificationAlgorithm {
signature: &[u8],
) -> Result<(), Unspecified> {
match self.sig_format {
EcdsaSignatureFormat::ASN1 => {
verify_asn1_signature(self.id, self.digest, public_key, msg, signature)
}
EcdsaSignatureFormat::Fixed => {
verify_fixed_signature(self.id, self.digest, public_key, msg, signature)
}
EcdsaSignatureFormat::ASN1 => verify_asn1_signature(
self.id,
self.digest,
public_key,
&public_key::OCTET_STRING,
msg,
signature,
),
EcdsaSignatureFormat::Fixed => verify_fixed_signature(
self.id,
self.digest,
public_key,
&public_key::OCTET_STRING,
msg,
signature,
),
}
}

fn verify_sig_with_x509_pubkey(
&self,
public_key: &[u8],
msg: &[u8],
signature: &[u8],
) -> Result<(), crate::error::Unspecified> {
match self.sig_format {
EcdsaSignatureFormat::ASN1 => verify_asn1_signature(
self.id,
self.digest,
public_key,
&public_key::X509,
msg,
signature,
),
EcdsaSignatureFormat::Fixed => verify_fixed_signature(
self.id,
self.digest,
public_key,
&public_key::X509,
msg,
signature,
),
}
}
}
Expand All @@ -195,6 +233,7 @@ fn verify_fixed_signature(
alg: &'static AlgorithmID,
digest: &'static digest::Algorithm,
public_key: &[u8],
encoding: &public_key::Encoding,
msg: &[u8],
signature: &[u8],
) -> Result<(), Unspecified> {
Expand All @@ -208,17 +247,21 @@ fn verify_fixed_signature(
}
let out_bytes = LcPtr::new(out_bytes)?;
let signature = unsafe { out_bytes.as_slice(out_bytes_len.assume_init()) };
verify_asn1_signature(alg, digest, public_key, msg, signature)
verify_asn1_signature(alg, digest, public_key, encoding, msg, signature)
}

fn verify_asn1_signature(
alg: &'static AlgorithmID,
digest: &'static digest::Algorithm,
public_key: &[u8],
encoding: &public_key::Encoding,
msg: &[u8],
signature: &[u8],
) -> Result<(), Unspecified> {
let pkey = evp_pkey_from_public_key(alg, public_key)?;
let pkey = match encoding.id {
public_key::EncodingID::OctetString => evp_pkey_from_public_key(alg, public_key)?,
public_key::EncodingID::X509 => evp_pkey_from_x509_pubkey(public_key)?,
};

let mut md_ctx = DigestContext::new_uninit();

Expand Down Expand Up @@ -376,6 +419,23 @@ pub(crate) unsafe fn evp_pkey_from_public_point(
Ok(pkey)
}

#[inline]
pub(crate) fn evp_pkey_from_x509_pubkey(
pubkey_data: &[u8],
) -> Result<LcPtr<EVP_PKEY>, Unspecified> {
// Create a memory BIO and write the public key data to it
let mem_bio = LcPtr::new(unsafe { BIO_new(BIO_s_mem()) })?;
let len = match c_int::try_from(pubkey_data.len()) {
Ok(len) => len,
Err(_) => return Err(Unspecified),
};
if unsafe { BIO_write(*mem_bio, pubkey_data.as_ptr().cast::<c_void>(), len) } <= 0 {
return Err(Unspecified);
}
// Use d2i_PUBKEY_bio to read the public key from the memory BIO
Ok(LcPtr::new(unsafe { d2i_PUBKEY_bio(*mem_bio, null_mut()) })?)
}

#[cfg(test)]
pub(crate) unsafe fn evp_pkey_from_private(
ec_group: &ConstPointer<EC_GROUP>,
Expand Down Expand Up @@ -580,6 +640,9 @@ unsafe fn ecdsa_sig_from_fixed(

#[cfg(test)]
mod tests {
use base64::engine::general_purpose;
use base64::Engine;

use crate::ec::key_pair::EcdsaKeyPair;
use crate::signature::{KeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
use crate::test::from_dirty_hex;
Expand Down Expand Up @@ -643,4 +706,32 @@ mod tests {
let actual_result = unparsed_pub_key.verify(msg.as_bytes(), &sig);
assert!(actual_result.is_ok(), "Key: {}", test::to_hex(public_key));
}

#[test]
fn test_p384_verify_with_x509_pubkey() {
let alg = &signature::ECDSA_P384_SHA384_ASN1;
let msg = "hello, world";

// Generated from bc-fips using "SHA384withECDSA"
let x509_pubkey_b64 = concat!(
"MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEPhS06qHiqNSnyanUSHHMMebqu2h4Ho3oSwlNfLOtCXlIsm91",
"684Hor2X1b056aWGymprw8W6cXn/d6O2Y6x0FGu0uJnEEkvsIAwzu+stRHzgxuiky633R7zsSIfI+rsc",
);
let x509_pubkey = general_purpose::STANDARD
.decode(x509_pubkey_b64)
.expect("Invalid base64 encoding");
let unparsed_pub_key =
signature::UnparsedPublicKey::new_with_x509(alg, x509_pubkey.as_slice());

// Output from bc-fips
let sig_b64 = concat!(
"MGYCMQCBzjtJuLZol+KWCN6Tsv+FBENp1QpOlfVpOSlBB82LpGn3fMcQcAnopkrTwqJA0gICMQDfHqOr",
"Kebqy7qOj26odws7oROlIParYYl9FfLWGjIysipMk51ZbakcUVDVuDHu3QY="
);
let sig = general_purpose::STANDARD
.decode(sig_b64)
.expect("Invalid base64 encoding");
let actual_result = unparsed_pub_key.verify(msg.as_bytes(), sig.as_slice());
assert!(actual_result.is_ok(), "Key: {x509_pubkey_b64}");
}
}
44 changes: 43 additions & 1 deletion aws-lc-rs/src/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ pub(crate) const ED25519_PRIVATE_KEY_SEED_LEN: usize =
aws_lc::ED25519_PRIVATE_KEY_SEED_LEN as usize;
const ED25519_SIGNATURE_LEN: usize = aws_lc::ED25519_SIGNATURE_LEN as usize;
const ED25519_SEED_LEN: usize = 32;
// Hex equivalent: 0x302a300506032b6570032100
const ED25519_X509_PUBLIC_KEY_PREFIX: [u8; 12] = [48, 42, 48, 5, 6, 3, 43, 101, 112, 3, 33, 0];
const ED25519_X509_PUBLIC_KEY_LEN: usize =
ED25519_X509_PUBLIC_KEY_PREFIX.len() + ED25519_PUBLIC_KEY_LEN;

/// Parameters for `EdDSA` signing and verification.
#[derive(Debug)]
Expand Down Expand Up @@ -74,6 +78,21 @@ impl VerificationAlgorithm for EdDSAParameters {
crate::fips::set_fips_service_status_unapproved();
Ok(())
}

fn verify_sig_with_x509_pubkey(
&self,
public_key: &[u8],
msg: &[u8],
signature: &[u8],
) -> Result<(), Unspecified> {
if public_key.len() == ED25519_X509_PUBLIC_KEY_LEN
&& public_key[..12] == ED25519_X509_PUBLIC_KEY_PREFIX
{
self.verify_sig(&public_key[12..], msg, signature)
} else {
Err(Unspecified)
}
}
}

/// An Ed25519 key pair, for signing.
Expand Down Expand Up @@ -414,9 +433,12 @@ impl Ed25519KeyPair {
#[cfg(test)]
mod tests {

use base64::engine::general_purpose;
use base64::Engine;

use crate::ed25519::Ed25519KeyPair;
use crate::rand::SystemRandom;
use crate::test;
use crate::{signature, test};

#[test]
fn test_generate_pkcs8() {
Expand Down Expand Up @@ -481,4 +503,24 @@ mod tests {
);
}
}

#[test]
fn verify_ed25519_signature() {
const MESSAGE: &[u8] = b"hello, world";
// Generated using "ED25519" from BC-FIPS
let b64_x509_pubkey = "MCowBQYDK2VwAyEAo7urSMCwnkczfz1hxyj15uE+ja/nK0aOenoNCtqE+9Q=";
let b64_sig = "ZLiMhsOeHjGmnO+B3CzUe8B6Hl1O9j/aaDJqPU4lZZHtmxZxCQIN1lGqvFTP9c6AxRblTlftPK/oZF2xNM+5Ag==";
let x509_pubkey = general_purpose::STANDARD
.decode(b64_x509_pubkey)
.expect("Invalid base64 encoding");
let signature = general_purpose::STANDARD
.decode(b64_sig)
.expect("Invalid base64 encoding");
// Verify the signature.
let public_key =
signature::UnparsedPublicKey::new_with_x509(&signature::ED25519, &x509_pubkey);
public_key
.verify(MESSAGE, &signature)
.expect("Signature verification failure");
}
}
7 changes: 4 additions & 3 deletions aws-lc-rs/src/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
use std::ops::Deref;

use aws_lc::{
BN_free, ECDSA_SIG_free, EC_GROUP_free, EC_KEY_free, EC_POINT_free, EVP_AEAD_CTX_free,
EVP_PKEY_CTX_free, EVP_PKEY_free, OPENSSL_free, RSA_free, BIGNUM, ECDSA_SIG, EC_GROUP, EC_KEY,
EC_POINT, EVP_AEAD_CTX, EVP_PKEY, EVP_PKEY_CTX, RSA,
BIO_free, BN_free, ECDSA_SIG_free, EC_GROUP_free, EC_KEY_free, EC_POINT_free,
EVP_AEAD_CTX_free, EVP_PKEY_CTX_free, EVP_PKEY_free, OPENSSL_free, RSA_free, BIGNUM, BIO,
ECDSA_SIG, EC_GROUP, EC_KEY, EC_POINT, EVP_AEAD_CTX, EVP_PKEY, EVP_PKEY_CTX, RSA,
};

use mirai_annotations::verify_unreachable;
Expand Down Expand Up @@ -201,6 +201,7 @@ macro_rules! create_pointer {
// freed. This is different than functions of the same name in OpenSSL which generally do not zero
// memory.
create_pointer!(u8, OPENSSL_free);
create_pointer!(BIO, BIO_free);
create_pointer!(EC_GROUP, EC_GROUP_free);
create_pointer!(EC_POINT, EC_POINT_free);
create_pointer!(EC_KEY, EC_KEY_free);
Expand Down
2 changes: 1 addition & 1 deletion aws-lc-rs/src/public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub(crate) enum EncodingID {

/// Encoding of bytes
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct Encoding {
pub struct Encoding {
/// Encoding ID
pub(crate) id: EncodingID,
}
Expand Down
48 changes: 48 additions & 0 deletions aws-lc-rs/src/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::fips::indicator_check;
#[cfg(feature = "ring-io")]
use crate::io;
use crate::ptr::{ConstPointer, DetachableLcPtr, LcPtr};
use crate::public_key::evp_pkey_from_x509_pubkey;
use crate::sealed::Sealed;
use crate::signature::{KeyPair, VerificationAlgorithm};
use crate::{cbs, digest, rand, test};
Expand Down Expand Up @@ -211,6 +212,16 @@ impl VerificationAlgorithm for RsaParameters {
verify_RSA(self.0, self.1, &rsa, msg, signature, &self.2)
}
}

fn verify_sig_with_x509_pubkey(
&self,
public_key: &[u8],
msg: &[u8],
signature: &[u8],
) -> Result<(), Unspecified> {
let rsa = evp_pkey_from_x509_pubkey(public_key)?;
verify_RSA(self.0, self.1, &rsa, msg, signature, &self.2)
}
}

impl RsaKeyPair {
Expand Down Expand Up @@ -730,6 +741,10 @@ where

#[cfg(test)]
mod tests {
use base64::{engine::general_purpose, Engine};

use crate::signature;

#[cfg(feature = "ring-io")]
#[test]
fn test_rsa() {
Expand Down Expand Up @@ -785,4 +800,37 @@ mod tests {
format!("{:?}", signature::RSA_PSS_2048_8192_SHA256)
);
}

#[test]
fn verify_rsa_signature() {
const MESSAGE: &[u8] = b"hello, world";
// Generated using "SHA384withRSA" from BC-FIPS
let b64_x509_pubkey = concat!(
"MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAzmMMJclDHPi+YvGUNjDtvGV+x40Fou5Nv0+c+uBH/xaO3+D",
"VDp/psg11UsA/haODPTNdFASqxu76/m1BLGmf+2MiLc1RNhvFJYTTJADsPtzObroBhXOhv1HYycB14JYFDFGJ0Bn/uf",
"tGyEn7wWHulcclTPRUg4F5i/MzzfiXimsn8sMme2hZXvlFvwjuSp6BGHCqu2DfXHY16BDZsEtK+DpABz8mGCN/D7ilc",
"8feSeyCc5wbFSbRgkJXRPYxuqg0icTQDCwx6OWdcNPRaQq5ty2HLG2PXmSDomtwsKPuAwvEqOy0BgZ0VjW0nCwMH1e/",
"jFvZqXDI7Sf9dWw3eMtc6jk9cTat+AdcvgtvFIEEcH9k48q+pFvfGQ8YBSbZBrYyCWZxPKuDOSuQHO682WX2mAsG1cY",
"Y+cLrsH2asqsCKCAwKcz43IucqqvEFqtnwpnkCIlGxqHWGbVrWTxmcC1qEM9Xkik2xJcXHGKmtMnoPgnsHiV7vVoglK",
"iDUfDyi5VTAgMBAAE=");
let b64_sig = concat!(
"y0NNWwHRLA0B2MvQ63QKcBYvlIx7gOxQXDh0IRdTryQ2pYuod3w1k5STGR3f+CTm83wp/MvBnCEEvnlxruK5Vrz3g1h",
"ThneUnZGwPo1MwCgvVLmR6RtoXy9+5M4BBcl/PqJTQcWo92RsbxEGVEVzsH6CNBAf4QDVW5ox1g5HjH4HsTutyezdE0",
"cmsHwz6IRiMeyuGWvs9sXECiIeripVpoHD7qz1TpvJPHA+wpvwDOUxJppk2naP5cVY+B0jRzTJ1TmX0IM1KMSrN/J5m",
"vkHm5TsyWkRBj6GD+4fsibSsF5W6vLSLJtQAk4dFpaafGcCgKsGSzpU605DeaHhrVOUnrAmLLCN5uIBRkGmekKowPkJ",
"AsdHP04nJ7QwQoIjxcaX9WJpPotcaqaKGdGf4xy2QIYRnhoRDq9DXfl6Wckqh6z4wz+R6gUr1wS3X5SiYvSgjEYQOYE",
"6A1xo+cpuPBpWuj9eL77QZBsn0rGwJSJNr4ds7gfIk7tYfkNcI/C7MFOh");
let x509_pubkey = general_purpose::STANDARD
.decode(b64_x509_pubkey)
.expect("Invalid base64 encoding");
let signature = general_purpose::STANDARD
.decode(b64_sig)
.expect("Invalid base64 encoding");
// Verify the signature.
let public_key = signature::UnparsedPublicKey::new_with_x509(
&signature::RSA_PKCS1_2048_8192_SHA384,
&x509_pubkey,
);
public_key.verify(MESSAGE, &signature).unwrap();
}
}
Loading

0 comments on commit 1ca187e

Please sign in to comment.