Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable signing without private key #291

Merged
merged 6 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions rcgen/src/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use yasna::{DERWriter, Tag};

use crate::crl::CrlDistributionPoint;
use crate::csr::CertificateSigningRequest;
use crate::key_pair::PublicKeyData;
use crate::key_pair::{serialize_public_key_der, PublicKeyData};
#[cfg(feature = "crypto")]
use crate::ring_like::digest;
#[cfg(feature = "pem")]
Expand Down Expand Up @@ -149,7 +149,7 @@ impl CertificateParams {
/// [`Certificate::pem`].
pub fn signed_by(
self,
key_pair: &KeyPair,
public_key: &impl PublicKeyData,
issuer: &Certificate,
issuer_key: &KeyPair,
) -> Result<Certificate, Error> {
Expand All @@ -160,8 +160,9 @@ impl CertificateParams {
key_pair: issuer_key,
};

let subject_public_key_info = key_pair.public_key_der();
let der = self.serialize_der_with_signer(key_pair, issuer)?;
let subject_public_key_info =
yasna::construct_der(|writer| serialize_public_key_der(public_key, writer));
let der = self.serialize_der_with_signer(public_key, issuer)?;
Ok(Certificate {
params: self,
subject_public_key_info,
Expand Down Expand Up @@ -547,7 +548,7 @@ impl CertificateParams {
// Write subject name
write_distinguished_name(writer.next(), distinguished_name);
// Write subjectPublicKeyInfo
subject_key.serialize_public_key_der(writer.next());
serialize_public_key_der(subject_key, writer.next());
// Write extensions
// According to the spec in RFC 2986, even if attributes are empty we need the empty attribute tag
writer.next().write_tagged(Tag::context(0), |writer| {
Expand Down Expand Up @@ -596,7 +597,7 @@ impl CertificateParams {
) -> Result<CertificateDer<'static>, Error> {
let der = issuer.key_pair.sign_der(|writer| {
let pub_key_spki =
yasna::construct_der(|writer| pub_key.serialize_public_key_der(writer));
yasna::construct_der(|writer| serialize_public_key_der(pub_key, writer));
// Write version
writer.next().write_tagged(Tag::context(0), |writer| {
writer.write_u8(2);
Expand All @@ -607,7 +608,7 @@ impl CertificateParams {
} else {
#[cfg(feature = "crypto")]
{
let hash = digest::digest(&digest::SHA256, pub_key.raw_bytes());
let hash = digest::digest(&digest::SHA256, pub_key.der_bytes());
// RFC 5280 specifies at most 20 bytes for a serial number
let mut sl = hash.as_ref()[0..20].to_vec();
sl[0] &= 0x7f; // MSB must be 0 to ensure encoding bignum in 20 bytes
Expand All @@ -633,7 +634,7 @@ impl CertificateParams {
// Write subject
write_distinguished_name(writer.next(), &self.distinguished_name);
// Write subjectPublicKeyInfo
pub_key.serialize_public_key_der(writer.next());
serialize_public_key_der(pub_key, writer.next());
// write extensions
let should_write_exts = self.use_authority_key_identifier_extension
|| !self.subject_alt_names.is_empty()
Expand Down
13 changes: 7 additions & 6 deletions rcgen/src/csr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use pki_types::CertificateSigningRequestDer;
#[cfg(feature = "pem")]
use crate::ENCODE_CONFIG;
use crate::{
Certificate, CertificateParams, Error, Issuer, KeyPair, PublicKeyData, SignatureAlgorithm,
key_pair::serialize_public_key_der, Certificate, CertificateParams, Error, Issuer, KeyPair,
PublicKeyData, SignatureAlgorithm,
};
#[cfg(feature = "x509-parser")]
use crate::{DistinguishedName, SanType};
Expand All @@ -27,12 +28,12 @@ impl PublicKey {
}

impl PublicKeyData for PublicKey {
fn alg(&self) -> &SignatureAlgorithm {
self.alg
fn der_bytes(&self) -> &[u8] {
&self.raw
}

fn raw_bytes(&self) -> &[u8] {
&self.raw
fn algorithm(&self) -> &SignatureAlgorithm {
self.alg
}
}

Expand Down Expand Up @@ -214,7 +215,7 @@ impl CertificateSigningRequestParams {
.params
.serialize_der_with_signer(&self.public_key, issuer)?;
let subject_public_key_info = yasna::construct_der(|writer| {
self.public_key.serialize_public_key_der(writer);
serialize_public_key_der(&self.public_key, writer);
});
Ok(Certificate {
params: self.params,
Expand Down
5 changes: 5 additions & 0 deletions rcgen/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pub enum Error {
#[cfg(not(feature = "crypto"))]
/// Missing serial number
MissingSerialNumber,
/// X509 parsing error
#[cfg(feature = "x509-parser")]
X509(String),
}

impl fmt::Display for Error {
Expand Down Expand Up @@ -91,6 +94,8 @@ impl fmt::Display for Error {
)?,
#[cfg(not(feature = "crypto"))]
MissingSerialNumber => write!(f, "A serial number must be specified")?,
#[cfg(feature = "x509-parser")]
X509(e) => write!(f, "X.509 parsing error: {e}")?,
};
Ok(())
}
Expand Down
118 changes: 104 additions & 14 deletions rcgen/src/key_pair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ impl KeyPair {
/// would output, and how [`ring::signature::UnparsedPublicKey::verify`]
/// would accept.
pub fn public_key_raw(&self) -> &[u8] {
self.raw_bytes()
self.der_bytes()
}

/// Check if this key pair can be used with the given signature algorithm
Expand Down Expand Up @@ -457,7 +457,7 @@ impl KeyPair {
/// X.509.
/// See [RFC 5280 section 4.1](https://tools.ietf.org/html/rfc5280#section-4.1).
pub fn public_key_der(&self) -> Vec<u8> {
yasna::construct_der(|writer| self.serialize_public_key_der(writer))
yasna::construct_der(|writer| serialize_public_key_der(self, writer))
}

/// Return the key pair's public key in PEM format
Expand Down Expand Up @@ -637,10 +637,7 @@ pub enum RsaKeySize {
}

impl PublicKeyData for KeyPair {
fn alg(&self) -> &SignatureAlgorithm {
self.alg
}
fn raw_bytes(&self) -> &[u8] {
fn der_bytes(&self) -> &[u8] {
match &self.kind {
#[cfg(feature = "crypto")]
KeyPairKind::Ec(kp) => kp.public_key().as_ref(),
Expand All @@ -651,6 +648,10 @@ impl PublicKeyData for KeyPair {
KeyPairKind::Remote(kp) => kp.public_key(),
}
}

fn algorithm(&self) -> &SignatureAlgorithm {
self.alg
}
}

/// A private key that is not directly accessible, but can be used to sign messages
Expand Down Expand Up @@ -689,20 +690,85 @@ impl<T> ExternalError<T> for Result<T, pem::PemError> {
}
}

pub(crate) trait PublicKeyData {
fn alg(&self) -> &SignatureAlgorithm;
/// A public key
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SubjectPublicKeyInfo {
pub(crate) alg: &'static SignatureAlgorithm,
pub(crate) subject_public_key: Vec<u8>,
}

fn raw_bytes(&self) -> &[u8];
impl SubjectPublicKeyInfo {
/// Create a `SubjectPublicKey` value from a PEM-encoded SubjectPublicKeyInfo string
#[cfg(all(feature = "x509-parser", feature = "pem"))]
pub fn from_pem(pem_str: &str) -> Result<Self, Error> {
Self::from_der(&pem::parse(pem_str)._err()?.into_contents())
}

fn serialize_public_key_der(&self, writer: DERWriter) {
writer.write_sequence(|writer| {
self.alg().write_oids_sign_alg(writer.next());
let pk = self.raw_bytes();
writer.next().write_bitvec_bytes(pk, pk.len() * 8);
/// Create a `SubjectPublicKey` value from DER-encoded SubjectPublicKeyInfo bytes
#[cfg(feature = "x509-parser")]
pub fn from_der(spki_der: &[u8]) -> Result<Self, Error> {
use x509_parser::{
prelude::FromDer,
x509::{AlgorithmIdentifier, SubjectPublicKeyInfo},
};

let (rem, spki) =
SubjectPublicKeyInfo::from_der(spki_der).map_err(|e| Error::X509(e.to_string()))?;
if !rem.is_empty() {
return Err(Error::X509(
"trailing bytes in SubjectPublicKeyInfo".to_string(),
));
}

let alg = SignatureAlgorithm::iter()
.find(|alg| {
let bytes = yasna::construct_der(|writer| {
alg.write_oids_sign_alg(writer);
});
let Ok((rest, aid)) = AlgorithmIdentifier::from_der(&bytes) else {
return false;
};
if !rest.is_empty() {
return false;
}
aid == spki.algorithm
})
.ok_or(Error::UnsupportedSignatureAlgorithm)?;

Ok(Self {
alg,
subject_public_key: Vec::from(spki.subject_public_key.as_ref()),
})
}
}

impl PublicKeyData for SubjectPublicKeyInfo {
fn der_bytes(&self) -> &[u8] {
&self.subject_public_key
}

fn algorithm(&self) -> &SignatureAlgorithm {
self.alg
}
}

/// The public key data of a key pair
pub trait PublicKeyData {
/// The public key in DER format
fn der_bytes(&self) -> &[u8];

/// The algorithm used by the key pair
fn algorithm(&self) -> &SignatureAlgorithm;
}

pub(crate) fn serialize_public_key_der(key: &impl PublicKeyData, writer: DERWriter) {
writer.write_sequence(|writer| {
key.algorithm().write_oids_sign_alg(writer.next());
let pk = key.der_bytes();
writer.next().write_bitvec_bytes(pk, pk.len() * 8);
})
}

#[cfg(all(test, feature = "crypto"))]
mod test {
use super::*;
Expand All @@ -712,6 +778,30 @@ mod test {
signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING},
};

#[cfg(all(feature = "x509-parser", feature = "pem"))]
#[test]
fn test_subject_public_key_parsing() {
for alg in [
&PKCS_ED25519,
&PKCS_ECDSA_P256_SHA256,
&PKCS_ECDSA_P384_SHA384,
#[cfg(feature = "aws_lc_rs")]
&PKCS_ECDSA_P521_SHA512,
#[cfg(feature = "aws_lc_rs")]
&PKCS_RSA_SHA256,
] {
let kp = KeyPair::generate_for(alg).expect("keygen");
let pem = kp.public_key_pem();
let der = kp.public_key_der();

let pkd_pem = SubjectPublicKeyInfo::from_pem(&pem).expect("from pem");
assert_eq!(kp.der_bytes(), pkd_pem.der_bytes());

let pkd_der = SubjectPublicKeyInfo::from_der(&der).expect("from der");
assert_eq!(kp.der_bytes(), pkd_der.der_bytes());
}
}

#[test]
fn test_algorithm() {
let rng = SystemRandom::new();
Expand Down
4 changes: 2 additions & 2 deletions rcgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ pub use crl::{
};
pub use csr::{CertificateSigningRequest, CertificateSigningRequestParams, PublicKey};
pub use error::{Error, InvalidAsn1String};
use key_pair::PublicKeyData;
pub use key_pair::PublicKeyData;
#[cfg(all(feature = "crypto", feature = "aws_lc_rs"))]
pub use key_pair::RsaKeySize;
pub use key_pair::{KeyPair, RemoteKeyPair};
pub use key_pair::{KeyPair, RemoteKeyPair, SubjectPublicKeyInfo};
#[cfg(feature = "crypto")]
use ring_like::digest;
pub use sign_algo::algo::*;
Expand Down