anchor/anchor/X509/certificate.py

285 lines
9.9 KiB
Python

#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
import base64
import binascii
import io
from cryptography.hazmat import backends as cio_backends
from cryptography.hazmat.primitives import hashes
from pyasn1.codec.ber import encoder as ber_encoder
from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder
from pyasn1.type import univ as asn1_univ
from pyasn1_modules import pem
from anchor.asn1 import rfc5280
from anchor.X509 import errors
from anchor.X509 import extension
from anchor.X509 import name
from anchor.X509 import signature
from anchor.X509 import utils
SIGNING_ALGORITHMS = {
('RSA', 'SHA224'): asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.14'),
('RSA', 'SHA256'): asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.11'),
('RSA', 'SHA384'): asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.12'),
('RSA', 'SHA512'): asn1_univ.ObjectIdentifier('1.2.840.113549.1.1.13'),
('DSA', 'SHA224'): asn1_univ.ObjectIdentifier('2.16.840.1.101.3.4.3.1'),
('DSA', 'SHA256'): asn1_univ.ObjectIdentifier('2.16.840.1.101.3.4.3.2'),
}
SIGNING_ALGORITHMS_INV = dict((v, k) for k, v in SIGNING_ALGORITHMS.items())
class X509CertificateError(errors.X509Error):
"""Specific error for X509 certificate operations."""
pass
class X509Certificate(signature.SignatureMixin):
"""X509 certificate class."""
def __init__(self, certificate=None):
if certificate is None:
self._cert = rfc5280.Certificate()
self._cert['tbsCertificate'] = rfc5280.TBSCertificate()
else:
self._cert = certificate
@staticmethod
def from_open_file(f):
try:
der_content = pem.readPemFromFile(f)
certificate = decoder.decode(der_content,
asn1Spec=rfc5280.Certificate())[0]
return X509Certificate(certificate)
except Exception:
raise X509CertificateError("Could not read X509 certificate from "
"PEM data.")
@staticmethod
def from_buffer(data):
"""Build this X509 object from a data buffer in memory.
:param data: A data buffer
"""
return X509Certificate.from_open_file(io.StringIO(data))
@staticmethod
def from_file(path):
"""Build this X509 certificate object from a data file on disk.
:param path: A data buffer
"""
with open(path, 'r') as f:
return X509Certificate.from_open_file(f)
def as_pem(self):
"""Serialise this X509 certificate object as PEM string."""
header = '-----BEGIN CERTIFICATE-----'
footer = '-----END CERTIFICATE-----'
der_cert = encoder.encode(self._cert)
b64_encoder = (base64.encodestring if str is bytes else
base64.encodebytes)
b64_cert = b64_encoder(der_cert).decode('ascii')
return "%s\n%s%s\n" % (header, b64_cert, footer)
def set_version(self, v):
"""Set the version of this X509 certificate object.
:param v: The version
"""
self._cert['tbsCertificate']['version'] = v
def get_version(self):
"""Get the version of this X509 certificate object."""
return self._cert['tbsCertificate']['version']
def get_validity(self):
if self._cert['tbsCertificate']['validity'] is None:
self._cert['tbsCertificate']['validity'] = None
return self._cert['tbsCertificate']['validity']
def set_not_before(self, t):
"""Set the 'not before' date field.
:param t: time in seconds since the epoch
"""
asn1_time = utils.timestamp_to_asn1_time(t)
validity = self.get_validity()
validity['notBefore'] = asn1_time
def get_not_before(self):
"""Get the 'not before' date field as seconds since the epoch."""
validity = self.get_validity()
not_before = validity['notBefore']
return utils.asn1_time_to_timestamp(not_before)
def set_not_after(self, t):
"""Set the 'not after' date field.
:param t: time in seconds since the epoch
"""
asn1_time = utils.timestamp_to_asn1_time(t)
validity = self.get_validity()
validity['notAfter'] = asn1_time
def get_not_after(self):
"""Get the 'not after' date field as seconds since the epoch."""
validity = self.get_validity()
not_after = validity['notAfter']
return utils.asn1_time_to_timestamp(not_after)
def set_pubkey(self, pkey):
"""Set the public key field.
:param pkey: The public key, rfc5280.SubjectPublicKeyInfo description
"""
self._cert['tbsCertificate']['subjectPublicKeyInfo'] = pkey
def get_subject(self):
"""Get the subject name field value.
:return: An X509Name object instance
"""
val = self._cert['tbsCertificate']['subject'][0]
return name.X509Name(val)
def set_subject(self, subject):
"""Set the subject name filed value.
:param subject: An X509Name object instance
"""
val = subject._name_obj
if self._cert['tbsCertificate']['subject'] is None:
self._cert['tbsCertificate']['subject'] = rfc5280.Name()
self._cert['tbsCertificate']['subject'][0] = val
def set_issuer(self, issuer):
"""Set the issuer name field value.
:param issuer: An X509Name object instance
"""
val = issuer._name_obj
if self._cert['tbsCertificate']['issuer'] is None:
self._cert['tbsCertificate']['issuer'] = rfc5280.Name()
self._cert['tbsCertificate']['issuer'][0] = val
def get_issuer(self):
"""Get the issuer name field value.
:return: An X509Name object instance
"""
val = self._cert['tbsCertificate']['issuer'][0]
return name.X509Name(val)
def set_serial_number(self, serial):
"""Set the serial number
The serial number is a 32 bit integer value that should be unique to
each certificate issued by a given certificate authority.
:param serial: The serial number, 32 bit integer
"""
self._cert['tbsCertificate']['serialNumber'] = serial
def get_serial_number(self,):
return self._cert['tbsCertificate']['serialNumber']
def _get_extensions(self):
if self._cert['tbsCertificate']['extensions'] is None:
# this actually initialises the extensions tag rather than
# assign None
self._cert['tbsCertificate']['extensions'] = None
return self._cert['tbsCertificate']['extensions']
def get_extensions(self, ext_type=None):
extensions = self._get_extensions()
return [extension.construct_extension(e) for e in extensions
if ext_type is None or e['extnID'] == ext_type._oid]
def add_extension(self, ext, index):
"""Add an X509 V3 Certificate extension.
:param ext: An X509Extension instance
:param index: The index of the extension
"""
if not isinstance(ext, extension.X509Extension):
raise errors.X509Error("ext needs to be a pyasn1 extension")
extensions = self._get_extensions()
extensions[index] = ext.as_asn1()
def _get_bytes_to_sign(self):
return encoder.encode(self._cert['tbsCertificate'])
def _embed_signature_algorithm(self, algo_id):
self._cert['tbsCertificate']['signature'] = algo_id
def _embed_signature(self, algo_id, signature):
self._cert['signature'] = "'%s'H" % (
str(binascii.hexlify(signature).decode('ascii')),)
self._cert['signatureAlgorithm'] = algo_id
def _get_signature(self):
return utils.bin_to_bytes(self._cert['signature'])
def _get_signing_algorithm(self):
tbs_signature = self._cert['tbsCertificate']['signature']
cert_signature = self._cert['signatureAlgorithm']
if tbs_signature != cert_signature:
raise errors.X509Error("algorithms mismatch")
return tbs_signature['algorithm']
def as_der(self):
"""Return this X509 certificate as DER encoded data."""
return encoder.encode(self._cert)
def get_fingerprint(self, md='sha256'):
"""Get the fingerprint of this X509 certificate.
:param md: The message digest algorithm used to compute the fingerprint
:return: The fingerprint encoded as a hex string
"""
hash_class = utils.get_hash_class(md)
if hash_class is None:
raise errors.X509Error(
"Unknown hash %s" % (md,))
hasher = hashes.Hash(hash_class(),
backend=cio_backends.default_backend())
hasher.update(self.as_der())
return binascii.hexlify(hasher.finalize()).upper().decode('ascii')
def get_key_id(self):
"""Construct a key identifier from public key.
Return the hash useful for keyIdentifier field, constructed as
described in RFC5280 section 4.2.1.2, method 1. The result is
SHA1(subjectPublicKey).
"""
key_info = self._cert['tbsCertificate']['subjectPublicKeyInfo']
public_key = key_info['subjectPublicKey']
# get the actual bit string value, without the length and tags
value = ber_encoder.BitStringEncoder().encodeValue(
None, public_key, True, None)[0][1:]
digest = hashes.Hash(hashes.SHA1(),
backend=cio_backends.default_backend())
digest.update(value)
return digest.finalize()