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

Add certificate signature verification #2460

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Changelog
CRLs.
* Unrecognized non-critical X.509 extensions are now parsed into an
:class:`~cryptography.x509.UnrecognizedExtension` object.
* Added :class: `~cryptography.x509.CertificationVerificationContext` to allow
verification of certificate signatures.

1.1.2 - 2015-12-10
~~~~~~~~~~~~~~~~~~
Expand Down
35 changes: 35 additions & 0 deletions docs/x509/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,34 @@ X.509 CSR (Certificate Signing Request) Builder Object

The dotted string value of the OID (e.g. ``"2.5.4.3"``)

X.509 Certificate Verification
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. class:: CertificateVerificationContext

.. versionadded:: 1.2

.. method:: update(certificate)

Processes the provided certificate. Raises an exception if the
certificate is invalid.

:param certificate: The certificate whose signature needs to be
verified.

:raises cryptography.x509.verification.InvalidCertificate: If the
certificate is an invalid type.

.. method:: verify()

Verifies the signature of the certificate provided to update against
the certificate associated with the context. Raises an exception if the
verification process fails.

:raises cryptography.x509.verification.InvalidCertificate: If the
certificate provided to update was not issued by the certificate
associated with the context.

.. _general_name_classes:

General Name Classes
Expand Down Expand Up @@ -2467,6 +2495,13 @@ Exceptions
The integer value of the unsupported type. The complete list of
types can be found in `RFC 5280 section 4.2.1.6`_.

.. currentmodule:: cryptography.x509.verification

.. class:: InvalidCertificate

This is raised by :class:`~cryptography.x509.CertificateVerificationContext`
when dealing with invalid certificate arguments and when certificate
signature verification fails.

.. _`RFC 5280 section 4.2.1.1`: https://tools.ietf.org/html/rfc5280#section-4.2.1.1
.. _`RFC 5280 section 4.2.1.6`: https://tools.ietf.org/html/rfc5280#section-4.2.1.6
7 changes: 7 additions & 0 deletions src/cryptography/x509/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
CertificatePoliciesOID, ExtendedKeyUsageOID, ExtensionOID, NameOID,
ObjectIdentifier, SignatureAlgorithmOID, _SIG_OIDS_TO_HASH
)
from cryptography.x509.verification import (
CertificateVerificationContext, InvalidCertificate,
InvalidSigningCertificate
)


OID_AUTHORITY_INFORMATION_ACCESS = ExtensionOID.AUTHORITY_INFORMATION_ACCESS
Expand Down Expand Up @@ -117,6 +121,8 @@
"UnsupportedExtension",
"ExtensionNotFound",
"UnsupportedGeneralNameType",
"InvalidCertificate",
"InvalidSigningCertificate",
"NameAttribute",
"Name",
"ObjectIdentifier",
Expand Down Expand Up @@ -160,6 +166,7 @@
"RevokedCertificateBuilder",
"CertificateSigningRequestBuilder",
"CertificateBuilder",
"CertificateVerificationContext",
"Version",
"_SIG_OIDS_TO_HASH",
"OID_CA_ISSUERS",
Expand Down
93 changes: 93 additions & 0 deletions src/cryptography/x509/verification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# 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.

from __future__ import absolute_import, division, print_function

from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa
from cryptography.x509 import Certificate
from cryptography.x509.oid import ExtensionOID


class InvalidCertificate(Exception):
pass


class InvalidSigningCertificate(Exception):
pass


def _can_sign_certificates(certificate):
basic_constraints = certificate.extensions.get_extension_for_oid(
ExtensionOID.BASIC_CONSTRAINTS).value
key_usage = certificate.extensions.get_extension_for_oid(
ExtensionOID.KEY_USAGE).value

