Skip to content

Commit

Permalink
Handle verification of keys using elliptic curve
Browse files Browse the repository at this point in the history
This commit introduces support for handling certificates using elliptic
curve public keys.

The implementation relies on the `ring` crate, which currently is the
only one capable of supporting both ECDSA p256 and ECDSA p384.

Signed-off-by: Flavio Castelli <[email protected]>
  • Loading branch information
flavio committed Mar 3, 2022
1 parent 0ffb806 commit 054bc6c
Show file tree
Hide file tree
Showing 7 changed files with 361 additions and 31 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions picky/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ aes-gcm = { version = "0.9.4", optional = true }
bcrypt-pbkdf = { version = "0.6", optional = true }
block-modes = { version = "0.8", optional = true }
aes = { version = "0.7.5", features = ["ctr"], default-features = false, optional = true }
ring = "0.16.20"

[dev-dependencies]
pretty_assertions = "1.0.0"
hex = "0.4.3"
cfg-if = "1.0.0"
ring = "0.16.20"
rstest = "0.12.0"

[features]
default = ["x509", "jose", "http_signature", "http_trait_impl"]
Expand Down
8 changes: 8 additions & 0 deletions picky/src/http/http_signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,9 @@ const HTTP_SIG_ALGO_RSA_SHA2_512: &str = "rsa-sha2-512";
const HTTP_SIG_ALGO_RSA_SHA3_384: &str = "rsa-sha3-384";
const HTTP_SIG_ALGO_RSA_SHA3_512: &str = "rsa-sha3-512";

const HTTP_SIG_ALGO_ECDSA_SHA_256: &str = "ecdsa-sha256";
const HTTP_SIG_ALGO_ECDSA_SHA_384: &str = "ecdsa-sha384";

fn to_http_sig_algo_str(algo: SignatureAlgorithm) -> &'static str {
match algo {
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::MD5) => HTTP_SIG_ALGO_RSA_MD5,
Expand All @@ -839,6 +842,9 @@ fn to_http_sig_algo_str(algo: SignatureAlgorithm) -> &'static str {
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_512) => HTTP_SIG_ALGO_RSA_SHA_512,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA3_384) => HTTP_SIG_ALGO_RSA_SHA3_384,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA3_512) => HTTP_SIG_ALGO_RSA_SHA3_512,
SignatureAlgorithm::Ecdsa(HashAlgorithm::SHA2_256) => HTTP_SIG_ALGO_ECDSA_SHA_256,
SignatureAlgorithm::Ecdsa(HashAlgorithm::SHA2_384) => HTTP_SIG_ALGO_ECDSA_SHA_384,
SignatureAlgorithm::Ecdsa(_) => "ECDSA unsupported algorithm",
}
}

Expand All @@ -860,6 +866,8 @@ fn from_http_sig_algo_str(s: &str) -> Option<SignatureAlgorithm> {
}
HTTP_SIG_ALGO_RSA_SHA3_384 => Some(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA3_384)),
HTTP_SIG_ALGO_RSA_SHA3_512 => Some(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA3_512)),
HTTP_SIG_ALGO_ECDSA_SHA_256 => Some(SignatureAlgorithm::Ecdsa(HashAlgorithm::SHA2_256)),
HTTP_SIG_ALGO_ECDSA_SHA_384 => Some(SignatureAlgorithm::Ecdsa(HashAlgorithm::SHA2_384)),
_ => None,
}
}
Expand Down
162 changes: 145 additions & 17 deletions picky/src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

use crate::pem::{parse_pem, Pem, PemError};
use num_bigint_dig::traits::ModInverse;
use picky_asn1::wrapper::{BitStringAsn1Container, IntegerAsn1, OctetStringAsn1Container};
use picky_asn1::wrapper::{BitStringAsn1, BitStringAsn1Container, IntegerAsn1, OctetStringAsn1Container};
use picky_asn1_der::Asn1DerError;
use picky_asn1_x509::{private_key_info, PrivateKeyInfo, PrivateKeyValue, SubjectPublicKeyInfo};
use picky_asn1_x509::{oids, private_key_info, PrivateKeyInfo, PrivateKeyValue, SubjectPublicKeyInfo};
use rsa::{BigUint, RsaPrivateKey, RsaPublicKey};
use thiserror::Error;

Expand Down Expand Up @@ -127,6 +127,70 @@ impl TryFrom<&'_ PrivateKey> for RsaPublicKey {
}
}

#[derive(Debug)]
pub(crate) struct EcdsaKeypair<'a> {
pub(crate) private_key: Vec<u8>,
pub(crate) public_key: &'a [u8],
pub(crate) curve: EcdsaCurve,
}

#[derive(Debug, PartialEq)]
pub(crate) enum EcdsaCurve {
Nist256,
Nist384,
Nist512,
}

