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 Dec 16, 2023
1 parent cd07f58 commit 30f6c54
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 27 deletions.
1 change: 1 addition & 0 deletions aws-lc-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ regex = "1.6.0"
lazy_static = "1.4.0"
clap = { version = "4.1.8", features = ["derive"] }
hex = "0.4.3"
base64 = "0"
124 changes: 110 additions & 14 deletions aws-lc-rs/src/ec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,28 @@ use std::fmt::{Debug, Formatter};
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::os::raw::{c_int, c_uint};

#[cfg(test)]
use std::ptr::null;

use std::ptr::null_mut;

#[cfg(feature = "ring-sig-verify")]
use untrusted::Input;

use crate::fips::indicator_check;
use crate::signature::{Signature, VerificationAlgorithm};
use crate::{digest, public_key, sealed};
#[cfg(feature = "fips")]
use aws_lc::EC_KEY_check_fips;
#[cfg(not(feature = "fips"))]
use aws_lc::EC_KEY_check_key;
#[cfg(test)]
use aws_lc::EC_POINT_mul;
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 @@ -37,10 +42,12 @@ use crate::buffer::Buffer;
use crate::digest::digest_ctx::DigestContext;
use crate::encoding::AsDer;
use crate::error::{KeyRejected, Unspecified};
use crate::fips::indicator_check;
use crate::hex;
use crate::ptr::{ConstPointer, DetachableLcPtr, LcPtr, Pointer};
use crate::signature::{Signature, VerificationAlgorithm};
use crate::{digest, hex, sealed};
use aws_lc::d2i_PUBKEY_bio;
use aws_lc::BIO_write;

use std::os::raw::c_void;

pub(crate) mod key_pair;

Expand Down Expand Up @@ -200,12 +207,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 @@ -214,6 +257,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 @@ -227,17 +271,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 @@ -382,6 +430,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 @@ -589,6 +654,9 @@ mod tests {
use crate::encoding::AsDer;
use crate::signature::EcPublicKeyX509Der;
use crate::signature::EcdsaKeyPair;
use base64::engine::general_purpose;
use base64::Engine;

use crate::signature::{KeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
use crate::test::from_dirty_hex;
use crate::{signature, test};
Expand Down Expand Up @@ -651,4 +719,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 @@ -35,6 +35,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 @@ -77,6 +81,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 @@ -419,9 +438,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 @@ -486,4 +508,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
37 changes: 37 additions & 0 deletions aws-lc-rs/src/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ pub(crate) use self::signature::RsaVerificationAlgorithmId;

#[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 @@ -75,4 +79,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();
}
}
18 changes: 18 additions & 0 deletions aws-lc-rs/src/rsa/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::{
error::Unspecified,
fips::indicator_check,
ptr::{ConstPointer, DetachableLcPtr, LcPtr},
public_key::evp_pkey_from_x509_pubkey,
sealed::Sealed,
signature::VerificationAlgorithm,
};
Expand Down Expand Up @@ -93,6 +94,23 @@ impl VerificationAlgorithm for RsaParameters {
)
}
}

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.digest_algorithm(),
self.padding(),
&rsa,
msg,
signature,
self.bit_size_range(),
)
}
}

impl Sealed for RsaParameters {}
Expand Down
Loading

0 comments on commit 30f6c54

Please sign in to comment.