if not basic_constraints.ca:
raise InvalidSigningCertificate(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the certificate has no basic constraint or key usage extensions? These v3 extensions are critical but not mandatory extensions. A cert w/o a BC, KU or EKU can be used as CA cert.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While not mandatory in the general case, these extensions are required for conforming CAs. The following quotes are taken from RFC 5280 on X.509 PKI:

https://www.ietf.org/rfc/rfc5280.txt

Regarding the Basic Constraints extension, see Section 4.2.1.9, bottom of page 38:

Conforming CAs MUST include this extension in all CA certificates that contain public keys used to validate digital signatures on certificates and MUST mark the extension as critical in such certificates. This extension MAY appear as a critical or non-critical extension in CA certificates that contain public keys used exclusively for purposes other than validating digital signatures on certificates. Such CA certificates include ones that contain public keys used exclusively for validating digital signatures on CRLs and ones that contain key management public keys used with certificate enrollment protocols. This extension MAY appear as a critical or non-critical extension in end entity certificates.

Regarding the Key Usage extension, see Section 4.2.1.3, top of page 29:

Conforming CAs MUST include this extension in certificates that contain public keys that are used to validate digital signatures on other public key certificates or CRLs. When present, conforming CAs SHOULD mark this extension as critical.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PeterHamilton , see #2460 (comment)
RFC 5280 is to new for some root CA widely used nowadays. You can't rely on existing of this extensions.

"The certificate is not marked as a CA in its BasicConstraints "
"extension."
)
elif not key_usage.key_cert_sign:
raise InvalidSigningCertificate(
"The certificate public key is not marked for verifying "
"certificates in its KeyUsage extension."
)
else:
return True


def _is_issuing_certificate(issuing_certificate, issued_certificate):
return (issuing_certificate.subject == issued_certificate.issuer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check, in addition to _can_sign_certificates, should incorporate a check to detect if the issuing_certificate is name constrained in any way, and if so, validate that the issued_certificate's subject / subjectAltName(s) match the issuing_certificate's set of name constraints.



class CertificateVerificationContext(object):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does the verification context handle unknown critical v3 extensions? Does it raise an exception or does it ignore unknown critical extensions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now the context only examines the Basic Constraints and Key Usage extensions for fields required for CA use. Other extensions are ignored.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is a valid approach to first have basic verification scenarios work and then work on corner cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was talking about unknown critical extensions. By definition a library must fail validation when a certificate contains an unknown X509v3 extension with the CRITICAL bit. @alex told me on IRC that cryptography handles the case internally. It raises an exception on accessing any extension.

def __init__(self, certificate):
if not isinstance(certificate, Certificate):
raise InvalidCertificate(
"The signing certificate must be a Certificate."
)
_can_sign_certificates(certificate)

self._signing_cert = certificate

def update(self, certificate):
"""
Processes the provided certificate. Raises an exception if the
certificate is invalid.
"""
if not isinstance(certificate, Certificate):
raise InvalidCertificate(
"The signed certificate must be a Certificate."
)

self._signed_cert = certificate

def verify(self):
"""
Verifies the signature of the certificate provided to update against
the certificate associated with the context. Raises an exception if
the verification process fails.
"""
if not _is_issuing_certificate(self._signing_cert, self._signed_cert):
raise InvalidCertificate(
"The certificate issuer does not match the subject name of "
"the context certificate."
)

signature_hash_algorithm = self._signed_cert.signature_hash_algorithm
signature_bytes = self._signed_cert.signature
signer_public_key = self._signing_cert.public_key()

if isinstance(signer_public_key, rsa.RSAPublicKey):
verifier = signer_public_key.verifier(
signature_bytes, padding.PKCS1v15(), signature_hash_algorithm)
elif isinstance(signer_public_key, ec.EllipticCurvePublicKey):
verifier = signer_public_key.verifier(
signature_bytes, ec.ECDSA(signature_hash_algorithm))
else:
verifier = signer_public_key.verifier(
signature_bytes, signature_hash_algorithm)

verifier.update(self._signed_cert.tbs_certificate_bytes)
verifier.verify()
Loading