Skip to content

Commit

Permalink
Support compiling without cryptography primitives (#208)
Browse files Browse the repository at this point in the history
This makes all of the crypto libraries optional. My use case requires
simply generating the binary ASN.1 structures and all cryptography is
handled externally with HSMs.

This change removes unnecessary code and build complexity (for example,
I'm targeting WASM, and I need to take extra steps to build the
dependencies I don't need).

Note to the maintainer: this is a clean (and ideally better) rework of a
previous PR, #207. Because I deleted the older and messier branch, the
previous PR couldn't be updated, so I closed it.
  • Loading branch information
corrideat authored Jan 29, 2024
1 parent 30179f6 commit 4a49584
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 42 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
matrix:
features:
- --all-features
- --no-default-features
- --no-default-features --features ring
- --no-default-features --features aws_lc_rs
- --no-default-features --features aws_lc_rs,pem
Expand Down Expand Up @@ -137,6 +138,8 @@ jobs:
run: cargo test --verbose --features x509-parser --all-targets
- name: Run the tests with aws_lc_rs backend enabled
run: cargo test --verbose --no-default-features --features aws_lc_rs,pem --all-targets
- name: Run the tests with no features enabled
run: cargo test --verbose --no-default-features --all-targets

build:
strategy:
Expand Down
11 changes: 8 additions & 3 deletions rcgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ required-features = ["pem"]
name = "sign-leaf-with-ca"
required-features = ["pem", "x509-parser"]

[[example]]
name = "simple"
required-features = ["crypto"]

[dependencies]
aws-lc-rs = { version = "1.0.0", optional = true }
yasna = { version = "0.5.2", features = ["time", "std"] }
Expand All @@ -31,9 +35,10 @@ x509-parser = { workspace = true, features = ["verify"], optional = true }
zeroize = { version = "1.2", optional = true }

[features]
default = ["pem", "ring"]
aws_lc_rs = ["dep:aws-lc-rs"]
ring = ["dep:ring"]
default = ["crypto", "pem", "ring"]
crypto = []
aws_lc_rs = ["crypto", "dep:aws-lc-rs"]
ring = ["crypto", "dep:ring"]


[package.metadata.docs.rs]
Expand Down
19 changes: 18 additions & 1 deletion rcgen/src/crl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,26 @@ use crate::{Certificate, Error, KeyIdMethod, KeyUsagePurpose, SerialNumber};
/// extern crate rcgen;
/// use rcgen::*;
///
/// #[cfg(not(feature = "crypto"))]
/// struct MyKeyPair { public_key: Vec<u8> }
/// #[cfg(not(feature = "crypto"))]
/// impl RemoteKeyPair for MyKeyPair {
/// fn public_key(&self) -> &[u8] { &self.public_key }
/// fn sign(&self, _: &[u8]) -> Result<Vec<u8>, rcgen::Error> { Ok(vec![]) }
/// fn algorithm(&self) -> &'static SignatureAlgorithm { &PKCS_ED25519 }
/// }
/// # fn main () {
/// // Generate a CRL issuer.
/// let mut issuer_params = CertificateParams::new(vec!["crl.issuer.example.com".to_string()]);
/// let mut issuer_params = CertificateParams::new(vec!["crl.issuer.example.com".to_string()]).unwrap();
/// issuer_params.serial_number = Some(SerialNumber::from(9999));
/// issuer_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
/// issuer_params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::CrlSign];
/// #[cfg(feature = "crypto")]
/// let key_pair = KeyPair::generate().unwrap();
/// #[cfg(not(feature = "crypto"))]
/// let remote_key_pair = MyKeyPair { public_key: vec![] };
/// #[cfg(not(feature = "crypto"))]
/// let key_pair = KeyPair::from_remote(Box::new(remote_key_pair)).unwrap();
/// let issuer = Certificate::generate_self_signed(issuer_params, &key_pair).unwrap();
/// // Describe a revoked certificate.
/// let revoked_cert = RevokedCertParams{
Expand All @@ -42,7 +56,10 @@ use crate::{Certificate, Error, KeyIdMethod, KeyUsagePurpose, SerialNumber};
/// crl_number: SerialNumber::from(1234),
/// issuing_distribution_point: None,
/// revoked_certs: vec![revoked_cert],
/// #[cfg(feature = "crypto")]
/// key_identifier_method: KeyIdMethod::Sha256,
/// #[cfg(not(feature = "crypto"))]
/// key_identifier_method: KeyIdMethod::PreSpecified(vec![]),
/// };
/// let crl = CertificateRevocationList::from_params(crl).unwrap();
///# }
Expand Down
5 changes: 5 additions & 0 deletions rcgen/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub enum Error {
InvalidCrlNextUpdate,
/// CRL issuer specifies Key Usages that don't include cRLSign.
IssuerNotCrlSigner,
#[cfg(not(feature = "crypto"))]
/// Missing serial number
MissingSerialNumber,
}

impl fmt::Display for Error {
Expand Down Expand Up @@ -86,6 +89,8 @@ impl fmt::Display for Error {
f,
"CRL issuer must specify no key usage, or key usage including cRLSign"
)?,
#[cfg(not(feature = "crypto"))]
MissingSerialNumber => write!(f, "A serial number must be specified")?,
};
Ok(())
}
Expand Down
58 changes: 46 additions & 12 deletions rcgen/src/key_pair.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
#[cfg(feature = "pem")]
use pem::Pem;
#[cfg(feature = "crypto")]
use std::convert::TryFrom;
use std::fmt;
use yasna::DERWriter;

#[cfg(any(feature = "crypto", feature = "pem"))]
use crate::error::ExternalError;
use crate::ring_like::error as ring_error;
use crate::ring_like::rand::SystemRandom;
use crate::ring_like::signature::{
self, EcdsaKeyPair, Ed25519KeyPair, KeyPair as RingKeyPair, RsaEncoding, RsaKeyPair,
#[cfg(feature = "crypto")]
use crate::ring_like::{
error as ring_error,
rand::SystemRandom,
signature::{
self, EcdsaKeyPair, Ed25519KeyPair, KeyPair as RingKeyPair, RsaEncoding, RsaKeyPair,
},
{ecdsa_from_pkcs8, rsa_key_pair_public_modulus_len},
};
use crate::ring_like::{ecdsa_from_pkcs8, rsa_key_pair_public_modulus_len};
use crate::sign_algo::algo::*;
use crate::sign_algo::SignAlgo;
#[cfg(feature = "crypto")]
use crate::sign_algo::{algo::*, SignAlgo};
#[cfg(feature = "pem")]
use crate::ENCODE_CONFIG;
use crate::{Error, SignatureAlgorithm};
use crate::{sign_algo::SignatureAlgorithm, Error};

/// A key pair variant
#[allow(clippy::large_enum_variant)]
pub(crate) enum KeyPairKind {
/// A Ecdsa key pair
#[cfg(feature = "crypto")]
Ec(EcdsaKeyPair),
/// A Ed25519 key pair
#[cfg(feature = "crypto")]
Ed(Ed25519KeyPair),
/// A RSA key pair
#[cfg(feature = "crypto")]
Rsa(RsaKeyPair, &'static dyn RsaEncoding),
/// A remote key pair
Remote(Box<dyn RemoteKeyPair + Send + Sync>),
Expand All @@ -33,8 +41,11 @@ pub(crate) enum KeyPairKind {
impl fmt::Debug for KeyPairKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
#[cfg(feature = "crypto")]
Self::Ec(key_pair) => write!(f, "{:?}", key_pair),
#[cfg(feature = "crypto")]
Self::Ed(key_pair) => write!(f, "{:?}", key_pair),
#[cfg(feature = "crypto")]
Self::Rsa(key_pair, _) => write!(f, "{:?}", key_pair),
Self::Remote(_) => write!(f, "Box<dyn RemotePrivateKey>"),
}
Expand All @@ -57,13 +68,15 @@ pub struct KeyPair {

impl KeyPair {
/// Generate a new random [`PKCS_ECDSA_P256_SHA256`] key pair
#[cfg(feature = "crypto")]
pub fn generate() -> Result<Self, Error> {
Self::generate_for(&PKCS_ECDSA_P256_SHA256)
}

/// Generate a new random key pair for the specified signature algorithm
///
/// If you're not sure which algorithm to use, [`PKCS_ECDSA_P256_SHA256`] is a good choice.
#[cfg(feature = "crypto")]
pub fn generate_for(alg: &'static SignatureAlgorithm) -> Result<Self, Error> {
let rng = &SystemRandom::new();

Expand Down Expand Up @@ -102,6 +115,7 @@ impl KeyPair {
/// Parses the key pair from the DER format
///
/// Equivalent to using the [`TryFrom`] implementation.
#[cfg(feature = "crypto")]
pub fn from_der(der: &[u8]) -> Result<Self, Error> {
Ok(der.try_into()?)
}
Expand All @@ -112,7 +126,7 @@ impl KeyPair {
}

/// Parses the key pair from the ASCII PEM format
#[cfg(feature = "pem")]
#[cfg(all(feature = "pem", feature = "crypto"))]
pub fn from_pem(pem_str: &str) -> Result<Self, Error> {
let private_key = pem::parse(pem_str)._err()?;
let private_key_der: &[_] = private_key.contents();
Expand All @@ -132,7 +146,7 @@ impl KeyPair {
/// using the specified [`SignatureAlgorithm`]
///
/// Same as [from_pem_and_sign_algo](Self::from_pem_and_sign_algo).
#[cfg(feature = "pem")]
#[cfg(all(feature = "pem", feature = "crypto"))]
pub fn from_pem_and_sign_algo(
pem_str: &str,
alg: &'static SignatureAlgorithm,
Expand All @@ -151,6 +165,7 @@ impl KeyPair {
/// key pair. However, sometimes multiple signature algorithms fit for the
/// same der key. In that instance, you can use this function to precisely
/// specify the `SignatureAlgorithm`.
#[cfg(feature = "crypto")]
pub fn from_der_and_sign_algo(
pkcs8: &[u8],
alg: &'static SignatureAlgorithm,
Expand Down Expand Up @@ -195,6 +210,7 @@ impl KeyPair {
})
}

#[cfg(feature = "crypto")]
pub(crate) fn from_raw(
pkcs8: &[u8],
) -> Result<(KeyPairKind, &'static SignatureAlgorithm), Error> {
Expand Down Expand Up @@ -242,17 +258,20 @@ impl KeyPair {

pub(crate) fn sign(&self, msg: &[u8], writer: DERWriter) -> Result<(), Error> {
match &self.kind {
#[cfg(feature = "crypto")]
KeyPairKind::Ec(kp) => {
let system_random = SystemRandom::new();
let signature = kp.sign(&system_random, msg)._err()?;
let sig = &signature.as_ref();
writer.write_bitvec_bytes(&sig, &sig.len() * 8);
},
#[cfg(feature = "crypto")]
KeyPairKind::Ed(kp) => {
let signature = kp.sign(msg);
let sig = &signature.as_ref();
writer.write_bitvec_bytes(&sig, &sig.len() * 8);
},
#[cfg(feature = "crypto")]
KeyPairKind::Rsa(kp, padding_alg) => {
let system_random = SystemRandom::new();
let mut signature = vec![0; rsa_key_pair_public_modulus_len(kp)];
Expand Down Expand Up @@ -292,6 +311,7 @@ impl KeyPair {
///
/// Panics if called on a remote key pair.
pub fn serialize_der(&self) -> Vec<u8> {
#[cfg_attr(not(feature = "crypto"), allow(irrefutable_let_patterns))]
if let KeyPairKind::Remote(_) = self.kind {
panic!("Serializing a remote key pair is not supported")
}
Expand All @@ -304,6 +324,7 @@ impl KeyPair {
///
/// Panics if called on a remote key pair.
pub fn serialized_der(&self) -> &[u8] {
#[cfg_attr(not(feature = "crypto"), allow(irrefutable_let_patterns))]
if let KeyPairKind::Remote(_) = self.kind {
panic!("Serializing a remote key pair is not supported")
}
Expand All @@ -313,6 +334,7 @@ impl KeyPair {

/// Access the remote key pair if it is a remote one
pub fn as_remote(&self) -> Option<&(dyn RemoteKeyPair + Send + Sync)> {
#[cfg_attr(not(feature = "crypto"), allow(irrefutable_let_patterns))]
if let KeyPairKind::Remote(remote) = &self.kind {
Some(remote.as_ref())
} else {
Expand All @@ -329,6 +351,7 @@ impl KeyPair {
}
}

#[cfg(feature = "crypto")]
impl TryFrom<&[u8]> for KeyPair {
type Error = Error;

Expand All @@ -342,6 +365,7 @@ impl TryFrom<&[u8]> for KeyPair {
}
}

#[cfg(feature = "crypto")]
impl TryFrom<Vec<u8>> for KeyPair {
type Error = Error;

Expand All @@ -361,8 +385,11 @@ impl PublicKeyData for KeyPair {
}
fn raw_bytes(&self) -> &[u8] {
match &self.kind {
#[cfg(feature = "crypto")]
KeyPairKind::Ec(kp) => kp.public_key().as_ref(),
#[cfg(feature = "crypto")]
KeyPairKind::Ed(kp) => kp.public_key().as_ref(),
#[cfg(feature = "crypto")]
KeyPairKind::Rsa(kp, _) => kp.public_key().as_ref(),
KeyPairKind::Remote(kp) => kp.public_key(),
}
Expand All @@ -384,12 +411,14 @@ pub trait RemoteKeyPair {
fn algorithm(&self) -> &'static SignatureAlgorithm;
}

#[cfg(feature = "crypto")]
impl<T> ExternalError<T> for Result<T, ring_error::KeyRejected> {
fn _err(self) -> Result<T, Error> {
self.map_err(|e| Error::RingKeyRejected(e.to_string()))
}
}

#[cfg(feature = "crypto")]
impl<T> ExternalError<T> for Result<T, ring_error::Unspecified> {
fn _err(self) -> Result<T, Error> {
self.map_err(|_| Error::RingUnspecified)
Expand Down Expand Up @@ -419,11 +448,16 @@ pub(crate) trait PublicKeyData {

#[cfg(test)]
mod test {
#[cfg(crypto)]
use super::*;

use crate::ring_like::rand::SystemRandom;
use crate::ring_like::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
#[cfg(crypto)]
use crate::ring_like::{
rand::SystemRandom,
signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING},
};

#[cfg(crypto)]
#[test]
fn test_algorithm() {
let rng = SystemRandom::new();
Expand Down
Loading

0 comments on commit 4a49584

Please sign in to comment.