Skip to content

Commit

Permalink
rsa: support PKCS#1 encoding of private keys
Browse files Browse the repository at this point in the history
  • Loading branch information
ctz committed Jan 2, 2025
1 parent c14728e commit de14527
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 7 deletions.
45 changes: 45 additions & 0 deletions graviola/src/high/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,35 @@ impl SigningKey {
Ok(Self(priv_key))
}

/// Encodes an RSA signing key to PKCS#1 DER format.
///
/// `output` is the output buffer, and the encoding is written to the start
/// of this buffer. An error is returned if the encoding is larger than
/// the supplied buffer. Otherwise, on success, the range containing the
/// encoding is returned.
pub fn to_pkcs1_der<'a>(&self, output: &'a mut [u8]) -> Result<&'a [u8], Error> {
let _ = Entry::new_secret();

let mut buf = rsa_priv::RsaComponentsBuffer::default();
let components = self.0.encode_components(&mut buf)?;

let used = pkix::RSAPrivateKey {
version: pkix::Version::two_prime,
modulus: asn1::Integer::new(components.public_modulus),
publicExponent: asn1::Integer::new(components.public_exponent),
prime1: asn1::Integer::new(components.p),
prime2: asn1::Integer::new(components.q),
privateExponent: asn1::Integer::new(components.d),
exponent1: asn1::Integer::new(components.dp),
exponent2: asn1::Integer::new(components.dq),
coefficient: asn1::Integer::new(components.iqmp),
}
.encode(&mut asn1::Encoder::new(output))
.map_err(Error::Asn1Error)?;

output.get(..used).ok_or(Error::WrongLength)
}

/// Decodes an RSA signing key from PKCS#8 DER format.
///
/// This format is defined in
Expand Down Expand Up @@ -468,4 +497,20 @@ mod tests {

check_all_algs(&mut [0u8; 1024], &private_key, &private_key.public_key());
}

#[test]
fn pairwise_key_formatting() {
check_pkcs1(include_bytes!("rsa/rsa2048.der"));
check_pkcs1(include_bytes!("rsa/rsa3072.der"));
check_pkcs1(include_bytes!("rsa/rsa4096.der"));
check_pkcs1(include_bytes!("rsa/rsa6144.der"));
check_pkcs1(include_bytes!("rsa/rsa8192.der"));
}

fn check_pkcs1(pkcs1_der: &[u8]) {
let decoded = SigningKey::from_pkcs1_der(pkcs1_der).unwrap();
let mut buffer = [0u8; 8192];
let encoded = decoded.to_pkcs1_der(&mut buffer).unwrap();
assert_eq!(encoded, pkcs1_der);
}
}
25 changes: 20 additions & 5 deletions graviola/src/low/posint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,30 @@ impl<const N: usize> PosInt<N> {

pub(crate) fn to_bytes<'a>(&self, out: &'a mut [u8]) -> Result<&'a [u8], Error> {
let required_bytes = self.used * 8;
let out = out.get_mut(..required_bytes).ok_or(Error::OutOfRange)?;

if out.len() < required_bytes {
return Err(Error::OutOfRange);
for (chunk, word) in out.chunks_exact_mut(8).rev().zip(self.as_words().iter()) {
chunk.copy_from_slice(&word.to_be_bytes());
}

let out = &mut out[..required_bytes];
Ok(out)
}

for (chunk, word) in out.chunks_exact_mut(8).rev().zip(self.as_words().iter()) {
chunk.copy_from_slice(&word.to_be_bytes());
/// Like `to_bytes`, but guarantees a zero byte prefix.
///
/// This means, if the result is used in an ASN.1 encoded integer, the encoding
/// is positive.
pub(crate) fn to_bytes_asn1<'a>(&self, out: &'a mut [u8]) -> Result<&'a [u8], Error> {
let required_bytes = self.used * 8 + 1;
let out = out.get_mut(..required_bytes).ok_or(Error::OutOfRange)?;

out[0] = 0x00;
{
let (_, val) = out.split_at_mut(1);

for (chunk, word) in val.chunks_exact_mut(8).rev().zip(self.as_words().iter()) {
chunk.copy_from_slice(&word.to_be_bytes());
}
}

Ok(out)
Expand Down
74 changes: 73 additions & 1 deletion graviola/src/mid/rsa_priv.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Written for Graviola by Joe Birr-Pixton, 2024.
// SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT-0

