mirror of
https://github.com/saymrwulf/cryptography.git
synced 2026-05-14 20:37:55 +00:00
Add Certificate.verify_signed_by (#8011)
* Add Certificate.verify_signed_by Verify that the signature on a certificate was created by the private key belonging to another certificate's public key. This code does not validate anything else! It is not a path builder, general x509 validator, etc. * switch to issued_by validate issuer subject matches certificate issuer and refactor * two fixes * signed_by isn't the right target now * coverage * skip test on some *ssls * extensive refactoring * lol * does any of this work * final commit i swear
This commit is contained in:
parent
796ebf6702
commit
db7dd61de3
6 changed files with 550 additions and 3 deletions
|
|
@ -24,6 +24,8 @@ Changelog
|
|||
continues to support only public keys.
|
||||
* Added support for generating SSH certificates with
|
||||
:class:`~cryptography.hazmat.primitives.serialization.SSHCertificateBuilder`.
|
||||
* Added :meth:`~cryptography.x509.Certificate.verify_directly_issued_by` to
|
||||
:class:`~cryptography.x509.Certificate`.
|
||||
|
||||
.. _v39-0-0:
|
||||
|
||||
|
|
|
|||
|
|
@ -486,6 +486,35 @@ X.509 Certificate Object
|
|||
An :class:`~cryptography.exceptions.InvalidSignature` exception will be
|
||||
raised if the signature fails to verify.
|
||||
|
||||
.. method:: verify_directly_issued_by(issuer)
|
||||
|
||||
.. versionadded:: 40.0
|
||||
|
||||
:param issuer: The issuer certificate to check against.
|
||||
:type issuer: :class:`~cryptography.x509.Certificate`
|
||||
|
||||
.. warning::
|
||||
This method verifies that the certificate issuer name matches the
|
||||
issuer subject name and that the certificate is signed by the
|
||||
issuer's private key. **No other validation is performed.**
|
||||
Callers are responsible for performing any additional
|
||||
validations required for their use case (e.g. checking the validity
|
||||
period, whether the signer is allowed to issue certificates,
|
||||
that the issuing certificate has a strong public key, etc).
|
||||
|
||||
Validates that the certificate is signed by the provided issuer and
|
||||
that the issuer's subject name matches the issuer name of the
|
||||
certificate.
|
||||
|
||||
:return: None
|
||||
:raise ValueError: If the issuer name on the certificate does
|
||||
not match the subject name of the issuer or the signature
|
||||
algorithm is unsupported.
|
||||
:raise TypeError: If the issuer does not have a supported public
|
||||
key type.
|
||||
:raise cryptography.exceptions.InvalidSignature: If the
|
||||
signature fails to verify.
|
||||
|
||||
|
||||
.. attribute:: tbs_precertificate_bytes
|
||||
|
||||
|
|
|
|||
|
|
@ -265,6 +265,14 @@ class Certificate(metaclass=abc.ABCMeta):
|
|||
Serializes the certificate to PEM or DER format.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def verify_directly_issued_by(self, issuer: "Certificate") -> None:
|
||||
"""
|
||||
This method verifies that certificate issuer name matches the
|
||||
issuer subject name and that the certificate is signed by the
|
||||
issuer's private key. No other validation is performed.
|
||||
"""
|
||||
|
||||
|
||||
# Runtime isinstance checks need this since the rust class is not a subclass.
|
||||
Certificate.register(rust_x509.Certificate)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::asn1::{
|
|||
PyAsn1Error, PyAsn1Result,
|
||||
};
|
||||
use crate::x509;
|
||||
use crate::x509::{crl, extensions, oid, sct, Asn1ReadableOrWritable};
|
||||
use crate::x509::{crl, extensions, oid, sct, sign, Asn1ReadableOrWritable};
|
||||
use chrono::Datelike;
|
||||
use pyo3::ToPyObject;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
|
|
@ -319,6 +319,30 @@ impl Certificate {
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn verify_directly_issued_by<'p>(
|
||||
&self,
|
||||
py: pyo3::Python<'p>,
|
||||
issuer: pyo3::PyRef<'_, Certificate>,
|
||||
) -> PyAsn1Result<()> {
|
||||
if self.raw.borrow_value().tbs_cert.signature_alg != self.raw.borrow_value().signature_alg {
|
||||
return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err(
|
||||
"Inner and outer signature algorithms do not match. This is an invalid certificate."
|
||||
)));
|
||||
};
|
||||
if self.raw.borrow_value().tbs_cert.issuer != issuer.raw.borrow_value().tbs_cert.subject {
|
||||
return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err(
|
||||
"Issuer certificate subject does not match certificate issuer.",
|
||||
)));
|
||||
};
|
||||
sign::verify_signature_with_oid(
|
||||
py,
|
||||
issuer.public_key(py)?,
|
||||
&self.raw.borrow_value().signature_alg.oid,
|
||||
self.raw.borrow_value().signature.as_bytes(),
|
||||
&asn1::write_single(&self.raw.borrow_value().tbs_cert)?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn cert_version(py: pyo3::Python<'_>, version: u8) -> Result<&pyo3::PyAny, PyAsn1Error> {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
// for complete details.
|
||||
|
||||
use crate::asn1::{PyAsn1Error, PyAsn1Result};
|
||||
use crate::x509;
|
||||
use crate::x509::oid;
|
||||
|
||||
|
|
@ -14,6 +15,7 @@ static NULL_DER: Lazy<Vec<u8>> = Lazy::new(|| {
|
|||
pub(crate) static NULL_TLV: Lazy<asn1::Tlv<'static>> =
|
||||
Lazy::new(|| asn1::parse_single(&NULL_DER).unwrap());
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum KeyType {
|
||||
Rsa,
|
||||
Dsa,
|
||||
|
|
@ -22,6 +24,7 @@ enum KeyType {
|
|||
Ed448,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum HashType {
|
||||
None,
|
||||
Sha224,
|
||||
|
|
@ -256,3 +259,253 @@ pub(crate) fn sign_data<'p>(
|
|||
};
|
||||
signature.extract()
|
||||
}
|
||||
|
||||
fn py_hash_name_from_hash_type(hash_type: HashType) -> Option<&'static str> {
|
||||
match hash_type {
|
||||
HashType::None => None,
|
||||
HashType::Sha224 => Some("SHA224"),
|
||||
HashType::Sha256 => Some("SHA256"),
|
||||
HashType::Sha384 => Some("SHA384"),
|
||||
HashType::Sha512 => Some("SHA512"),
|
||||
HashType::Sha3_224 => Some("SHA3_224"),
|
||||
HashType::Sha3_256 => Some("SHA3_256"),
|
||||
HashType::Sha3_384 => Some("SHA3_384"),
|
||||
HashType::Sha3_512 => Some("SHA3_512"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn verify_signature_with_oid<'p>(
|
||||
py: pyo3::Python<'p>,
|
||||
issuer_public_key: &'p pyo3::PyAny,
|
||||
signature_oid: &asn1::ObjectIdentifier,
|
||||
signature: &[u8],
|
||||
data: &[u8],
|
||||
) -> PyAsn1Result<()> {
|
||||
let key_type = identify_public_key_type(py, issuer_public_key)?;
|
||||
let (sig_key_type, sig_hash_type) = identify_key_hash_type_for_oid(signature_oid)?;
|
||||
if key_type != sig_key_type {
|
||||
return Err(PyAsn1Error::from(pyo3::exceptions::PyValueError::new_err(
|
||||
"Signature algorithm does not match issuer key type",
|
||||
)));
|
||||
}
|
||||
let sig_hash_name = py_hash_name_from_hash_type(sig_hash_type);
|
||||
let hashes = py.import("cryptography.hazmat.primitives.hashes")?;
|
||||
let signature_hash = match sig_hash_name {
|
||||
Some(data) => hashes.getattr(data)?.call0()?,
|
||||
None => py.None().into_ref(py),
|
||||
};
|
||||
|
||||
match key_type {
|
||||
KeyType::Ed25519 | KeyType::Ed448 => {
|
||||
issuer_public_key.call_method1("verify", (signature, data))?
|
||||
}
|
||||
KeyType::Ec => {
|
||||
let ec_mod = py.import("cryptography.hazmat.primitives.asymmetric.ec")?;
|
||||
let ecdsa = ec_mod
|
||||
.getattr(crate::intern!(py, "ECDSA"))?
|
||||
.call1((signature_hash,))?;
|
||||
issuer_public_key.call_method1("verify", (signature, data, ecdsa))?
|
||||
}
|
||||
KeyType::Rsa => {
|
||||
let padding_mod = py.import("cryptography.hazmat.primitives.asymmetric.padding")?;
|
||||
let pkcs1v15 = padding_mod
|
||||
.getattr(crate::intern!(py, "PKCS1v15"))?
|
||||
.call0()?;
|
||||
issuer_public_key.call_method1("verify", (signature, data, pkcs1v15, signature_hash))?
|
||||
}
|
||||
KeyType::Dsa => {
|
||||
issuer_public_key.call_method1("verify", (signature, data, signature_hash))?
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn identify_public_key_type(
|
||||
py: pyo3::Python<'_>,
|
||||
public_key: &pyo3::PyAny,
|
||||
) -> pyo3::PyResult<KeyType> {
|
||||
let rsa_key_type: &pyo3::types::PyType = py
|
||||
.import("cryptography.hazmat.primitives.asymmetric.rsa")?
|
||||
.getattr(crate::intern!(py, "RSAPublicKey"))?
|
||||
.extract()?;
|
||||
let dsa_key_type: &pyo3::types::PyType = py
|
||||
.import("cryptography.hazmat.primitives.asymmetric.dsa")?
|
||||
.getattr(crate::intern!(py, "DSAPublicKey"))?
|
||||
.extract()?;
|
||||
let ec_key_type: &pyo3::types::PyType = py
|
||||
.import("cryptography.hazmat.primitives.asymmetric.ec")?
|
||||
.getattr(crate::intern!(py, "EllipticCurvePublicKey"))?
|
||||
.extract()?;
|
||||
let ed25519_key_type: &pyo3::types::PyType = py
|
||||
.import("cryptography.hazmat.primitives.asymmetric.ed25519")?
|
||||
.getattr(crate::intern!(py, "Ed25519PublicKey"))?
|
||||
.extract()?;
|
||||
let ed448_key_type: &pyo3::types::PyType = py
|
||||
.import("cryptography.hazmat.primitives.asymmetric.ed448")?
|
||||
.getattr(crate::intern!(py, "Ed448PublicKey"))?
|
||||
.extract()?;
|
||||
|
||||
if rsa_key_type.is_instance(public_key)? {
|
||||
Ok(KeyType::Rsa)
|
||||
} else if dsa_key_type.is_instance(public_key)? {
|
||||
Ok(KeyType::Dsa)
|
||||
} else if ec_key_type.is_instance(public_key)? {
|
||||
Ok(KeyType::Ec)
|
||||
} else if ed25519_key_type.is_instance(public_key)? {
|
||||
Ok(KeyType::Ed25519)
|
||||
} else if ed448_key_type.is_instance(public_key)? {
|
||||
Ok(KeyType::Ed448)
|
||||
} else {
|
||||
Err(pyo3::exceptions::PyTypeError::new_err(
|
||||
"Key must be an rsa, dsa, ec, ed25519, or ed448 public key.",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn identify_key_hash_type_for_oid(
|
||||
oid: &asn1::ObjectIdentifier,
|
||||
) -> pyo3::PyResult<(KeyType, HashType)> {
|
||||
match *oid {
|
||||
oid::RSA_WITH_SHA224_OID => Ok((KeyType::Rsa, HashType::Sha224)),
|
||||
oid::RSA_WITH_SHA256_OID => Ok((KeyType::Rsa, HashType::Sha256)),
|
||||
oid::RSA_WITH_SHA384_OID => Ok((KeyType::Rsa, HashType::Sha384)),
|
||||
oid::RSA_WITH_SHA512_OID => Ok((KeyType::Rsa, HashType::Sha512)),
|
||||
oid::RSA_WITH_SHA3_224_OID => Ok((KeyType::Rsa, HashType::Sha3_224)),
|
||||
oid::RSA_WITH_SHA3_256_OID => Ok((KeyType::Rsa, HashType::Sha3_256)),
|
||||
oid::RSA_WITH_SHA3_384_OID => Ok((KeyType::Rsa, HashType::Sha3_384)),
|
||||
oid::RSA_WITH_SHA3_512_OID => Ok((KeyType::Rsa, HashType::Sha3_512)),
|
||||
oid::ECDSA_WITH_SHA224_OID => Ok((KeyType::Ec, HashType::Sha224)),
|
||||
oid::ECDSA_WITH_SHA256_OID => Ok((KeyType::Ec, HashType::Sha256)),
|
||||
oid::ECDSA_WITH_SHA384_OID => Ok((KeyType::Ec, HashType::Sha384)),
|
||||
oid::ECDSA_WITH_SHA512_OID => Ok((KeyType::Ec, HashType::Sha512)),
|
||||
oid::ECDSA_WITH_SHA3_224_OID => Ok((KeyType::Ec, HashType::Sha3_224)),
|
||||
oid::ECDSA_WITH_SHA3_256_OID => Ok((KeyType::Ec, HashType::Sha3_256)),
|
||||
oid::ECDSA_WITH_SHA3_384_OID => Ok((KeyType::Ec, HashType::Sha3_384)),
|
||||
oid::ECDSA_WITH_SHA3_512_OID => Ok((KeyType::Ec, HashType::Sha3_512)),
|
||||
oid::ED25519_OID => Ok((KeyType::Ed25519, HashType::None)),
|
||||
oid::ED448_OID => Ok((KeyType::Ed448, HashType::None)),
|
||||
oid::DSA_WITH_SHA224_OID => Ok((KeyType::Dsa, HashType::Sha224)),
|
||||
oid::DSA_WITH_SHA256_OID => Ok((KeyType::Dsa, HashType::Sha256)),
|
||||
oid::DSA_WITH_SHA384_OID => Ok((KeyType::Dsa, HashType::Sha384)),
|
||||
oid::DSA_WITH_SHA512_OID => Ok((KeyType::Dsa, HashType::Sha512)),
|
||||
_ => Err(pyo3::exceptions::PyValueError::new_err(
|
||||
"Unsupported signature algorithm",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{identify_key_hash_type_for_oid, py_hash_name_from_hash_type, HashType, KeyType};
|
||||
use crate::x509::oid;
|
||||
|
||||
#[test]
|
||||
fn test_identify_key_hash_type_for_oid() {
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::RSA_WITH_SHA224_OID).unwrap(),
|
||||
(KeyType::Rsa, HashType::Sha224)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::RSA_WITH_SHA256_OID).unwrap(),
|
||||
(KeyType::Rsa, HashType::Sha256)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::RSA_WITH_SHA384_OID).unwrap(),
|
||||
(KeyType::Rsa, HashType::Sha384)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::RSA_WITH_SHA512_OID).unwrap(),
|
||||
(KeyType::Rsa, HashType::Sha512)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::RSA_WITH_SHA3_224_OID).unwrap(),
|
||||
(KeyType::Rsa, HashType::Sha3_224)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::RSA_WITH_SHA3_256_OID).unwrap(),
|
||||
(KeyType::Rsa, HashType::Sha3_256)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::RSA_WITH_SHA3_384_OID).unwrap(),
|
||||
(KeyType::Rsa, HashType::Sha3_384)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::RSA_WITH_SHA3_512_OID).unwrap(),
|
||||
(KeyType::Rsa, HashType::Sha3_512)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::ECDSA_WITH_SHA224_OID).unwrap(),
|
||||
(KeyType::Ec, HashType::Sha224)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::ECDSA_WITH_SHA256_OID).unwrap(),
|
||||
(KeyType::Ec, HashType::Sha256)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::ECDSA_WITH_SHA384_OID).unwrap(),
|
||||
(KeyType::Ec, HashType::Sha384)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::ECDSA_WITH_SHA512_OID).unwrap(),
|
||||
(KeyType::Ec, HashType::Sha512)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::ECDSA_WITH_SHA3_224_OID).unwrap(),
|
||||
(KeyType::Ec, HashType::Sha3_224)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::ECDSA_WITH_SHA3_256_OID).unwrap(),
|
||||
(KeyType::Ec, HashType::Sha3_256)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::ECDSA_WITH_SHA3_384_OID).unwrap(),
|
||||
(KeyType::Ec, HashType::Sha3_384)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::ECDSA_WITH_SHA3_512_OID).unwrap(),
|
||||
(KeyType::Ec, HashType::Sha3_512)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::ED25519_OID).unwrap(),
|
||||
(KeyType::Ed25519, HashType::None)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::ED448_OID).unwrap(),
|
||||
(KeyType::Ed448, HashType::None)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::DSA_WITH_SHA224_OID).unwrap(),
|
||||
(KeyType::Dsa, HashType::Sha224)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::DSA_WITH_SHA256_OID).unwrap(),
|
||||
(KeyType::Dsa, HashType::Sha256)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::DSA_WITH_SHA384_OID).unwrap(),
|
||||
(KeyType::Dsa, HashType::Sha384)
|
||||
);
|
||||
assert_eq!(
|
||||
identify_key_hash_type_for_oid(&oid::DSA_WITH_SHA512_OID).unwrap(),
|
||||
(KeyType::Dsa, HashType::Sha512)
|
||||
);
|
||||
assert!(identify_key_hash_type_for_oid(&oid::TLS_FEATURE_OID).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_py_hash_name_from_hash_type() {
|
||||
for (hash, name) in [
|
||||
(HashType::Sha224, "SHA224"),
|
||||
(HashType::Sha256, "SHA256"),
|
||||
(HashType::Sha384, "SHA384"),
|
||||
(HashType::Sha512, "SHA512"),
|
||||
(HashType::Sha3_224, "SHA3_224"),
|
||||
(HashType::Sha3_256, "SHA3_256"),
|
||||
(HashType::Sha3_384, "SHA3_384"),
|
||||
(HashType::Sha3_512, "SHA3_512"),
|
||||
] {
|
||||
let hash_str = py_hash_name_from_hash_type(hash).unwrap();
|
||||
assert_eq!(hash_str, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import pytest
|
|||
import pytz
|
||||
|
||||
from cryptography import utils, x509
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.hazmat.bindings._rust import asn1
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import (
|
||||
|
|
@ -25,6 +26,7 @@ from cryptography.hazmat.primitives.asymmetric import (
|
|||
ed25519,
|
||||
padding,
|
||||
rsa,
|
||||
types,
|
||||
x448,
|
||||
x25519,
|
||||
)
|
||||
|
|
@ -41,9 +43,13 @@ from cryptography.x509.oid import (
|
|||
SubjectInformationAccessOID,
|
||||
)
|
||||
|
||||
from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048
|
||||
from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048, DSA_KEY_3072
|
||||
from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1
|
||||
from ..hazmat.primitives.fixtures_rsa import RSA_KEY_512, RSA_KEY_2048
|
||||
from ..hazmat.primitives.fixtures_rsa import (
|
||||
RSA_KEY_512,
|
||||
RSA_KEY_2048,
|
||||
RSA_KEY_2048_ALT,
|
||||
)
|
||||
from ..hazmat.primitives.test_ec import _skip_curve_unsupported
|
||||
from ..utils import (
|
||||
load_nist_vectors,
|
||||
|
|
@ -77,6 +83,57 @@ def _load_cert(filename, loader: typing.Callable[..., T], backend=None) -> T:
|
|||
return cert
|
||||
|
||||
|
||||
def _generate_ca_and_leaf(
|
||||
issuer_private_key: types.CERTIFICATE_PRIVATE_KEY_TYPES,
|
||||
subject_private_key: types.CERTIFICATE_PRIVATE_KEY_TYPES,
|
||||
):
|
||||
if isinstance(
|
||||
issuer_private_key,
|
||||
(ed25519.Ed25519PrivateKey, ed448.Ed448PrivateKey),
|
||||
):
|
||||
hash_alg = None
|
||||
else:
|
||||
hash_alg = hashes.SHA256()
|
||||
|
||||
builder = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(
|
||||
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")])
|
||||
)
|
||||
.issuer_name(
|
||||
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")])
|
||||
)
|
||||
.public_key(issuer_private_key.public_key())
|
||||
.serial_number(1)
|
||||
.not_valid_before(datetime.datetime(2020, 1, 1))
|
||||
.not_valid_after(datetime.datetime(2030, 1, 1))
|
||||
)
|
||||
ca = builder.sign(issuer_private_key, hash_alg)
|
||||
builder = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(
|
||||
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "leaf")])
|
||||
)
|
||||
.issuer_name(
|
||||
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")])
|
||||
)
|
||||
.public_key(subject_private_key.public_key())
|
||||
.serial_number(100)
|
||||
.not_valid_before(datetime.datetime(2020, 1, 1))
|
||||
.not_valid_after(datetime.datetime(2025, 1, 1))
|
||||
)
|
||||
cert = builder.sign(issuer_private_key, hash_alg)
|
||||
return ca, cert
|
||||
|
||||
|
||||
def _break_cert_sig(cert: x509.Certificate) -> x509.Certificate:
|
||||
cert_bad_sig = bytearray(cert.public_bytes(serialization.Encoding.PEM))
|
||||
# Break the sig by mutating 5 bytes. This has a 2**-40 chance of
|
||||
# not breaking the sig. Spin that roulette wheel.
|
||||
cert_bad_sig[-40:-35] = 90, 90, 90, 90, 90
|
||||
return x509.load_pem_x509_certificate(bytes(cert_bad_sig))
|
||||
|
||||
|
||||
class TestCertificateRevocationList:
|
||||
def test_load_pem_crl(self, backend):
|
||||
crl = _load_cert(
|
||||
|
|
@ -1473,6 +1530,108 @@ class TestRSACertificate:
|
|||
[x509.TLSFeatureType.status_request]
|
||||
)
|
||||
|
||||
def test_verify_directly_issued_by_rsa(self):
|
||||
issuer_private_key = RSA_KEY_2048.private_key()
|
||||
subject_private_key = RSA_KEY_2048_ALT.private_key()
|
||||
ca, cert = _generate_ca_and_leaf(
|
||||
issuer_private_key, subject_private_key
|
||||
)
|
||||
cert.verify_directly_issued_by(ca)
|
||||
|
||||
def test_verify_directly_issued_by_rsa_bad_sig(self):
|
||||
issuer_private_key = RSA_KEY_2048.private_key()
|
||||
subject_private_key = RSA_KEY_2048_ALT.private_key()
|
||||
ca, cert = _generate_ca_and_leaf(
|
||||
issuer_private_key, subject_private_key
|
||||
)
|
||||
cert_bad_sig = _break_cert_sig(cert)
|
||||
with pytest.raises(InvalidSignature):
|
||||
cert_bad_sig.verify_directly_issued_by(ca)
|
||||
|
||||
def test_verify_directly_issued_by_rsa_mismatched_inner_out_oid(self):
|
||||
cert = _load_cert(
|
||||
os.path.join(
|
||||
"x509", "custom", "mismatch_inner_outer_sig_algorithm.der"
|
||||
),
|
||||
x509.load_der_x509_certificate,
|
||||
)
|
||||
with pytest.raises(ValueError) as exc:
|
||||
cert.verify_directly_issued_by(cert)
|
||||
|
||||
assert str(exc.value) == (
|
||||
"Inner and outer signature algorithms do not match. This is an "
|
||||
"invalid certificate."
|
||||
)
|
||||
|
||||
def test_verify_directly_issued_by_subject_issuer_mismatch(self):
|
||||
cert = _load_cert(
|
||||
os.path.join("x509", "cryptography.io.pem"),
|
||||
x509.load_pem_x509_certificate,
|
||||
)
|
||||
with pytest.raises(ValueError) as exc:
|
||||
cert.verify_directly_issued_by(cert)
|
||||
|
||||
assert str(exc.value) == (
|
||||
"Issuer certificate subject does not match certificate issuer."
|
||||
)
|
||||
|
||||
def test_verify_directly_issued_by_algorithm_mismatch(self):
|
||||
issuer_private_key = RSA_KEY_2048.private_key()
|
||||
subject_private_key = RSA_KEY_2048_ALT.private_key()
|
||||
_, cert = _generate_ca_and_leaf(
|
||||
issuer_private_key, subject_private_key
|
||||
)
|
||||
# We need a CA with the same issuer DN but diff signature algorithm
|
||||
secondary_issuer_key = ec.generate_private_key(ec.SECP256R1())
|
||||
ca2, _ = _generate_ca_and_leaf(
|
||||
secondary_issuer_key, subject_private_key
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
cert.verify_directly_issued_by(ca2)
|
||||
|
||||
@pytest.mark.supported(
|
||||
only_if=lambda backend: (
|
||||
backend.ed25519_supported() and backend.x25519_supported()
|
||||
),
|
||||
skip_message="Requires OpenSSL with Ed25519 and X25519 support",
|
||||
)
|
||||
def test_verify_directly_issued_by_unsupported_key_type(self, backend):
|
||||
private_key = ed25519.Ed25519PrivateKey.generate()
|
||||
x25519_public = x25519.X25519PrivateKey.generate().public_key()
|
||||
# Generate an ed25519 CA
|
||||
builder = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(
|
||||
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")])
|
||||
)
|
||||
.issuer_name(
|
||||
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")])
|
||||
)
|
||||
.public_key(private_key.public_key())
|
||||
.serial_number(1)
|
||||
.not_valid_before(datetime.datetime(2020, 1, 1))
|
||||
.not_valid_after(datetime.datetime(2030, 1, 1))
|
||||
)
|
||||
cert = builder.sign(private_key, None)
|
||||
# Make a cert with the right issuer name but the wrong public key
|
||||
builder = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(
|
||||
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")])
|
||||
)
|
||||
.issuer_name(
|
||||
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PyCA CA")])
|
||||
)
|
||||
.public_key(x25519_public)
|
||||
.serial_number(1)
|
||||
.not_valid_before(datetime.datetime(2020, 1, 1))
|
||||
.not_valid_after(datetime.datetime(2030, 1, 1))
|
||||
)
|
||||
leaf = builder.sign(private_key, None)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
cert.verify_directly_issued_by(leaf)
|
||||
|
||||
|
||||
class TestRSACertificateRequest:
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -4562,6 +4721,24 @@ class TestDSACertificate:
|
|||
cert.signature_hash_algorithm,
|
||||
)
|
||||
|
||||
def test_verify_directly_issued_by_dsa(self):
|
||||
issuer_private_key = DSA_KEY_3072.private_key()
|
||||
subject_private_key = DSA_KEY_2048.private_key()
|
||||
ca, cert = _generate_ca_and_leaf(
|
||||
issuer_private_key, subject_private_key
|
||||
)
|
||||
cert.verify_directly_issued_by(ca)
|
||||
|
||||
def test_verify_directly_issued_by_dsa_bad_sig(self):
|
||||
issuer_private_key = DSA_KEY_3072.private_key()
|
||||
subject_private_key = DSA_KEY_2048.private_key()
|
||||
ca, cert = _generate_ca_and_leaf(
|
||||
issuer_private_key, subject_private_key
|
||||
)
|
||||
cert_bad_sig = _break_cert_sig(cert)
|
||||
with pytest.raises(InvalidSignature):
|
||||
cert_bad_sig.verify_directly_issued_by(ca)
|
||||
|
||||
|
||||
@pytest.mark.supported(
|
||||
only_if=lambda backend: backend.dsa_supported(),
|
||||
|
|
@ -4788,6 +4965,24 @@ class TestECDSACertificate:
|
|||
with pytest.raises(ValueError, match="explicit parameters"):
|
||||
cert.public_key()
|
||||
|
||||
def test_verify_directly_issued_by_ec(self):
|
||||
issuer_private_key = ec.generate_private_key(ec.SECP256R1())
|
||||
subject_private_key = ec.generate_private_key(ec.SECP256R1())
|
||||
ca, cert = _generate_ca_and_leaf(
|
||||
issuer_private_key, subject_private_key
|
||||
)
|
||||
cert.verify_directly_issued_by(ca)
|
||||
|
||||
def test_verify_directly_issued_by_ec_bad_sig(self):
|
||||
issuer_private_key = ec.generate_private_key(ec.SECP256R1())
|
||||
subject_private_key = ec.generate_private_key(ec.SECP256R1())
|
||||
ca, cert = _generate_ca_and_leaf(
|
||||
issuer_private_key, subject_private_key
|
||||
)
|
||||
cert_bad_sig = _break_cert_sig(cert)
|
||||
with pytest.raises(InvalidSignature):
|
||||
cert_bad_sig.verify_directly_issued_by(ca)
|
||||
|
||||
|
||||
class TestECDSACertificateRequest:
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -5411,6 +5606,24 @@ class TestEd25519Certificate:
|
|||
)
|
||||
assert copy.deepcopy(cert) is cert
|
||||
|
||||
def test_verify_directly_issued_by_ed25519(self, backend):
|
||||
issuer_private_key = ed25519.Ed25519PrivateKey.generate()
|
||||
subject_private_key = ed25519.Ed25519PrivateKey.generate()
|
||||
ca, cert = _generate_ca_and_leaf(
|
||||
issuer_private_key, subject_private_key
|
||||
)
|
||||
cert.verify_directly_issued_by(ca)
|
||||
|
||||
def test_verify_directly_issued_by_ed25519_bad_sig(self, backend):
|
||||
issuer_private_key = ed25519.Ed25519PrivateKey.generate()
|
||||
subject_private_key = ed25519.Ed25519PrivateKey.generate()
|
||||
ca, cert = _generate_ca_and_leaf(
|
||||
issuer_private_key, subject_private_key
|
||||
)
|
||||
cert_bad_sig = _break_cert_sig(cert)
|
||||
with pytest.raises(InvalidSignature):
|
||||
cert_bad_sig.verify_directly_issued_by(ca)
|
||||
|
||||
|
||||
@pytest.mark.supported(
|
||||
only_if=lambda backend: backend.ed448_supported(),
|
||||
|
|
@ -5432,6 +5645,24 @@ class TestEd448Certificate:
|
|||
assert cert.signature_hash_algorithm is None
|
||||
assert cert.signature_algorithm_oid == SignatureAlgorithmOID.ED448
|
||||
|
||||
def test_verify_directly_issued_by_ed448(self, backend):
|
||||
issuer_private_key = ed448.Ed448PrivateKey.generate()
|
||||
subject_private_key = ed448.Ed448PrivateKey.generate()
|
||||
ca, cert = _generate_ca_and_leaf(
|
||||
issuer_private_key, subject_private_key
|
||||
)
|
||||
cert.verify_directly_issued_by(ca)
|
||||
|
||||
def test_verify_directly_issued_by_ed448_bad_sig(self, backend):
|
||||
issuer_private_key = ed448.Ed448PrivateKey.generate()
|
||||
subject_private_key = ed448.Ed448PrivateKey.generate()
|
||||
ca, cert = _generate_ca_and_leaf(
|
||||
issuer_private_key, subject_private_key
|
||||
)
|
||||
cert_bad_sig = _break_cert_sig(cert)
|
||||
with pytest.raises(InvalidSignature):
|
||||
cert_bad_sig.verify_directly_issued_by(ca)
|
||||
|
||||
|
||||
@pytest.mark.supported(
|
||||
only_if=lambda backend: backend.dh_supported(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue