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

Migrate parsing of private keys to Rust #12296

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
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
ddbc80c
Migrate parsing of unencrypted PKCS#8 private keys to Rust
alex Jan 15, 2025
06e56bf
handle unencrypted PEMs as well
alex Jan 17, 2025
94f7d00
fixes
alex Jan 17, 2025
481d0a1
remove boringssl branches
alex Jan 17, 2025
23e5740
ruff
alex Jan 17, 2025
16e80de
fixes
alex Jan 17, 2025
b82f48e
xxx
alex Jan 18, 2025
094283b
this is fine now, I guess
alex Jan 18, 2025
914a681
cleanups
alex Jan 18, 2025
be0cc09
these pass
alex Jan 18, 2025
346c41a
no more xfail!
alex Jan 19, 2025
bedab97
we pass
alex Jan 19, 2025
8e56031
added TODOs to sketch out missing bits
alex Jan 19, 2025
3a279e5
general progress
alex Jan 20, 2025
fb531bb
encrypted pkcs#8 support
alex Jan 22, 2025
9d18dab
3des decryption
alex Jan 22, 2025
1fb119c
PEM decryption
alex Jan 23, 2025
90ca812
forgotten file
alex Jan 23, 2025
215048c
cleanup
alex Jan 23, 2025
14ad0d5
Fix FIPS?
alex Jan 23, 2025
59ca6f0
Unit tests for hex_decode
alex Jan 23, 2025
6339004
Another test
alex Jan 23, 2025
42b3557
replace todos with code where we have tests
alex Jan 24, 2025
b713a97
roundtrip PKCS#12 through us
alex Jan 24, 2025
ed9df89
pass PEM tests
alex Jan 25, 2025
da3633f
Remove unreachable code
alex Jan 25, 2025
41b9597
for coverage
alex Jan 25, 2025
4a171ef
paramiko branch
alex Jan 25, 2025
01c4d27
oops
alex Jan 27, 2025
40acc6f
cleanups and fixes
alex Jan 31, 2025
a7fd179
cleanup
alex Jan 31, 2025
127808c
cleanup
alex Jan 31, 2025
8b10f16
fix
alex Feb 2, 2025
807f8f6
revert that
alex Feb 4, 2025
e736c3c
changelog
alex Feb 4, 2025
c346cf2
Support a variety of additional encryption algorithms
alex Feb 4, 2025
5b9d047
scrypt == no libressl
alex Feb 4, 2025
2ed54d6
remove xfail
alex Feb 5, 2025
f07d79f
unused
alex Feb 5, 2025
b12c203
improve rc2
alex Feb 6, 2025
f283e65
support no rc2
alex Feb 6, 2025
1c8933c
lol
alex Feb 6, 2025
4ad0746
Revert "lol"
alex Feb 6, 2025
1bcff9a
skip on boringssl
alex Feb 6, 2025
fcf6a54
update rust-openssl
alex Feb 6, 2025
dc6c87f
fix
alex Feb 15, 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