use super::rsa_pub::RsaPublicKey;
use super::rsa_pub::{RsaPublicKey, MAX_PUBLIC_MODULUS_BYTES};
use crate::error::Error;
use crate::low;

Expand All @@ -13,6 +13,7 @@ pub(crate) struct RsaPrivateKey {
d: RsaPosIntD,
dp: RsaPosIntModP,
dq: RsaPosIntModP,
iqmp: RsaPosIntModP,

iqmp_mont: RsaPosIntModP,
p_montifier: RsaPosIntModP,
Expand Down Expand Up @@ -56,6 +57,7 @@ impl RsaPrivateKey {
d,
dp,
dq,
iqmp,
iqmp_mont,
p_montifier,
q_montifier,
Expand All @@ -72,6 +74,41 @@ impl RsaPrivateKey {
self.public.modulus_len_bytes()
}

pub(crate) fn encode_components<'a>(
&self,
buffer: &'a mut RsaComponentsBuffer,
) -> Result<RsaComponents<'a>, Error> {
let (public_modulus, buffer) = buffer.0.split_at_mut(MAX_PUBLIC_MODULUS_BYTES + 1);
let (public_exponent, buffer) = buffer.split_at_mut(4);
let (p, buffer) = buffer.split_at_mut(MAX_PRIVATE_MODULUS_BYTES + 1);
let (q, buffer) = buffer.split_at_mut(MAX_PRIVATE_MODULUS_BYTES + 1);
let (d, buffer) = buffer.split_at_mut(MAX_PUBLIC_MODULUS_BYTES + 1);
let (dp, buffer) = buffer.split_at_mut(MAX_PRIVATE_MODULUS_BYTES + 1);
let (dq, buffer) = buffer.split_at_mut(MAX_PRIVATE_MODULUS_BYTES + 1);
let (iqmp, _) = buffer.split_at_mut(MAX_PRIVATE_MODULUS_BYTES + 1);

let public_modulus = self.public.n.to_bytes_asn1(public_modulus)?;
public_exponent.copy_from_slice(&self.public.e.to_be_bytes());

let p = self.p.to_bytes_asn1(p)?;
let q = self.q.to_bytes_asn1(q)?;
let d = self.d.to_bytes_asn1(d)?;
let dp = self.dp.to_bytes_asn1(dp)?;
let dq = self.dq.to_bytes_asn1(dq)?;
let iqmp = self.iqmp.to_bytes_asn1(iqmp)?;

Ok(RsaComponents {
public_modulus,
public_exponent,
p,
q,
d,
dp,
dq,
iqmp,
})
}

/// returns c ^ d mod n
///
/// (albeit via CRT)
Expand Down Expand Up @@ -127,6 +164,41 @@ impl Drop for RsaPrivateKey {
}
}

pub(crate) struct RsaComponents<'a> {
pub(crate) public_modulus: &'a [u8],
pub(crate) public_exponent: &'a [u8],
pub(crate) p: &'a [u8],
pub(crate) q: &'a [u8],
pub(crate) d: &'a [u8],
pub(crate) dp: &'a [u8],
pub(crate) dq: &'a [u8],
pub(crate) iqmp: &'a [u8],
}

pub(crate) struct RsaComponentsBuffer([u8; Self::LEN]);

impl RsaComponentsBuffer {
const LEN: usize =
// public modulus and private exponent
(MAX_PUBLIC_MODULUS_BYTES + 1) * 2 +
// public exponent
4 +
// private moduli and crt components
(MAX_PRIVATE_MODULUS_BYTES + 1) * 5;
}

impl Drop for RsaComponentsBuffer {
fn drop(&mut self) {
low::zeroise(&mut self.0);
}
}

impl Default for RsaComponentsBuffer {
fn default() -> Self {
Self([0u8; 4619])
}
}

const MAX_PRIVATE_MODULUS_BITS: usize = 4096;
const MAX_PRIVATE_MODULUS_WORDS: usize = MAX_PRIVATE_MODULUS_BITS / 64;
pub(crate) const MAX_PRIVATE_MODULUS_BYTES: usize = MAX_PRIVATE_MODULUS_BITS / 8;
Expand Down
2 changes: 1 addition & 1 deletion graviola/src/mid/rsa_pub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::low;
#[derive(Clone, Debug)]
pub(crate) struct RsaPublicKey {
pub(crate) n: RsaPosInt,
e: u32,
pub(crate) e: u32,

montifier: RsaPosInt,
one: RsaPosInt,
Expand Down

0 comments on commit de14527

Please sign in to comment.