summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2023-05-07 15:26:45 -0500
committerGitHub <noreply@github.com>2023-05-07 20:26:45 +0000
commitb436fafa7cf43c96f66d50162ac495c99ade1f39 (patch)
tree20a69e87bbc1cc2c60c1059941b9c9e88a86adaf /src
parent8834b590ede72c79532ccd34857a6904c48d3634 (diff)
downloadcryptography-b436fafa7cf43c96f66d50162ac495c99ade1f39.tar.gz
add signature_algorithm_parameters to certificate (#8795)
this allows easier verification of cert signatures, but more specifically allows PSS signature verification
Diffstat (limited to 'src')
-rw-r--r--src/cryptography/x509/base.py10
-rw-r--r--src/rust/cryptography-x509/src/common.rs57
-rw-r--r--src/rust/cryptography-x509/src/oid.rs13
-rw-r--r--src/rust/src/x509/certificate.rs130
4 files changed, 200 insertions, 10 deletions
diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py
index 63eaa6bd4..64453eb70 100644
--- a/src/cryptography/x509/base.py
+++ b/src/cryptography/x509/base.py
@@ -17,6 +17,7 @@ from cryptography.hazmat.primitives.asymmetric import (
ec,
ed448,
ed25519,
+ padding,
rsa,
x448,
x25519,
@@ -234,6 +235,15 @@ class Certificate(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
+ def signature_algorithm_parameters(
+ self,
+ ) -> typing.Union[None, padding.PSS, padding.PKCS1v15, ec.ECDSA]:
+ """
+ Returns the signature algorithm parameters.
+ """
+
+ @property
+ @abc.abstractmethod
def extensions(self) -> Extensions:
"""
Returns an Extensions object.
diff --git a/src/rust/cryptography-x509/src/common.rs b/src/rust/cryptography-x509/src/common.rs
index 9668ae237..d09971659 100644
--- a/src/rust/cryptography-x509/src/common.rs
+++ b/src/rust/cryptography-x509/src/common.rs
@@ -6,7 +6,7 @@ use crate::oid;
use asn1::Asn1DefinedByWritable;
use std::marker::PhantomData;
-#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone)]
+#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone, Eq)]
pub struct AlgorithmIdentifier<'a> {
pub oid: asn1::DefinedByMarker<asn1::ObjectIdentifier>,
#[defined_by(oid)]
@@ -55,6 +55,11 @@ pub enum AlgorithmParameters<'a> {
#[defined_by(oid::ECDSA_WITH_SHA3_512_OID)]
EcDsaWithSha3_512,
+ #[defined_by(oid::RSA_WITH_SHA1_OID)]
+ RsaWithSha1(Option<asn1::Null>),
+ #[defined_by(oid::RSA_WITH_SHA1_ALT_OID)]
+ RsaWithSha1Alt(Option<asn1::Null>),
+
#[defined_by(oid::RSA_WITH_SHA224_OID)]
RsaWithSha224(Option<asn1::Null>),
#[defined_by(oid::RSA_WITH_SHA256_OID)]
@@ -73,6 +78,12 @@ pub enum AlgorithmParameters<'a> {
#[defined_by(oid::RSA_WITH_SHA3_512_OID)]
RsaWithSha3_512(Option<asn1::Null>),
+ // RsaPssParameters must be present in Certificate::tbs_cert::signature_alg::params
+ // and Certificate::signature_alg::params, but Certificate::tbs_cert::spki::algorithm::oid
+ // also uses RSASSA_PSS_OID and the params field is omitted since it has no meaning there.
+ #[defined_by(oid::RSASSA_PSS_OID)]
+ RsaPss(Option<Box<RsaPssParameters<'a>>>),
+
#[defined_by(oid::DSA_WITH_SHA224_OID)]
DsaWithSha224,
#[defined_by(oid::DSA_WITH_SHA256_OID)]
@@ -205,6 +216,50 @@ pub struct DHParams<'a> {
pub g: asn1::BigUint<'a>,
pub q: Option<asn1::BigUint<'a>>,
}
+// RSA-PSS ASN.1 default hash algorithm
+pub const PSS_SHA1_HASH_ALG: AlgorithmIdentifier<'_> = AlgorithmIdentifier {
+ oid: asn1::DefinedByMarker::marker(),
+ params: AlgorithmParameters::Sha1(()),
+};
+
+// This is defined as an AlgorithmIdentifier in RFC 4055,
+// but the mask generation algorithm **must** contain an AlgorithmIdentifier
+// in its params, so we define it this way.
+#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq)]
+pub struct MaskGenAlgorithm<'a> {
+ pub oid: asn1::ObjectIdentifier,
+ pub params: AlgorithmIdentifier<'a>,
+}
+
+// RSA-PSS ASN.1 default mask gen algorithm
+pub const PSS_SHA1_MASK_GEN_ALG: MaskGenAlgorithm<'_> = MaskGenAlgorithm {
+ oid: oid::MGF1_OID,
+ params: PSS_SHA1_HASH_ALG,
+};
+
+// From RFC 4055 section 3.1:
+// RSASSA-PSS-params ::= SEQUENCE {
+// hashAlgorithm [0] HashAlgorithm DEFAULT
+// sha1Identifier,
+// maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT
+// mgf1SHA1Identifier,
+// saltLength [2] INTEGER DEFAULT 20,
+// trailerField [3] INTEGER DEFAULT 1 }
+#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq)]
+pub struct RsaPssParameters<'a> {
+ #[explicit(0)]
+ #[default(PSS_SHA1_HASH_ALG)]
+ pub hash_algorithm: AlgorithmIdentifier<'a>,
+ #[explicit(1)]
+ #[default(PSS_SHA1_MASK_GEN_ALG)]
+ pub mask_gen_algorithm: MaskGenAlgorithm<'a>,
+ #[explicit(2)]
+ #[default(20u16)]
+ pub salt_length: u16,
+ #[explicit(3)]
+ #[default(1u8)]
+ _trailer_field: u8,
+}
/// A VisibleString ASN.1 element whose contents is not validated as meeting the
/// requirements (visible characters of IA5), and instead is only known to be
diff --git a/src/rust/cryptography-x509/src/oid.rs b/src/rust/cryptography-x509/src/oid.rs
index b2d22ebdd..ac80b9a31 100644
--- a/src/rust/cryptography-x509/src/oid.rs
+++ b/src/rust/cryptography-x509/src/oid.rs
@@ -57,6 +57,8 @@ pub const ECDSA_WITH_SHA3_384_OID: asn1::ObjectIdentifier =
pub const ECDSA_WITH_SHA3_512_OID: asn1::ObjectIdentifier =
asn1::oid!(2, 16, 840, 1, 101, 3, 4, 3, 12);
+pub const RSA_WITH_SHA1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 5);
+pub const RSA_WITH_SHA1_ALT_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 14, 3, 2, 29);
pub const RSA_WITH_SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 14);
pub const RSA_WITH_SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 11);
pub const RSA_WITH_SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 12);
@@ -84,3 +86,14 @@ pub const SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3,
pub const SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 1);
pub const SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 2);
pub const SHA512_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 2, 3);
+pub const SHA3_224_OID: asn1::ObjectIdentifier =
+ asn1::oid!(1, 3, 6, 1, 4, 1, 37476, 3, 2, 1, 99, 7, 224);
+pub const SHA3_256_OID: asn1::ObjectIdentifier =
+ asn1::oid!(1, 3, 6, 1, 4, 1, 37476, 3, 2, 1, 99, 7, 256);
+pub const SHA3_384_OID: asn1::ObjectIdentifier =
+ asn1::oid!(1, 3, 6, 1, 4, 1, 37476, 3, 2, 1, 99, 7, 384);
+pub const SHA3_512_OID: asn1::ObjectIdentifier =
+ asn1::oid!(1, 3, 6, 1, 4, 1, 37476, 3, 2, 1, 99, 7, 512);
+
+pub const MGF1_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 8);
+pub const RSASSA_PSS_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 1, 10);
diff --git a/src/rust/src/x509/certificate.rs b/src/rust/src/x509/certificate.rs
index 98f1a073f..03d8ae883 100644
--- a/src/rust/src/x509/certificate.rs
+++ b/src/rust/src/x509/certificate.rs
@@ -17,10 +17,28 @@ use cryptography_x509::extensions::{
};
use cryptography_x509::extensions::{Extension, Extensions};
use cryptography_x509::{common, name, oid};
+use once_cell::sync::Lazy;
use pyo3::{IntoPy, ToPyObject};
use std::collections::hash_map::DefaultHasher;
+use std::collections::HashMap;
use std::hash::{Hash, Hasher};
+// This is similar to a hashmap in ocsp.rs but contains more hash algorithms
+// that aren't allowable in OCSP
+static HASH_OIDS_TO_HASH: Lazy<HashMap<&asn1::ObjectIdentifier, &str>> = Lazy::new(|| {
+ let mut h = HashMap::new();
+ h.insert(&oid::SHA1_OID, "SHA1");
+ h.insert(&oid::SHA224_OID, "SHA224");
+ h.insert(&oid::SHA256_OID, "SHA256");
+ h.insert(&oid::SHA384_OID, "SHA384");
+ h.insert(&oid::SHA512_OID, "SHA512");
+ h.insert(&oid::SHA3_224_OID, "SHA3_224");
+ h.insert(&oid::SHA3_256_OID, "SHA3_256");
+ h.insert(&oid::SHA3_384_OID, "SHA3_384");
+ h.insert(&oid::SHA3_512_OID, "SHA3_512");
+ h
+});
+
#[ouroboros::self_referencing]
pub(crate) struct OwnedCertificate {
data: pyo3::Py<pyo3::types::PyBytes>,
@@ -241,15 +259,25 @@ impl Certificate {
let sig_oids_to_hash = py
.import(pyo3::intern!(py, "cryptography.hazmat._oid"))?
.getattr(pyo3::intern!(py, "_SIG_OIDS_TO_HASH"))?;
- let hash_alg = sig_oids_to_hash.get_item(self.signature_algorithm_oid(py)?);
- match hash_alg {
- Ok(data) => Ok(data),
- Err(_) => Err(CryptographyError::from(
- exceptions::UnsupportedAlgorithm::new_err(format!(
- "Signature algorithm OID: {} not recognized",
- self.raw.borrow_value().signature_alg.oid(),
- )),
- )),
+ match &self.raw.borrow_value().signature_alg.params {
+ common::AlgorithmParameters::RsaPss(opt_pss) => {
+ let pss = opt_pss.as_ref().ok_or_else(|| {
+ pyo3::exceptions::PyValueError::new_err("Invalid RSA PSS parameters")
+ })?;
+ hash_oid_py_hash(py, pss.hash_algorithm.oid().clone())
+ }
+ _ => {
+ let hash_alg = sig_oids_to_hash.get_item(self.signature_algorithm_oid(py)?);
+ match hash_alg {
+ Ok(data) => Ok(data),
+ Err(_) => Err(CryptographyError::from(
+ exceptions::UnsupportedAlgorithm::new_err(format!(
+ "Signature algorithm OID: {} not recognized",
+ self.raw.borrow_value().signature_alg.oid()
+ )),
+ )),
+ }
+ }
}
}
@@ -259,6 +287,74 @@ impl Certificate {
}
#[getter]
+ fn signature_algorithm_parameters<'p>(
+ &'p self,
+ py: pyo3::Python<'p>,
+ ) -> CryptographyResult<&'p pyo3::PyAny> {
+ match &self.raw.borrow_value().signature_alg.params {
+ common::AlgorithmParameters::RsaPss(opt_pss) => {
+ let pss = opt_pss.as_ref().ok_or_else(|| {
+ pyo3::exceptions::PyValueError::new_err("Invalid RSA PSS parameters")
+ })?;
+ if pss.mask_gen_algorithm.oid != oid::MGF1_OID {
+ return Err(CryptographyError::from(
+ pyo3::exceptions::PyValueError::new_err(format!(
+ "Unsupported mask generation OID: {}",
+ pss.mask_gen_algorithm.oid
+ )),
+ ));
+ }
+ let py_mask_gen_hash_alg =
+ hash_oid_py_hash(py, pss.mask_gen_algorithm.params.oid().clone())?;
+ let padding = py.import(pyo3::intern!(
+ py,
+ "cryptography.hazmat.primitives.asymmetric.padding"
+ ))?;
+ let py_mgf = padding
+ .getattr(pyo3::intern!(py, "MGF1"))?
+ .call1((py_mask_gen_hash_alg,))?;
+ Ok(padding
+ .getattr(pyo3::intern!(py, "PSS"))?
+ .call1((py_mgf, pss.salt_length))?)
+ }
+ common::AlgorithmParameters::RsaWithSha1(_)
+ | common::AlgorithmParameters::RsaWithSha1Alt(_)
+ | common::AlgorithmParameters::RsaWithSha224(_)
+ | common::AlgorithmParameters::RsaWithSha256(_)
+ | common::AlgorithmParameters::RsaWithSha384(_)
+ | common::AlgorithmParameters::RsaWithSha512(_)
+ | common::AlgorithmParameters::RsaWithSha3_224(_)
+ | common::AlgorithmParameters::RsaWithSha3_256(_)
+ | common::AlgorithmParameters::RsaWithSha3_384(_)
+ | common::AlgorithmParameters::RsaWithSha3_512(_) => {
+ let pkcs = py
+ .import(pyo3::intern!(
+ py,
+ "cryptography.hazmat.primitives.asymmetric.padding"
+ ))?
+ .getattr(pyo3::intern!(py, "PKCS1v15"))?
+ .call0()?;
+ Ok(pkcs)
+ }
+ common::AlgorithmParameters::EcDsaWithSha224
+ | common::AlgorithmParameters::EcDsaWithSha256
+ | common::AlgorithmParameters::EcDsaWithSha384
+ | common::AlgorithmParameters::EcDsaWithSha512
+ | common::AlgorithmParameters::EcDsaWithSha3_224
+ | common::AlgorithmParameters::EcDsaWithSha3_256
+ | common::AlgorithmParameters::EcDsaWithSha3_384
+ | common::AlgorithmParameters::EcDsaWithSha3_512 => Ok(py
+ .import(pyo3::intern!(
+ py,
+ "cryptography.hazmat.primitives.asymmetric.ec"
+ ))?
+ .getattr(pyo3::intern!(py, "ECDSA"))?
+ .call1((self.signature_hash_algorithm(py)?,))?),
+ _ => Ok(py.None().into_ref(py)),
+ }
+ }
+
+ #[getter]
fn extensions(&mut self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::PyObject> {
let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
x509::parse_and_cache_extensions(
@@ -853,6 +949,22 @@ pub fn parse_cert_ext<'p>(
}
}
+fn hash_oid_py_hash(
+ py: pyo3::Python<'_>,
+ oid: asn1::ObjectIdentifier,
+) -> CryptographyResult<&pyo3::PyAny> {
+ let hashes = py.import(pyo3::intern!(py, "cryptography.hazmat.primitives.hashes"))?;
+ match HASH_OIDS_TO_HASH.get(&oid) {
+ Some(alg_name) => Ok(hashes.getattr(*alg_name)?.call0()?),
+ None => Err(CryptographyError::from(
+ exceptions::UnsupportedAlgorithm::new_err(format!(
+ "Signature algorithm OID: {} not recognized",
+ &oid
+ )),
+ )),
+ }
+}
+
pub(crate) fn time_from_py(
py: pyo3::Python<'_>,
val: &pyo3::PyAny,