Skip to content

Migrate parsing of private keys to Rust #12296

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

Merged
merged 46 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
9cc4b7a
Migrate parsing of unencrypted PKCS#8 private keys to Rust
alex Jan 15, 2025
2ae2258
handle unencrypted PEMs as well
alex Jan 17, 2025
12c4e24
fixes
alex Jan 17, 2025
673d51d
remove boringssl branches
alex Jan 17, 2025
d5b2cd5
ruff
alex Jan 17, 2025
3aef309
fixes
alex Jan 17, 2025
e3d1554
xxx
alex Jan 18, 2025
52bbe80
this is fine now, I guess
alex Jan 18, 2025
ab984a9
cleanups
alex Jan 18, 2025
bde0dbc
these pass
alex Jan 18, 2025
5faca34
no more xfail!
alex Jan 19, 2025
f8164ae
we pass
alex Jan 19, 2025
0c634af
added TODOs to sketch out missing bits
alex Jan 19, 2025
e0cbaec
general progress
alex Jan 20, 2025
8610ec3
encrypted pkcs#8 support
alex Jan 22, 2025
85d3ab2
3des decryption
alex Jan 22, 2025
9fecbe1
PEM decryption
alex Jan 23, 2025
b943e3b
forgotten file
alex Jan 23, 2025
c9d2535
cleanup
alex Jan 23, 2025
ca3c32e
Fix FIPS?
alex Jan 23, 2025
cd5086f
Unit tests for hex_decode
alex Jan 23, 2025
1813a69
Another test
alex Jan 23, 2025
d6775f8
replace todos with code where we have tests
alex Jan 24, 2025
a9acbbf
roundtrip PKCS#12 through us
alex Jan 24, 2025
74f62d2
pass PEM tests
alex Jan 25, 2025
8990907
Remove unreachable code
alex Jan 25, 2025
f7796c8
for coverage
alex Jan 25, 2025
ad269f9
paramiko branch
alex Jan 25, 2025
f371eaa
oops
alex Jan 27, 2025
a83e1fa
cleanups and fixes
alex Jan 31, 2025
2c23cc5
cleanup
alex Jan 31, 2025
b2ec075
cleanup
alex Jan 31, 2025
989964e
fix
alex Feb 2, 2025
853c67a
revert that
alex Feb 4, 2025
47eac4d
changelog
alex Feb 4, 2025
f485b93
Support a variety of additional encryption algorithms
alex Feb 4, 2025
07c483d
scrypt == no libressl
alex Feb 4, 2025
1dc1422
remove xfail
alex Feb 5, 2025
ce58b0e
unused
alex Feb 5, 2025
5d6f93b
improve rc2
alex Feb 6, 2025
878b7cd
support no rc2
alex Feb 6, 2025
a519f0a
lol
alex Feb 6, 2025
4d5d435
Revert "lol"
alex Feb 6, 2025
656b54d
skip on boringssl
alex Feb 6, 2025
054b444
update rust-openssl
alex Feb 6, 2025
281fae4
review comments
alex Apr 11, 2025
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
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ Changelog
provided (previously no exception was raised), and raises a ``TypeError`` if
the key is encrypted but no password is provided (previously a ``ValueError``
was raised).
* We significantly refactored how private key loading (
:func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key`
and
:func:`~cryptography.hazmat.primitives.serialization.load_der_private_key`)
works. This is intended to be backwards compatible for all well-formed keys,
therefore if you discover a key that now raises an exception, please file a
bug with instructions for reproducing.
* Added ``unsafe_skip_rsa_key_validation`` keyword-argument to
:func:`~cryptography.hazmat.primitives.serialization.load_ssh_private_key`.
* Added :class:`~cryptography.hazmat.primitives.hashes.XOFHash` to support
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion src/rust/cryptography-key-parsing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ asn1.workspace = true
cfg-if = "1"
openssl.workspace = true
openssl-sys.workspace = true
cryptography-crypto = { path = "../cryptography-crypto" }
cryptography-x509 = { path = "../cryptography-x509" }

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)'] }
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)', 'cfg(CRYPTOGRAPHY_OSSLCONF, values("OPENSSL_NO_RC2"))'] }
6 changes: 6 additions & 0 deletions src/rust/cryptography-key-parsing/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,10 @@ fn main() {
if env::var("DEP_OPENSSL_BORINGSSL").is_ok() {
println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_BORINGSSL");
}

if let Ok(vars) = env::var("DEP_OPENSSL_CONF") {
for var in vars.split(',') {
println!("cargo:rustc-cfg=CRYPTOGRAPHY_OSSLCONF=\"{var}\"");
}
}
}
31 changes: 31 additions & 0 deletions src/rust/cryptography-key-parsing/src/dsa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// This file is dual licensed under the terms of the Apache License, Version
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
// for complete details.

use crate::KeyParsingResult;

#[derive(asn1::Asn1Read)]
struct DsaPrivateKey<'a> {
version: u8,
p: asn1::BigUint<'a>,
q: asn1::BigUint<'a>,
g: asn1::BigUint<'a>,
pub_key: asn1::BigUint<'a>,
priv_key: asn1::BigUint<'a>,
}

pub fn parse_pkcs1_private_key(
data: &[u8],
) -> KeyParsingResult<openssl::pkey::PKey<openssl::pkey::Private>> {
let dsa_private_key = asn1::parse_single::<DsaPrivateKey<'_>>(data)?;
if dsa_private_key.version != 0 {
return Err(crate::KeyParsingError::InvalidKey);
}
let p = openssl::bn::BigNum::from_slice(dsa_private_key.p.as_bytes())?;
let q = openssl::bn::BigNum::from_slice(dsa_private_key.q.as_bytes())?;
let g = openssl::bn::BigNum::from_slice(dsa_private_key.g.as_bytes())?;
let priv_key = openssl::bn::BigNum::from_slice(dsa_private_key.priv_key.as_bytes())?;
let pub_key = openssl::bn::BigNum::from_slice(dsa_private_key.pub_key.as_bytes())?;
let dsa = openssl::dsa::Dsa::from_private_components(p, q, g, priv_key, pub_key)?;
Ok(openssl::pkey::PKey::from_dsa(dsa)?)
}
54 changes: 54 additions & 0 deletions src/rust/cryptography-key-parsing/src/ec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ use cryptography_x509::common::EcParameters;

use crate::{KeyParsingError, KeyParsingResult};

// From RFC 5915 Section 3
#[derive(asn1::Asn1Read)]
pub(crate) struct EcPrivateKey<'a> {
pub(crate) version: u8,
pub(crate) private_key: &'a [u8],
#[explicit(0)]
pub(crate) parameters: Option<EcParameters<'a>>,
#[explicit(1)]
pub(crate) public_key: Option<asn1::BitString<'a>>,
}

pub(crate) fn ec_params_to_group(
params: &EcParameters<'_>,
) -> KeyParsingResult<openssl::ec::EcGroup> {
Expand Down Expand Up @@ -51,3 +62,46 @@ pub(crate) fn ec_params_to_group(
}
}
}

pub fn parse_pkcs1_private_key(
data: &[u8],
ec_params: Option<EcParameters<'_>>,
) -> KeyParsingResult<openssl::pkey::PKey<openssl::pkey::Private>> {
let ec_private_key = asn1::parse_single::<EcPrivateKey<'_>>(data)?;
if ec_private_key.version != 1 {
return Err(crate::KeyParsingError::InvalidKey);
}

let group = match (ec_params, ec_private_key.parameters) {
(Some(outer_params), Some(inner_params)) => {
if outer_params != inner_params {
return Err(crate::KeyParsingError::InvalidKey);
}
ec_params_to_group(&outer_params)?
}
(Some(outer_params), None) => ec_params_to_group(&outer_params)?,
(None, Some(inner_params)) => ec_params_to_group(&inner_params)?,
(None, None) => return Err(crate::KeyParsingError::InvalidKey),
};

let private_number = openssl::bn::BigNum::from_slice(ec_private_key.private_key)?;
let mut bn_ctx = openssl::bn::BigNumContext::new()?;
let public_point = if let Some(point_bytes) = ec_private_key.public_key {
openssl::ec::EcPoint::from_bytes(&group, point_bytes.as_bytes(), &mut bn_ctx)
.map_err(|_| crate::KeyParsingError::InvalidKey)?
} else {
let mut public_point = openssl::ec::EcPoint::new(&group)?;
public_point
.mul_generator(&group, &private_number, &bn_ctx)
.map_err(|_| crate::KeyParsingError::InvalidKey)?;
public_point
};

let ec_key =
openssl::ec::EcKey::from_private_components(&group, &private_number, &public_point)
.map_err(|_| KeyParsingError::InvalidKey)?;
ec_key
.check_key()
.map_err(|_| KeyParsingError::InvalidKey)?;
Ok(openssl::pkey::PKey::from_ec_key(ec_key)?)
}
7 changes: 6 additions & 1 deletion src/rust/cryptography-key-parsing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)]
#![allow(unknown_lints, clippy::result_large_err)]

mod ec;
pub mod dsa;
pub mod ec;
pub mod pkcs8;
pub mod rsa;
pub mod spki;

Expand All @@ -17,6 +19,9 @@ pub enum KeyParsingError {
UnsupportedEllipticCurve(asn1::ObjectIdentifier),
Parse(asn1::ParseError),
OpenSSL(openssl::error::ErrorStack),
UnsupportedEncryptionAlgorithm(asn1::ObjectIdentifier),
EncryptedKeyWithoutPassword,
IncorrectPassword,
}

impl From<asn1::ParseError> for KeyParsingError {
Expand Down
Loading
Loading