impl<'a> TryFrom<&'a PrivateKey> for EcdsaKeypair<'a> {
type Error = KeyError;

fn try_from(v: &'a PrivateKey) -> Result<Self, Self::Error> {
match &v.as_inner().private_key {
private_key_info::PrivateKeyValue::RSA(_) => Err(KeyError::EC {
context: "EC keypair cannot be built from RSA private key".to_string(),
}),
private_key_info::PrivateKeyValue::EC(OctetStringAsn1Container(private_key)) => {
let private_key_data = private_key.private_key.to_vec();
let public_key_data = private_key.public_key.payload_view();

let curve = match &private_key.parameters.0 .0 {
Some(ec_parameters) => match ec_parameters {
picky_asn1_x509::EcParameters::NamedCurve(curve) => {
let oid = Into::<String>::into(&curve.0);
match oid.as_str() {
oids::SECP256R1 => Ok(EcdsaCurve::Nist256),
oids::SECP384R1 => Ok(EcdsaCurve::Nist384),
oids::SECP521R1 => Ok(EcdsaCurve::Nist512),
unknown => Err(KeyError::EC {
context: format!("Unknown curve type: {}", unknown),
}),
}
}
},
None => Err(KeyError::EC {
context: "EC keypair cannot be built when curve type is not provided by private key"
.to_string(),
}),
}?;

if public_key_data.is_empty() {
Err(KeyError::EC {
context:
"EC keypair cannot be built from EC private key that doesn't have a bundled private key"
.to_string(),
})
} else {
Ok(EcdsaKeypair {
private_key: private_key_data,
public_key: public_key_data,
curve,
})
}
}
}
}
}

impl PrivateKey {
pub fn from_rsa_components(
modulus: &BigUint,
Expand Down Expand Up @@ -356,6 +420,31 @@ impl TryFrom<&'_ PublicKey> for RsaPublicKey {
}
}

pub(crate) struct EcdsaPublicKey<'a> {
pub(crate) data: &'a [u8],
}

impl<'a> TryFrom<&'a PublicKey> for EcdsaPublicKey<'a> {
type Error = KeyError;

fn try_from(v: &'a PublicKey) -> Result<Self, Self::Error> {
use picky_asn1_x509::PublicKey as InnerPublicKey;

match &v.as_inner().subject_public_key {
InnerPublicKey::Rsa(_) => Err(KeyError::EC {
context: "EC public key cannot be constructed from RSA public key".to_string(),
}),
InnerPublicKey::Ec(BitStringAsn1(bitstring)) => {
let data = bitstring.payload_view();
Ok(EcdsaPublicKey { data })
}
InnerPublicKey::Ed(_) => Err(KeyError::EC {
context: "EC public key cannot be constructed from ED25519 public key".to_string(),
}),
}
}
}

