Migrate RSA PKCS#1 parsing to pure-Rust (#10104)

We no longer let OpenSSL parse anything.
This commit is contained in:
Alex Gaynor 2024-01-03 09:02:55 -05:00 committed by GitHub
parent a542c5429a
commit 01fc9fb6bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 126 additions and 23 deletions

9
src/rust/Cargo.lock generated
View file

@ -70,6 +70,14 @@ dependencies = [
"pyo3",
]
[[package]]
name = "cryptography-key-parsing"
version = "0.1.0"
dependencies = [
"asn1",
"openssl",
]
[[package]]
name = "cryptography-openssl"
version = "0.1.0"
@ -88,6 +96,7 @@ dependencies = [
"cc",
"cfg-if",
"cryptography-cffi",
"cryptography-key-parsing",
"cryptography-openssl",
"cryptography-x509",
"cryptography-x509-verification",

View file

@ -13,6 +13,7 @@ cfg-if = "1"
pyo3 = { version = "0.20", features = ["abi3"] }
asn1 = { version = "0.15.5", default-features = false }
cryptography-cffi = { path = "cryptography-cffi" }
cryptography-key-parsing = { path = "cryptography-key-parsing" }
cryptography-x509 = { path = "cryptography-x509" }
cryptography-x509-verification = { path = "cryptography-x509-verification" }
cryptography-openssl = { path = "cryptography-openssl" }
@ -39,6 +40,7 @@ overflow-checks = true
[workspace]
members = [
"cryptography-cffi",
"cryptography-key-parsing",
"cryptography-openssl",
"cryptography-x509",
"cryptography-x509-verification",

View file

@ -0,0 +1,12 @@
[package]
name = "cryptography-key-parsing"
version = "0.1.0"
authors = ["The cryptography developers <cryptography-dev@python.org>"]
edition = "2021"
publish = false
# This specifies the MSRV
rust-version = "1.63.0"
[dependencies]
asn1 = { version = "0.15.5", default-features = false }
openssl = "0.10.62"

View file

@ -0,0 +1,39 @@
// 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.
pub mod rsa;
pub enum KeyParsingError {
Parse(asn1::ParseError),
OpenSSL(openssl::error::ErrorStack),
}
impl From<asn1::ParseError> for KeyParsingError {
fn from(e: asn1::ParseError) -> KeyParsingError {
KeyParsingError::Parse(e)
}
}
impl From<openssl::error::ErrorStack> for KeyParsingError {
fn from(e: openssl::error::ErrorStack) -> KeyParsingError {
KeyParsingError::OpenSSL(e)
}
}
pub type KeyParsingResult<T> = Result<T, KeyParsingError>;
#[cfg(test)]
mod tests {
use super::KeyParsingError;
#[test]
fn test_key_parsing_error_from() {
let e = openssl::error::ErrorStack::get();
assert!(matches!(
KeyParsingError::from(e),
KeyParsingError::OpenSSL(_)
));
}
}

View file

@ -0,0 +1,23 @@
// 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 Pksc1RsaPublicKey<'a> {
n: asn1::BigUint<'a>,
e: asn1::BigUint<'a>,
}
pub fn parse_pkcs1_rsa_public_key(
data: &[u8],
) -> KeyParsingResult<openssl::pkey::PKey<openssl::pkey::Public>> {
let k = asn1::parse_single::<Pksc1RsaPublicKey>(data)?;
let n = openssl::bn::BigNum::from_slice(k.n.as_bytes())?;
let e = openssl::bn::BigNum::from_slice(k.e.as_bytes())?;
let rsa = openssl::rsa::Rsa::from_public_components(n, e)?;
Ok(openssl::pkey::PKey::from_rsa(rsa)?)
}

View file

@ -6,8 +6,8 @@ use foreign_types_shared::ForeignTypeRef;
use pyo3::IntoPy;
use crate::buf::CffiBuf;
use crate::error::{CryptographyError, CryptographyResult};
use crate::{error, exceptions, types};
use crate::error::{self, CryptographyError, CryptographyResult};
use crate::{exceptions, types};
#[pyo3::prelude::pyfunction]
fn private_key_from_ptr(
@ -86,14 +86,7 @@ pub(crate) fn load_der_public_key_bytes(
// It's not a (RSA/DSA/ECDSA) subjectPublicKeyInfo, but we still need to
// check to see if it is a pure PKCS1 RSA public key (not embedded in a
// subjectPublicKeyInfo)
let rsa = openssl::rsa::Rsa::public_key_from_der_pkcs1(data).or_else(|e| {
let errors = error::list_from_openssl_error(py, e);
Err(types::BACKEND_HANDLE_KEY_LOADING_ERROR
.get(py)?
.call1((errors,))
.unwrap_err())
})?;
let pkey = openssl::pkey::PKey::from_rsa(rsa)?;
let pkey = cryptography_key_parsing::rsa::parse_pkcs1_rsa_public_key(data)?;
public_key_from_pkey(py, &pkey, pkey.id())
}
@ -104,18 +97,18 @@ fn load_pem_public_key(
) -> CryptographyResult<pyo3::PyObject> {
let p = pem::parse(data.as_bytes())?;
let pkey = match p.tag() {
"RSA PUBLIC KEY" => openssl::rsa::Rsa::public_key_from_der_pkcs1(p.contents())
.and_then(openssl::pkey::PKey::from_rsa),
"PUBLIC KEY" => openssl::pkey::PKey::public_key_from_der(p.contents()),
"RSA PUBLIC KEY" => {
cryptography_key_parsing::rsa::parse_pkcs1_rsa_public_key(p.contents())?
}
"PUBLIC KEY" => openssl::pkey::PKey::public_key_from_der(p.contents()).or_else(|e| {
let errors = error::list_from_openssl_error(py, e);
Err(types::BACKEND_HANDLE_KEY_LOADING_ERROR
.get(py)?
.call1((errors,))
.unwrap_err())
})?,
_ => return Err(CryptographyError::from(pem::PemError::MalformedFraming)),
}
.or_else(|e| {
let errors = error::list_from_openssl_error(py, e);
Err(types::BACKEND_HANDLE_KEY_LOADING_ERROR
.get(py)?
.call1((errors,))
.unwrap_err())
})?;
};
public_key_from_pkey(py, &pkey, pkey.id())
}

View file

@ -2,11 +2,12 @@
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
// for complete details.
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use crate::backend::{hashes, utils};
use crate::error::{CryptographyError, CryptographyResult};
use crate::{exceptions, types};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[pyo3::prelude::pyclass(
frozen,

View file

@ -9,6 +9,7 @@ use crate::exceptions;
pub enum CryptographyError {
Asn1Parse(asn1::ParseError),
Asn1Write(asn1::WriteError),
KeyParsing(asn1::ParseError),
Py(pyo3::PyErr),
OpenSSL(openssl::error::ErrorStack),
}
@ -51,6 +52,15 @@ impl From<pem::PemError> for CryptographyError {
}
}
impl From<cryptography_key_parsing::KeyParsingError> for CryptographyError {
fn from(e: cryptography_key_parsing::KeyParsingError) -> CryptographyError {
match e {
cryptography_key_parsing::KeyParsingError::Parse(e) => CryptographyError::KeyParsing(e),
cryptography_key_parsing::KeyParsingError::OpenSSL(e) => CryptographyError::OpenSSL(e),
}
}
}
pub(crate) fn list_from_openssl_error(
py: pyo3::Python<'_>,
error_stack: openssl::error::ErrorStack,
@ -78,6 +88,9 @@ impl From<CryptographyError> for pyo3::PyErr {
"failed to allocate memory while performing ASN.1 serialization",
)
}
CryptographyError::KeyParsing(asn1_error) => pyo3::exceptions::PyValueError::new_err(
format!("Could not deserialize key data. The data may be in an incorrect format, it may be encrypted with an unsupported algorithm, or it may be an unsupported key type (e.g. EC curves with explicit parameters). Details: {asn1_error}"),
),
CryptographyError::Py(py_error) => py_error,
CryptographyError::OpenSSL(error_stack) => pyo3::Python::with_gil(|py| {
let errors = list_from_openssl_error(py, error_stack);
@ -103,6 +116,7 @@ impl CryptographyError {
match self {
CryptographyError::Py(e) => CryptographyError::Py(e),
CryptographyError::Asn1Parse(e) => CryptographyError::Asn1Parse(e.add_location(loc)),
CryptographyError::KeyParsing(e) => CryptographyError::KeyParsing(e.add_location(loc)),
CryptographyError::Asn1Write(e) => CryptographyError::Asn1Write(e),
CryptographyError::OpenSSL(e) => CryptographyError::OpenSSL(e),
}
@ -184,6 +198,12 @@ mod tests {
let e: CryptographyError =
pyo3::PyDowncastError::new(py.None().as_ref(py), "abc").into();
assert!(matches!(e, CryptographyError::Py(_)));
let e = cryptography_key_parsing::KeyParsingError::OpenSSL(
openssl::error::ErrorStack::get(),
)
.into();
assert!(matches!(e, CryptographyError::OpenSSL(_)));
})
}
@ -198,5 +218,9 @@ mod tests {
let openssl_error = openssl::error::ErrorStack::get();
CryptographyError::from(openssl_error).add_location(asn1::ParseLocation::Field("meh"));
let asn1_parse_error = asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue);
CryptographyError::KeyParsing(asn1_parse_error)
.add_location(asn1::ParseLocation::Field("meh"));
}
}