impl PublicKey {
pub fn from_rsa_components(modulus: &BigUint, public_exponent: &BigUint) -> Self {
PublicKey(SubjectPublicKeyInfo::new_rsa_key(
Expand Down Expand Up @@ -430,6 +519,7 @@ mod tests {
use crate::hash::HashAlgorithm;
use crate::signature::SignatureAlgorithm;
use rsa::PublicKeyParts;
use rstest::*;

cfg_if::cfg_if! { if #[cfg(feature = "x509")] {
use crate::x509::{certificate::CertificateBuilder, date::UtcDate, name::DirectoryName};
Expand Down Expand Up @@ -581,14 +671,6 @@ mod tests {
ring::signature::RsaKeyPair::from_pkcs8(&pkcs8).unwrap();
}

const EC_PRIVATE_KEY_PEM: &str = "-----BEGIN EC PRIVATE KEY-----\n\
MIHcAgEBBEIBhqphIGu2PmlcEb6xADhhSCpgPUulB0s4L2qOgolRgaBx4fNgINFE\n\
mBsSyHJncsWG8WFEuUzAYy/YKz2lP0Qx6Z2gBwYFK4EEACOhgYkDgYYABABwBevJ\n\
w/+Xh6I98ruzoTX3MNTsbgnc+glenJRCbEJkjbJrObFhbfgqP52r1lAy2RxuShGi\n\
NYJJzNPT6vR1abS32QFtvTH7YbYa6OWk9dtGNY/cYxgx1nQyhUuofdW7qbbfu/Ww\n\
TP2oFsPXRAavZCh4AbWUn8bAHmzNRyuJonQBKlQlVQ==\n\
-----END EC PRIVATE KEY-----";

const PKCS8_EC_PRIVATE_KEY_PEM: &str = "-----BEGIN PRIVATE KEY-----\n\
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKZqrmOg/cDZ4tPCn\n\
4LROs145nxx+ssufvflL8cROxFmhRANCAARmU90fCSTsncefY7hVeKw1WIg/YQmT\n\
Expand All @@ -600,18 +682,64 @@ mod tests {
bJM105PImXUuqTMyqSmX96/m7zFfyh/DQQbyXIo3E07qifCPMw9/oQ==\n\
-----END PUBLIC KEY-----";

#[test]
fn private_key_from_ec_pem() {
PrivateKey::from_pem_str(EC_PRIVATE_KEY_PEM).unwrap();
const EC_PRIVATE_KEY_NIST256_PEM: &str = r#"-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICHio5XUa+RbeFfGtGHfbPWehTFJJtCB4/izKHJ9Vm+goAoGCCqGSM49
AwEHoUQDQgAEh7ZqcI6f0tgqq7nqdcxWM6P4GGCfkWc4q11uXFjtXOKHKCV3LzMY
g8/V1PD/YOh0HodRJAjkjXub8AmYxiTcXw==
-----END EC PRIVATE KEY-----"#;

const EC_PRIVATE_KEY_NIST384_PEM: &str = r#"-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDDT8VOfdzHbIRaWOO1F0vgotY2qM2FfYS3zpdKE7Vqbh26hFsUw+iaG
GmGnT+29kg+gBwYFK4EEACKhZANiAAQFvVVUKRdN3/bqaEpDA1aHu8FEd3ujuyS0
AadG6QAiZxH37BGumBcyTTeGHyArqb+GTpsHTUXASbP+P+p5JgkfF9wBMF1SVTvu
ACZOYcqzGbsAXXdMYqewckhc42ye0u0=
-----END EC PRIVATE KEY-----"#;

const EC_PRIVATE_KEY_NIST512_PEM: &str = r#"-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIBhqphIGu2PmlcEb6xADhhSCpgPUulB0s4L2qOgolRgaBx4fNgINFE
mBsSyHJncsWG8WFEuUzAYy/YKz2lP0Qx6Z2gBwYFK4EEACOhgYkDgYYABABwBevJ
w/+Xh6I98ruzoTX3MNTsbgnc+glenJRCbEJkjbJrObFhbfgqP52r1lAy2RxuShGi
NYJJzNPT6vR1abS32QFtvTH7YbYa6OWk9dtGNY/cYxgx1nQyhUuofdW7qbbfu/Ww
TP2oFsPXRAavZCh4AbWUn8bAHmzNRyuJonQBKlQlVQ==
-----END EC PRIVATE KEY-----"#;

#[rstest]
#[case(EC_PRIVATE_KEY_NIST256_PEM)]
#[case(EC_PRIVATE_KEY_NIST384_PEM)]
#[case(EC_PRIVATE_KEY_NIST512_PEM)]
#[case(PKCS8_EC_PRIVATE_KEY_PEM)]
fn private_key_from_ec_pem(#[case] key_pem: &str) {
PrivateKey::from_pem_str(key_pem).unwrap();
}

#[test]
fn private_key_from_pkcs8_ec_pem() {
PrivateKey::from_pem_str(PKCS8_EC_PRIVATE_KEY_PEM).unwrap();
fn public_key_from_ec_pem() {
PublicKey::from_pem_str(EC_PUBLIC_KEY_PEM).unwrap();
}

#[test]
fn public_key_from_ec_pem() {
PublicKey::from_pem_str(EC_PUBLIC_KEY_PEM).unwrap();
fn ecdsa_public_key_conversions() {
// ECDSA public key conversion works
let pk: &PublicKey = &PublicKey::from_pem_str(EC_PUBLIC_KEY_PEM).unwrap();
let epk: Result<EcdsaPublicKey, KeyError> = pk.try_into();
assert!(epk.is_ok());

// PEM public key conversion fails with an error
let pk: &PublicKey = &PublicKey::from_pem_str(RSA_PUBLIC_KEY_PEM).unwrap();
let epk: Result<EcdsaPublicKey, KeyError> = pk.try_into();
assert!(epk.is_err());
assert!(matches!(epk, Err(KeyError::EC { context: _ })));

// TODO: add check for attempted conversion from ED keys - which are not supported yet
}

#[rstest]
#[case(EC_PRIVATE_KEY_NIST256_PEM, EcdsaCurve::Nist256)]
#[case(EC_PRIVATE_KEY_NIST384_PEM, EcdsaCurve::Nist384)]
#[case(EC_PRIVATE_KEY_NIST512_PEM, EcdsaCurve::Nist512)]
fn ecdsa_key_pair_from_ec_private_key(#[case] key: &str, #[case] curve: EcdsaCurve) {
let pk = PrivateKey::from_pem_str(key).unwrap();
let pair = EcdsaKeypair::try_from(&pk).unwrap();
assert_eq!(curve, pair.curve);
}
}
Loading

0 comments on commit 054bc6c

Please sign in to comment.