x509: use python-cryptography to process certs

Update x509.load_certificate and related functions to return
python-cryptography ``Certificate`` objects.  Update the call sites
accordingly, including removal of NSS initialisation code.

Also update GeneralName parsing code to return python-cryptography
GeneralName values, for consistency with other code that processes
GeneralNames.  The new function, `get_san_general_names`, and
associated helper functions, can be removed when python-cryptography
provides a way to deal with unrecognised critical extensions.

Part of: https://fedorahosted.org/freeipa/ticket/6398

Reviewed-By: Jan Cholasta <jcholast@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
This commit is contained in:
Fraser Tweedale
2016-10-13 17:12:31 +10:00
committed by David Kupka
parent c57dc890b2
commit db116f73fe
16 changed files with 369 additions and 397 deletions

View File

@@ -35,9 +35,9 @@ try:
import gssapi
import netifaces
import nss.nss as nss
import SSSDConfig
from six.moves.urllib.parse import urlparse, urlunparse
from cryptography.hazmat.primitives import serialization
from ipapython.ipa_log_manager import standard_logging_setup, root_logger
from ipaclient import ipadiscovery
@@ -92,15 +92,10 @@ def parse_options():
if not os.path.isabs(value):
raise OptionValueError("%s option '%s' is not an absolute file path" % (opt, value))
initialized = nss.nss_is_initialized()
try:
cert = x509.load_certificate_from_file(value)
x509.load_certificate_from_file(value)
except Exception:
raise OptionValueError("%s option '%s' is not a valid certificate file" % (opt, value))
else:
del(cert)
if not initialized:
nss.nss_shutdown()
parser.values.ca_cert_file = value
@@ -300,10 +295,10 @@ def cert_summary(msg, certs, indent=' '):
else:
s = ''
for cert in certs:
s += '%sSubject: %s\n' % (indent, cert.subject)
s += '%sIssuer: %s\n' % (indent, cert.issuer)
s += '%sValid From: %s\n' % (indent, cert.valid_not_before_str)
s += '%sValid Until: %s\n' % (indent, cert.valid_not_after_str)
s += '%sSubject: %s\n' % (indent, DN(cert.subject))
s += '%sIssuer: %s\n' % (indent, DN(cert.issuer))
s += '%sValid From: %s\n' % (indent, cert.not_valid_before)
s += '%sValid Until: %s\n' % (indent, cert.not_valid_after)
s += '\n'
s = s[:-1]
@@ -2148,7 +2143,10 @@ def get_ca_certs(fstore, options, server, basedn, realm):
if ca_certs is not None:
try:
ca_certs = [cert.der_data for cert in ca_certs]
ca_certs = [
cert.public_bytes(serialization.Encoding.DER)
for cert in ca_certs
]
x509.write_certificate_list(ca_certs, ca_file)
except Exception as e:
if os.path.exists(ca_file):
@@ -2815,7 +2813,10 @@ def install(options, env, fstore, statestore):
# Add CA certs to a temporary NSS database
ca_certs = x509.load_certificate_list_from_file(CACERT)
ca_certs = [cert.der_data for cert in ca_certs]
ca_certs = [
cert.public_bytes(serialization.Encoding.DER)
for cert in ca_certs
]
try:
pwd_file = ipautil.write_tmp_file(ipautil.ipa_generate_password())
tmp_db.create_db(pwd_file.name)

View File

@@ -21,6 +21,7 @@
from __future__ import print_function
from ipapython.config import IPAOptionParser
from ipapython.dn import DN
from ipapython import version
from ipapython import ipautil, certdb
from ipalib import api, errors, x509
@@ -40,7 +41,7 @@ from socket import SOCK_STREAM, SOCK_DGRAM
import distutils.spawn
from ipaplatform.paths import paths
import gssapi
from nss import nss
from cryptography.hazmat.primitives import serialization
CONNECT_TIMEOUT = 5
RESPONDERS = [ ]
@@ -121,16 +122,12 @@ def parse_options():
raise OptionValueError(
"%s option '%s' is not an absolute file path" % (opt, value))
initialized = nss.nss_is_initialized()
try:
x509.load_certificate_list_from_file(value)
except Exception:
raise OptionValueError(
"%s option '%s' is not a valid certificate file" %
(opt, value))
finally:
if not initialized:
nss.nss_shutdown()
parser.values.ca_cert_file = value
@@ -472,12 +469,12 @@ def main():
nss_db.create_db(password_file.name)
ca_certs = x509.load_certificate_list_from_file(
options.ca_cert_file, dbdir=nss_db.secdir)
options.ca_cert_file)
for ca_cert in ca_certs:
data = ca_cert.public_bytes(
serialization.Encoding.DER)
nss_db.add_cert(
ca_cert.der_data, str(ca_cert.subject), 'C,,')
del ca_cert
del ca_certs
data, str(DN(ca_cert.subject)), 'C,,')
else:
nss_dir = None

View File

@@ -22,7 +22,6 @@
LDAP shared certificate store.
"""
from nss.error import NSPRError
from pyasn1.error import PyAsn1Error
from ipapython.dn import DN
@@ -31,11 +30,12 @@ from ipalib import errors, x509
def _parse_cert(dercert):
try:
subject = x509.get_subject(dercert, x509.DER)
issuer = x509.get_issuer(dercert, x509.DER)
serial_number = x509.get_serial_number(dercert, x509.DER)
cert = x509.load_certificate(dercert, x509.DER)
subject = DN(cert.subject)
issuer = DN(cert.issuer)
serial_number = cert.serial
public_key_info = x509.get_der_public_key_info(dercert, x509.DER)
except (NSPRError, PyAsn1Error) as e:
except (ValueError, PyAsn1Error) as e:
raise ValueError("failed to decode certificate: %s" % e)
subject = str(subject).replace('\\;', '\\3b')
@@ -54,7 +54,7 @@ def init_ca_entry(entry, dercert, nickname, trusted, ext_key_usage):
if ext_key_usage is not None:
try:
cert_eku = x509.get_ext_key_usage(dercert, x509.DER)
except NSPRError as e:
except ValueError as e:
raise ValueError("failed to decode certificate: %s" % e)
if cert_eku is not None:
cert_eku -= {x509.EKU_SERVER_AUTH, x509.EKU_CLIENT_AUTH,

View File

@@ -28,31 +28,27 @@
#
# cert: the certificate is a PEM-encoded certificate
# dercert: the certificate is DER-encoded
# nsscert: the certificate is an NSS Certificate object
# rawcert: the cert is in an unknown format
from __future__ import print_function
import binascii
import collections
import os
import datetime
import ipaddress
import sys
import base64
import re
from cryptography.hazmat.backends import default_backend
import cryptography.x509
import nss.nss as nss
from nss.error import NSPRError
from pyasn1.type import univ, char, namedtype, tag
from pyasn1.codec.der import decoder, encoder
from pyasn1_modules import rfc2459
import six
from ipalib import api
from ipalib import _
from ipalib import util
from ipalib import errors
from ipaplatform.paths import paths
from ipapython.dn import DN
if six.PY3:
@@ -95,32 +91,16 @@ def strip_header(pem):
return pem
def initialize_nss_database(dbdir=None):
"""
Initializes NSS database, if not initialized yet. Uses a proper database
directory (.ipa/alias or HTTPD_ALIAS_DIR), depending on the value of
api.env.in_tree.
"""
if not nss.nss_is_initialized():
if dbdir is None:
if 'in_tree' in api.env:
if api.env.in_tree:
dbdir = api.env.dot_ipa + os.sep + 'alias'
else:
dbdir = paths.HTTPD_ALIAS_DIR
nss.nss_init(dbdir)
else:
nss.nss_init_nodb()
else:
nss.nss_init(dbdir)
def load_certificate(data, datatype=PEM, dbdir=None):
def load_certificate(data, datatype=PEM):
"""
Given a base64-encoded certificate, with or without the
header/footer, return a request object.
Load an X.509 certificate.
:param datatype: PEM for base64-encoded data (with or without header),
or DER
:return: a python-cryptography ``CertificateSigningRequest`` object.
:raises: ``ValueError`` if unable to load the certificate.
Returns a nss.Certificate type
"""
if type(data) in (tuple, list):
data = data[0]
@@ -129,82 +109,50 @@ def load_certificate(data, datatype=PEM, dbdir=None):
data = strip_header(data)
data = base64.b64decode(data)
initialize_nss_database(dbdir=dbdir)
return cryptography.x509.load_der_x509_certificate(data, default_backend())
if six.PY2:
return nss.Certificate(buffer(data)) # pylint: disable=buffer-builtin
else:
# In python 3 , `bytes` has the buffer interface
return nss.Certificate(data)
def load_certificate_from_file(filename, dbdir=None):
"""
Load a certificate from a PEM file.
Returns a nss.Certificate type
Returns a python-cryptography ``Certificate`` object.
"""
fd = open(filename, 'r')
data = fd.read()
fd.close()
with open(filename, mode='rb') as f:
return load_certificate(f.read(), PEM)
return load_certificate(data, PEM, dbdir)
def load_certificate_list(data, dbdir=None):
def load_certificate_list(data):
"""
Load a certificate list from a sequence of concatenated PEMs.
Return a list of python-cryptography ``Certificate`` objects.
"""
certs = PEM_REGEX.findall(data)
certs = [load_certificate(cert, PEM, dbdir) for cert in certs]
certs = [load_certificate(cert, PEM) for cert in certs]
return certs
def load_certificate_list_from_file(filename, dbdir=None):
def load_certificate_list_from_file(filename):
"""
Load a certificate list from a PEM file.
Returns a list of nss.Certificate objects.
"""
fd = open(filename, 'r')
data = fd.read()
fd.close()
Return a list of python-cryptography ``Certificate`` objects.
return load_certificate_list(data, dbdir)
def get_subject(certificate, datatype=PEM, dbdir=None):
"""
Load an X509.3 certificate and get the subject.
"""
with open(filename) as f:
return load_certificate_list(f.read())
nsscert = load_certificate(certificate, datatype, dbdir)
subject = nsscert.subject
del(nsscert)
return subject
def get_issuer(certificate, datatype=PEM, dbdir=None):
"""
Load an X509.3 certificate and get the issuer.
"""
nsscert = load_certificate(certificate, datatype, dbdir)
issuer = nsscert.issuer
del(nsscert)
return issuer
def get_serial_number(certificate, datatype=PEM, dbdir=None):
"""
Return the decimal value of the serial number.
"""
nsscert = load_certificate(certificate, datatype, dbdir)
serial_number = nsscert.serial_number
del(nsscert)
return serial_number
def is_self_signed(certificate, datatype=PEM, dbdir=None):
nsscert = load_certificate(certificate, datatype, dbdir)
self_signed = (nsscert.issuer == nsscert.subject)
del nsscert
return self_signed
def is_self_signed(certificate, datatype=PEM):
cert = load_certificate(certificate, datatype)
return cert.issuer == cert.subject
def _get_der_field(cert, datatype, dbdir, field):
cert = load_certificate(cert, datatype, dbdir)
cert = cert.der_data
cert = normalize_certificate(cert)
cert = decoder.decode(cert, rfc2459.Certificate())[0]
field = cert['tbsCertificate'][field]
field = encoder.encode(field)
@@ -222,20 +170,17 @@ def get_der_serial_number(cert, datatype=PEM, dbdir=None):
def get_der_public_key_info(cert, datatype=PEM, dbdir=None):
return _get_der_field(cert, datatype, dbdir, 'subjectPublicKeyInfo')
def get_ext_key_usage(certificate, datatype=PEM, dbdir=None):
nsscert = load_certificate(certificate, datatype, dbdir)
if not nsscert.extensions:
def get_ext_key_usage(certificate, datatype=PEM):
cert = load_certificate(certificate, datatype)
try:
eku = cert.extensions.get_extension_for_oid(
cryptography.x509.oid.ExtensionOID.EXTENDED_KEY_USAGE).value
except cryptography.x509.ExtensionNotFound:
return None
for ext in nsscert.extensions:
if ext.oid_tag == nss.SEC_OID_X509_EXT_KEY_USAGE:
break
else:
return None
return set(oid.dotted_string for oid in eku)
eku = nss.x509_ext_key_usage(ext.value, nss.AsDottedDecimal)
eku = set(o[4:] for o in eku)
return eku
def make_pem(data):
"""
@@ -270,27 +215,21 @@ def normalize_certificate(rawcert):
else:
dercert = rawcert
# At this point we should have a certificate, either because the data
# was base64-encoded and now its not or it came in as DER format.
# Let's decode it and see. Fetching the serial number will pass the
# certificate through the NSS DER parser.
# At this point we should have a DER certificate.
# Attempt to decode it.
validate_certificate(dercert, datatype=DER)
return dercert
def validate_certificate(cert, datatype=PEM, dbdir=None):
def validate_certificate(cert, datatype=PEM):
"""
Perform certificate validation by trying to load it into NSS database
Perform cert validation by trying to load it via python-cryptography.
"""
try:
load_certificate(cert, datatype=datatype, dbdir=dbdir)
except NSPRError as nsprerr:
if nsprerr.errno == -8183: # SEC_ERROR_BAD_DER
raise errors.CertificateFormatError(
error=_('improperly formatted DER-encoded certificate'))
else:
raise errors.CertificateFormatError(error=str(nsprerr))
load_certificate(cert, datatype=datatype)
except ValueError as e:
raise errors.CertificateFormatError(error=str(e))
def write_certificate(rawcert, filename):
@@ -379,56 +318,6 @@ def _decode_krb5principalname(data):
return name
GeneralNameInfo = collections.namedtuple(
'GeneralNameInfo', ('type', 'desc', 'value', 'der_value'))
def decode_generalnames(secitem):
"""
Decode a GeneralNames object (this the data for the Subject
Alt Name and Issuer Alt Name extensions, among others).
``secitem``
The input is the DER-encoded extension data, without the
OCTET STRING header, as an nss SecItem object.
Return a list of ``GeneralNameInfo`` namedtuples. The
``der_value`` field is set for otherNames, otherwise it is
``None``.
"""
nss_names = nss.x509_alt_name(secitem, repr_kind=nss.AsObject)
asn1_names = decoder.decode(
secitem.data, asn1Spec=rfc2459.SubjectAltName())[0]
names = []
for nss_name, asn1_name in zip(nss_names, asn1_names):
# NOTE: we use the NSS enum to identify the name type.
# (For otherName we also tuple it up with the type-id OID).
# The enum does not correspond exactly to the ASN.1 tags.
# If we ever want to switch to using the true tag numbers,
# the expression to get the tag is:
#
# asn1_name.getComponent().getTagSet()[0].asTuple()[2]
#
if nss_name.type_enum == nss.certOtherName:
oid = str(asn1_name['otherName']['type-id'])
nametype = (nss_name.type_enum, oid)
der_value = asn1_name['otherName']['value'].asOctets()
else:
nametype = nss_name.type_enum
der_value = None
if nametype == (nss.certOtherName, SAN_KRB5PRINCIPALNAME):
name = _decode_krb5principalname(asn1_name['otherName']['value'])
else:
name = nss_name.name
gni = GeneralNameInfo(nametype, nss_name.type_string, name, der_value)
names.append(gni)
return names
class KRB5PrincipalName(cryptography.x509.general_name.OtherName):
def __init__(self, type_id, value):
super(KRB5PrincipalName, self).__init__(type_id, value)
@@ -464,6 +353,100 @@ def process_othernames(gns):
yield gn
def get_san_general_names(cert):
"""
Return SAN general names from a python-cryptography
certificate object. If the SAN extension is not present,
return an empty sequence.
Because python-cryptography does not yet provide a way to
handle unrecognised critical extensions (which may occur),
we must parse the certificate and extract the General Names.
For uniformity with other code, we manually construct values
of python-crytography GeneralName subtypes.
python-cryptography does not yet provide types for
ediPartyName or x400Address, so we drop these name types.
otherNames are NOT instantiated to more specific types where
the type is known. Use ``process_othernames`` to do that.
When python-cryptography can handle certs with unrecognised
critical extensions and implements ediPartyName and
x400Address, this function (and helpers) will be redundant
and should go away.
"""
tbs = decoder.decode(
cert.tbs_certificate_bytes,
asn1Spec=rfc2459.TBSCertificate()
)[0]
OID_SAN = univ.ObjectIdentifier('2.5.29.17')
gns = []
for ext in tbs['extensions']:
if ext['extnID'] == OID_SAN:
der = decoder.decode(
ext['extnValue'], asn1Spec=univ.OctetString())[0]
gns = decoder.decode(der, asn1Spec=rfc2459.SubjectAltName())[0]
break
GENERAL_NAME_CONSTRUCTORS = {
'rfc822Name': lambda x: cryptography.x509.RFC822Name(unicode(x)),
'dNSName': lambda x: cryptography.x509.DNSName(unicode(x)),
'directoryName': _pyasn1_to_cryptography_directoryname,
'registeredID': _pyasn1_to_cryptography_registeredid,
'iPAddress': _pyasn1_to_cryptography_ipaddress,
'uniformResourceIdentifier':
lambda x: cryptography.x509.UniformResourceIdentifier(unicode(x)),
'otherName': _pyasn1_to_cryptography_othername,
}
result = []
for gn in gns:
gn_type = gn.getName()
if gn_type in GENERAL_NAME_CONSTRUCTORS:
result.append(
GENERAL_NAME_CONSTRUCTORS[gn_type](gn.getComponent()))
return result
def _pyasn1_to_cryptography_directoryname(dn):
attrs = []
# Name is CHOICE { RDNSequence } (only one possibility)
for rdn in dn.getComponent():
for ava in rdn:
attr = cryptography.x509.NameAttribute(
_pyasn1_to_cryptography_oid(ava['type']),
unicode(decoder.decode(ava['value'])[0])
)
attrs.append(attr)
return cryptography.x509.DirectoryName(cryptography.x509.Name(attrs))
def _pyasn1_to_cryptography_registeredid(oid):
return cryptography.x509.RegisteredID(_pyasn1_to_cryptography_oid(oid))
def _pyasn1_to_cryptography_ipaddress(octet_string):
return cryptography.x509.IPAddress(
ipaddress.ip_address(bytes(octet_string)))
def _pyasn1_to_cryptography_othername(on):
return cryptography.x509.OtherName(
_pyasn1_to_cryptography_oid(on['type-id']),
bytes(on['value'])
)
def _pyasn1_to_cryptography_oid(oid):
return cryptography.x509.ObjectIdentifier(str(oid))
def chunk(size, s):
"""Yield chunks of the specified size from the given string.
@@ -486,20 +469,34 @@ def to_hex_with_colons(bs):
return add_colons(binascii.hexlify(bs).decode('utf-8'))
class UTC(datetime.tzinfo):
ZERO = datetime.timedelta(0)
def tzname(self, dt):
return "UTC"
def utcoffset(self, dt):
return self.ZERO
def dst(self, dt):
return self.ZERO
def format_datetime(t):
if t.tzinfo is None:
t = t.replace(tzinfo=UTC())
return unicode(t.strftime("%a %b %d %H:%M:%S %Y %Z"))
if __name__ == '__main__':
# this can be run with:
# python ipalib/x509.py < /etc/ipa/ca.crt
api.bootstrap()
api.finalize()
nss.nss_init_nodb()
# Read PEM certs from stdin and print out its components
# Read PEM cert from stdin and print out its components
certlines = sys.stdin.readlines()
cert = ''.join(certlines)
nsscert = load_certificate(cert)
cert = load_certificate(cert)
print(nsscert)
print(cert)

View File

@@ -22,10 +22,12 @@ import re
import tempfile
import shutil
import base64
from cryptography.hazmat.primitives import serialization
from nss import nss
from nss.error import NSPRError
from ipaplatform.paths import paths
from ipapython.dn import DN
from ipapython.ipa_log_manager import root_logger
from ipapython import ipautil
from ipalib import x509
@@ -258,7 +260,7 @@ class NSSDatabase(object):
'X.509 CERTIFICATE'):
try:
x509.load_certificate(match.group(2))
except NSPRError as e:
except ValueError as e:
if label != 'CERTIFICATE':
root_logger.warning(
"Skipping certificate in %s at line %s: %s",
@@ -334,7 +336,7 @@ class NSSDatabase(object):
# Try to load the file as DER certificate
try:
x509.load_certificate(data, x509.DER)
except NSPRError:
except ValueError:
pass
else:
data = x509.make_pem(base64.b64encode(data))
@@ -379,12 +381,11 @@ class NSSDatabase(object):
raise RuntimeError(
"No server certificates found in %s" % (', '.join(files)))
nss_certs = x509.load_certificate_list(extracted_certs)
nss_cert = None
for nss_cert in nss_certs:
nickname = str(nss_cert.subject)
self.add_cert(nss_cert.der_data, nickname, ',,')
del nss_certs, nss_cert
certs = x509.load_certificate_list(extracted_certs)
for cert in certs:
nickname = str(DN(cert.subject))
data = cert.public_bytes(serialization.Encoding.DER)
self.add_cert(data, nickname, ',,')
if extracted_key:
in_file = ipautil.write_tmp_file(extracted_certs + extracted_key)

View File

@@ -102,7 +102,7 @@ def install_check(standalone, replica_config, options):
cert = db.get_cert_from_db(nickname)
if not cert:
continue
subject = DN(str(x509.get_subject(cert)))
subject = DN(x509.load_certificate(cert).subject)
if subject in (DN('CN=Certificate Authority', subject_base),
DN('CN=IPA RA', subject_base)):
raise ScriptError(

View File

@@ -554,9 +554,9 @@ class CAInstance(DogtagInstance):
config.set("CA", "pki_req_ext_data", "1E0A00530075006200430041")
elif self.external == 2:
cert = x509.load_certificate_from_file(self.cert_file)
cert_file = tempfile.NamedTemporaryFile()
x509.write_certificate(cert.der_data, cert_file.name)
with open(self.cert_file) as f:
x509.write_certificate(f.read(), cert_file.name)
cert_file.flush()
result = ipautil.run(
@@ -778,7 +778,7 @@ class CAInstance(DogtagInstance):
userstate=["1"],
userCertificate=[cert_data],
description=['2;%s;%s;%s' % (
cert.serial_number,
cert.serial,
DN(('CN', 'Certificate Authority'), self.subject_base),
DN(('CN', 'IPA RA'), self.subject_base))])
conn.add_entry(entry)
@@ -1674,8 +1674,9 @@ def update_people_entry(dercert):
is needed when a certificate is renewed.
"""
def make_filter(dercert):
subject = x509.get_subject(dercert, datatype=x509.DER)
issuer = x509.get_issuer(dercert, datatype=x509.DER)
cert = x509.load_certificate(dercert, datatype=x509.DER)
subject = DN(cert.subject)
issuer = DN(cert.issuer)
return ldap2.ldap2.combine_filters(
[
ldap2.ldap2.make_filter({'objectClass': 'inetOrgPerson'}),
@@ -1686,9 +1687,10 @@ def update_people_entry(dercert):
ldap2.ldap2.MATCH_ALL)
def make_entry(dercert, entry):
serial_number = x509.get_serial_number(dercert, datatype=x509.DER)
subject = x509.get_subject(dercert, datatype=x509.DER)
issuer = x509.get_issuer(dercert, datatype=x509.DER)
cert = x509.load_certificate(dercert, datatype=x509.DER)
serial_number = cert.serial
subject = DN(cert.subject)
issuer = DN(cert.issuer)
entry['usercertificate'].append(dercert)
entry['description'] = '2;%d;%s;%s' % (serial_number, issuer, subject)
return entry
@@ -1702,15 +1704,16 @@ def update_authority_entry(dercert):
serial number to match the given cert.
"""
def make_filter(dercert):
subject = x509.get_subject(dercert, datatype=x509.DER)
cert = x509.load_certificate(dercert, datatype=x509.DER)
subject = str(DN(cert.subject))
return ldap2.ldap2.make_filter(
dict(objectclass='authority', authoritydn=subject),
rules=ldap2.ldap2.MATCH_ALL,
)
def make_entry(dercert, entry):
serial_number = x509.get_serial_number(dercert, datatype=x509.DER)
entry['authoritySerial'] = serial_number
cert = x509.load_certificate(dercert, datatype=x509.DER)
entry['authoritySerial'] = cert.serial
return entry
return __update_entry_from_cert(make_filter, make_entry, dercert)

View File

@@ -60,9 +60,8 @@ def get_cert_nickname(cert):
representation of the first RDN in the subject and subject_dn
is a DN object.
"""
nsscert = x509.load_certificate(cert)
subject = str(nsscert.subject)
dn = DN(subject)
cert_obj = x509.load_certificate(cert)
dn = DN(cert_obj.subject)
return (str(dn[0]), dn)
@@ -304,8 +303,8 @@ class CertDB(object):
return
cert = self.get_cert_from_db(nickname)
nsscert = x509.load_certificate(cert, dbdir=self.secdir)
subject = str(nsscert.subject)
cert_obj = x509.load_certificate(cert)
subject = str(DN(cert_obj.subject))
certmonger.add_principal(request_id, principal)
certmonger.add_subject(request_id, subject)

View File

@@ -921,10 +921,9 @@ def load_pkcs12(cert_files, key_password, key_nickname, ca_cert_files,
if ca_cert is None:
ca_cert = cert
nss_cert = x509.load_certificate(cert, x509.DER)
subject = DN(str(nss_cert.subject))
issuer = DN(str(nss_cert.issuer))
del nss_cert
cert_obj = x509.load_certificate(cert, x509.DER)
subject = DN(cert_obj.subject)
issuer = DN(cert_obj.issuer)
if subject == issuer:
break
@@ -1046,10 +1045,9 @@ def load_external_cert(files, subject_base):
for nickname, _trust_flags in nssdb.list_certs():
cert = nssdb.get_cert(nickname, pem=True)
nss_cert = x509.load_certificate(cert)
subject = DN(str(nss_cert.subject))
issuer = DN(str(nss_cert.issuer))
del nss_cert
cert_obj = x509.load_certificate(cert)
subject = DN(cert_obj.subject)
issuer = DN(cert_obj.issuer)
cache[nickname] = (cert, subject, issuer)
if subject == ca_subject:

View File

@@ -21,8 +21,7 @@ from __future__ import print_function
import os
from optparse import OptionGroup
from nss import nss
from nss.error import NSPRError
from cryptography.hazmat.primitives import serialization
import gssapi
from ipapython import admintool, certmonger, ipautil
@@ -187,7 +186,7 @@ class CACertManage(admintool.AdminTool):
"--external-cert-file=/path/to/signed_certificate "
"--external-cert-file=/path/to/external_ca_certificate")
def renew_external_step_2(self, ca, old_cert):
def renew_external_step_2(self, ca, old_cert_der):
print("Importing the renewed CA certificate, please wait")
options = self.options
@@ -195,55 +194,54 @@ class CACertManage(admintool.AdminTool):
cert_file, ca_file = installutils.load_external_cert(
options.external_cert_files, x509.subject_base())
nss_cert = None
nss.nss_init(paths.PKI_TOMCAT_ALIAS_DIR)
try:
nss_cert = x509.load_certificate(old_cert, x509.DER)
subject = nss_cert.subject
der_subject = x509.get_der_subject(old_cert, x509.DER)
#pylint: disable=E1101
pkinfo = nss_cert.subject_public_key_info.format()
#pylint: enable=E1101
old_cert_obj = x509.load_certificate(old_cert_der, x509.DER)
old_der_subject = x509.get_der_subject(old_cert_der, x509.DER)
old_spki = old_cert_obj.public_key().public_bytes(
serialization.Encoding.DER,
serialization.PublicFormat.SubjectPublicKeyInfo
)
nss_cert = x509.load_certificate_from_file(cert_file.name)
cert = nss_cert.der_data
if nss_cert.subject != subject:
raise admintool.ScriptError(
"Subject name mismatch (visit "
"http://www.freeipa.org/page/Troubleshooting for "
"troubleshooting guide)")
if x509.get_der_subject(cert, x509.DER) != der_subject:
raise admintool.ScriptError(
"Subject name encoding mismatch (visit "
"http://www.freeipa.org/page/Troubleshooting for "
"troubleshooting guide)")
#pylint: disable=E1101
if nss_cert.subject_public_key_info.format() != pkinfo:
raise admintool.ScriptError(
"Subject public key info mismatch (visit "
"http://www.freeipa.org/page/Troubleshooting for "
"troubleshooting guide)")
#pylint: enable=E1101
finally:
del nss_cert
nss.nss_shutdown()
with open(cert_file.name) as f:
new_cert_data = f.read()
new_cert_der = x509.normalize_certificate(new_cert_data)
new_cert_obj = x509.load_certificate(new_cert_der, x509.DER)
new_der_subject = x509.get_der_subject(new_cert_der, x509.DER)
new_spki = new_cert_obj.public_key().public_bytes(
serialization.Encoding.DER,
serialization.PublicFormat.SubjectPublicKeyInfo
)
if new_cert_obj.subject != old_cert_obj.subject:
raise admintool.ScriptError(
"Subject name mismatch (visit "
"http://www.freeipa.org/page/Troubleshooting for "
"troubleshooting guide)")
if new_der_subject != old_der_subject:
raise admintool.ScriptError(
"Subject name encoding mismatch (visit "
"http://www.freeipa.org/page/Troubleshooting for "
"troubleshooting guide)")
if new_spki != old_spki:
raise admintool.ScriptError(
"Subject public key info mismatch (visit "
"http://www.freeipa.org/page/Troubleshooting for "
"troubleshooting guide)")
with certs.NSSDatabase() as tmpdb:
pw = ipautil.write_tmp_file(ipautil.ipa_generate_password())
tmpdb.create_db(pw.name)
tmpdb.add_cert(old_cert, 'IPA CA', 'C,,')
tmpdb.add_cert(old_cert_der, 'IPA CA', 'C,,')
try:
tmpdb.add_cert(cert, 'IPA CA', 'C,,')
tmpdb.add_cert(new_cert_der, 'IPA CA', 'C,,')
except ipautil.CalledProcessError as e:
raise admintool.ScriptError(
"Not compatible with the current CA certificate: %s" % e)
ca_certs = x509.load_certificate_list_from_file(ca_file.name)
for ca_cert in ca_certs:
tmpdb.add_cert(ca_cert.der_data, str(ca_cert.subject), 'C,,')
del ca_certs
del ca_cert
data = ca_cert.public_bytes(serialization.Encoding.DER)
tmpdb.add_cert(data, str(DN(ca_cert.subject)), 'C,,')
try:
tmpdb.verify_ca_cert_validity('IPA CA')
@@ -266,14 +264,14 @@ class CACertManage(admintool.AdminTool):
('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
try:
entry = conn.get_entry(dn, ['usercertificate'])
entry['usercertificate'] = [cert]
entry['usercertificate'] = [new_cert_der]
conn.update_entry(entry)
except errors.NotFound:
entry = conn.make_entry(
dn,
objectclass=['top', 'pkiuser', 'nscontainer'],
cn=[self.cert_nickname],
usercertificate=[cert])
usercertificate=[new_cert_der])
conn.add_entry(entry)
except errors.EmptyModlist:
pass
@@ -313,21 +311,16 @@ class CACertManage(admintool.AdminTool):
options = self.options
cert_filename = self.args[1]
nss_cert = None
try:
try:
nss_cert = x509.load_certificate_from_file(cert_filename)
except IOError as e:
raise admintool.ScriptError(
"Can't open \"%s\": %s" % (cert_filename, e))
except (TypeError, NSPRError, ValueError) as e:
raise admintool.ScriptError("Not a valid certificate: %s" % e)
subject = nss_cert.subject
cert = nss_cert.der_data
finally:
del nss_cert
cert_obj = x509.load_certificate_from_file(cert_filename)
except IOError as e:
raise admintool.ScriptError(
"Can't open \"%s\": %s" % (cert_filename, e))
except (TypeError, ValueError) as e:
raise admintool.ScriptError("Not a valid certificate: %s" % e)
cert = cert_obj.public_bytes(serialization.Encoding.DER)
nickname = options.nickname or str(subject)
nickname = options.nickname or str(DN(cert_obj.subject))
ca_certs = certstore.get_ca_certs_nss(api.Backend.ldap2,
api.env.basedn,

View File

@@ -297,7 +297,7 @@ class KRAInstance(DogtagInstance):
usertype=["undefined"],
userCertificate=[cert_data],
description=['2;%s;%s;%s' % (
cert.serial_number,
cert.serial,
DN(('CN', 'Certificate Authority'), self.subject_base),
DN(('CN', 'IPA RA'), self.subject_base))])
conn.add_entry(entry)

View File

@@ -22,11 +22,11 @@
import base64
import collections
import datetime
from operator import attrgetter
import os
import cryptography.x509
from nss import nss
from nss.error import NSPRError
from cryptography.hazmat.primitives import hashes
import six
from ipalib import Command, Str, Int, Flag
@@ -224,7 +224,7 @@ def bind_principal_can_manage_cert(cert):
"""Check that the bind principal can manage the given cert.
``cert``
An NSS certificate object.
A python-cryptography ``Certificate`` object.
"""
bind_principal = kerberos.Principal(getattr(context, 'principal'))
@@ -233,9 +233,14 @@ def bind_principal_can_manage_cert(cert):
hostname = bind_principal.hostname
# If we have a hostname we want to verify that the subject
# of the certificate matches it.
return hostname == cert.subject.common_name #pylint: disable=E1101
# Verify that hostname matches subject of cert.
# We check the "most-specific" CN value.
cns = cert.subject.get_attributes_for_oid(
cryptography.x509.oid.NameOID.COMMON_NAME)
if len(cns) == 0:
return False # no CN in subject
else:
return hostname == cns[-1].value
class BaseCertObject(Object):
@@ -370,30 +375,27 @@ class BaseCertObject(Object):
attribute.
"""
cert = obj.get('certificate')
if cert is not None:
cert = x509.load_certificate(cert)
obj['subject'] = DN(unicode(cert.subject))
obj['issuer'] = DN(unicode(cert.issuer))
obj['serial_number'] = cert.serial_number
obj['valid_not_before'] = unicode(cert.valid_not_before_str)
obj['valid_not_after'] = unicode(cert.valid_not_after_str)
if 'certificate' in obj:
cert = x509.load_certificate(obj['certificate'])
obj['subject'] = DN(cert.subject)
obj['issuer'] = DN(cert.issuer)
obj['serial_number'] = cert.serial
obj['valid_not_before'] = x509.format_datetime(
cert.not_valid_before)
obj['valid_not_after'] = x509.format_datetime(
cert.not_valid_after)
if full:
obj['md5_fingerprint'] = x509.to_hex_with_colons(
nss.md5_digest(cert.der_data))
cert.fingerprint(hashes.MD5()))
obj['sha1_fingerprint'] = x509.to_hex_with_colons(
nss.sha1_digest(cert.der_data))
cert.fingerprint(hashes.SHA1()))
try:
ext_san = cert.get_extension(nss.SEC_OID_X509_SUBJECT_ALT_NAME)
general_names = x509.decode_generalnames(ext_san.value)
except KeyError:
general_names = []
general_names = x509.process_othernames(
x509.get_san_general_names(cert))
for name_type, _desc, name, der_name in general_names:
for gn in general_names:
try:
self._add_san_attribute(
obj, full, name_type, name, der_name)
self._add_san_attribute(obj, full, gn)
except Exception:
# Invalid GeneralName (i.e. not a valid X.509 cert);
# don't fail but log something about it
@@ -404,45 +406,52 @@ class BaseCertObject(Object):
if serial_number is not None:
obj['serial_number_hex'] = u'0x%X' % serial_number
def _add_san_attribute(
self, obj, full, name_type, name, der_name):
def _add_san_attribute(self, obj, full, gn):
name_type_map = {
nss.certRFC822Name: 'san_rfc822name',
nss.certDNSName: 'san_dnsname',
nss.certX400Address: 'san_x400address',
nss.certDirectoryName: 'san_directoryname',
nss.certEDIPartyName: 'san_edipartyname',
nss.certURI: 'san_uri',
nss.certIPAddress: 'san_ipaddress',
nss.certRegisterID: 'san_oid',
(nss.certOtherName, x509.SAN_UPN): 'san_other_upn',
(nss.certOtherName, x509.SAN_KRB5PRINCIPALNAME): 'san_other_kpn',
cryptography.x509.RFC822Name:
('san_rfc822name', attrgetter('value')),
cryptography.x509.DNSName: ('san_dnsname', attrgetter('value')),
# cryptography.x509.???: 'san_x400address',
cryptography.x509.DirectoryName:
('san_directoryname', lambda x: DN(x.value)),
# cryptography.x509.???: 'san_edipartyname',
cryptography.x509.UniformResourceIdentifier:
('san_uri', attrgetter('value')),
cryptography.x509.IPAddress:
('san_ipaddress', attrgetter('value')),
cryptography.x509.RegisteredID:
('san_oid', attrgetter('value.dotted_string')),
cryptography.x509.OtherName: ('san_other', _format_othername),
x509.UPN: ('san_other_upn', attrgetter('name')),
x509.KRB5PrincipalName: ('san_other_kpn', attrgetter('name')),
}
default_attrs = {
'san_rfc822name', 'san_dnsname', 'san_other_upn', 'san_other_kpn',
}
attr_name = name_type_map.get(name_type, 'san_other')
if type(gn) not in name_type_map:
return
attr_name, format_name = name_type_map[type(gn)]
if full or attr_name in default_attrs:
if attr_name != 'san_other':
name_formatted = name
else:
# display as "OID : b64(DER)"
name_formatted = u'{}:{}'.format(
name_type[1], base64.b64encode(der_name))
attr_value = self.params[attr_name].type(name_formatted)
attr_value = self.params[attr_name].type(format_name(gn))
obj.setdefault(attr_name, []).append(attr_value)
if full and attr_name.startswith('san_other_'):
# also include known otherName in generic otherName attribute
name_formatted = u'{}:{}'.format(
name_type[1], base64.b64encode(der_name))
attr_value = self.params['san_other'].type(name_formatted)
attr_value = self.params['san_other'].type(_format_othername(gn))
obj.setdefault('san_other', []).append(attr_value)
def _format_othername(on):
"""Format a python-cryptography OtherName for display."""
return u'{}:{}'.format(
on.type_id.dotted_string,
base64.b64encode(on.value)
)
class BaseCertMethod(Method):
def get_options(self):
yield self.obj.params['cacn'].clone(query=True)
@@ -909,7 +918,7 @@ class cert_show(Retrieve, CertMethod, VirtualCommand):
raise acierr # pylint: disable=E0702
ca_obj = api.Command.ca_show(options['cacn'])['result']
if DN(unicode(cert.issuer)) != DN(ca_obj['ipacasubjectdn'][0]):
if DN(cert.issuer) != DN(ca_obj['ipacasubjectdn'][0]):
# DN of cert differs from what we requested
raise errors.NotFound(
reason=_("Certificate with serial number %(serial)s "
@@ -1132,16 +1141,16 @@ class cert_find(Search, CertMethod):
def _get_cert_key(self, cert):
try:
nss_cert = x509.load_certificate(cert, x509.DER)
except NSPRError as e:
cert_obj = x509.load_certificate(cert, x509.DER)
except ValueError as e:
message = messages.SearchResultTruncated(
reason=_("failed to load certificate: %s") % e,
)
self.add_message(message)
raise ValueError("failed to load certificate")
raise
return (DN(unicode(nss_cert.issuer)), nss_cert.serial_number)
return (DN(cert_obj.issuer), cert_obj.serial)
def _get_cert_obj(self, cert, all, raw, pkey_only):
obj = {'certificate': unicode(base64.b64encode(cert))}

View File

@@ -19,6 +19,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from cryptography.hazmat.primitives import hashes
import six
from ipalib import api, errors, messages
@@ -49,8 +50,6 @@ from ipalib import output
from ipapython import kerberos
from ipapython.dn import DN
import nss.nss as nss
if six.PY3:
unicode = str
@@ -268,16 +267,17 @@ def set_certificate_attrs(entry_attrs):
cert = entry_attrs['usercertificate']
cert = x509.normalize_certificate(cert)
cert = x509.load_certificate(cert, datatype=x509.DER)
entry_attrs['subject'] = unicode(cert.subject)
entry_attrs['serial_number'] = unicode(cert.serial_number)
entry_attrs['serial_number_hex'] = u'0x%X' % cert.serial_number
entry_attrs['issuer'] = unicode(cert.issuer)
entry_attrs['valid_not_before'] = unicode(cert.valid_not_before_str)
entry_attrs['valid_not_after'] = unicode(cert.valid_not_after_str)
entry_attrs['subject'] = unicode(DN(cert.subject))
entry_attrs['serial_number'] = unicode(cert.serial)
entry_attrs['serial_number_hex'] = u'0x%X' % cert.serial
entry_attrs['issuer'] = unicode(DN(cert.issuer))
entry_attrs['valid_not_before'] = x509.format_datetime(
cert.not_valid_before)
entry_attrs['valid_not_after'] = x509.format_datetime(cert.not_valid_after)
entry_attrs['md5_fingerprint'] = x509.to_hex_with_colons(
nss.md5_digest(cert.der_data))
cert.fingerprint(hashes.MD5()))
entry_attrs['sha1_fingerprint'] = x509.to_hex_with_colons(
nss.sha1_digest(cert.der_data))
cert.fingerprint(hashes.SHA1()))
def check_required_principal(ldap, principal):
"""

View File

@@ -22,9 +22,9 @@ Test the `ipalib.x509` module.
"""
import base64
import datetime
import pytest
from nss.error import NSPRError
from ipalib import x509
from ipapython.dn import DN
@@ -57,17 +57,25 @@ class test_x509(object):
# Load a good cert
x509.load_certificate(goodcert)
# Should handle list/tuple
x509.load_certificate((goodcert,))
x509.load_certificate([goodcert])
# Load a good cert with headers
newcert = '-----BEGIN CERTIFICATE-----' + goodcert + '-----END CERTIFICATE-----'
x509.load_certificate(newcert)
# Should handle list/tuple
x509.load_certificate((newcert,))
x509.load_certificate([newcert])
# Load a good cert with bad headers
newcert = '-----BEGIN CERTIFICATE-----' + goodcert
with pytest.raises((TypeError, ValueError)):
x509.load_certificate(newcert)
# Load a bad cert
with pytest.raises(NSPRError):
with pytest.raises(ValueError):
x509.load_certificate(badcert)
def test_1_load_der_cert(self):
@@ -80,53 +88,23 @@ class test_x509(object):
# Load a good cert
x509.load_certificate(der, x509.DER)
def test_2_get_subject(self):
"""
Test retrieving the subject
"""
subject = x509.get_subject(goodcert)
assert DN(str(subject)) == DN(('CN','ipa.example.com'),('O','IPA'))
der = base64.b64decode(goodcert)
subject = x509.get_subject(der, x509.DER)
assert DN(str(subject)) == DN(('CN','ipa.example.com'),('O','IPA'))
# We should be able to pass in a tuple/list of certs too
subject = x509.get_subject((goodcert))
assert DN(str(subject)) == DN(('CN','ipa.example.com'),('O','IPA'))
subject = x509.get_subject([goodcert])
assert DN(str(subject)) == DN(('CN','ipa.example.com'),('O','IPA'))
def test_2_get_serial_number(self):
"""
Test retrieving the serial number
"""
serial = x509.get_serial_number(goodcert)
assert serial == 1093
der = base64.b64decode(goodcert)
serial = x509.get_serial_number(der, x509.DER)
assert serial == 1093
# We should be able to pass in a tuple/list of certs too
serial = x509.get_serial_number((goodcert))
assert serial == 1093
serial = x509.get_serial_number([goodcert])
assert serial == 1093
# Should handle list/tuple
x509.load_certificate((der,), x509.DER)
x509.load_certificate([der], x509.DER)
def test_3_cert_contents(self):
"""
Test the contents of a certificate
"""
# Verify certificate contents. This exercises python-nss more than
# anything but confirms our usage of it.
# Verify certificate contents. This exercises python-cryptography
# more than anything but confirms our usage of it.
not_before = datetime.datetime(2010, 6, 25, 13, 0, 42)
not_after = datetime.datetime(2015, 6, 25, 13, 0, 42)
cert = x509.load_certificate(goodcert)
assert DN(str(cert.subject)) == DN(('CN','ipa.example.com'),('O','IPA'))
assert DN(str(cert.issuer)) == DN(('CN','IPA Test Certificate Authority'))
assert cert.serial_number == 1093
assert cert.valid_not_before_str == 'Fri Jun 25 13:00:42 2010 UTC'
assert cert.valid_not_after_str == 'Thu Jun 25 13:00:42 2015 UTC'
assert DN(cert.subject) == DN(('CN', 'ipa.example.com'), ('O', 'IPA'))
assert DN(cert.issuer) == DN(('CN', 'IPA Test Certificate Authority'))
assert cert.serial == 1093
assert cert.not_valid_before == not_before
assert cert.not_valid_after == not_after

View File

@@ -80,7 +80,7 @@ class test_ldap(object):
entry_attrs = self.conn.get_entry(self.dn, ['usercertificate'])
cert = entry_attrs.get('usercertificate')
cert = cert[0]
serial = unicode(x509.get_serial_number(cert, x509.DER))
serial = x509.load_certificate(cert, x509.DER).serial
assert serial is not None
def test_simple(self):
@@ -99,7 +99,7 @@ class test_ldap(object):
entry_attrs = self.conn.get_entry(self.dn, ['usercertificate'])
cert = entry_attrs.get('usercertificate')
cert = cert[0]
serial = unicode(x509.get_serial_number(cert, x509.DER))
serial = x509.load_certificate(cert, x509.DER).serial
assert serial is not None
def test_Backend(self):
@@ -127,7 +127,7 @@ class test_ldap(object):
entry_attrs = result['result']
cert = entry_attrs.get('usercertificate')
cert = cert[0]
serial = unicode(x509.get_serial_number(cert, x509.DER))
serial = x509.load_certificate(cert, x509.DER).serial
assert serial is not None
def test_autobind(self):
@@ -143,7 +143,7 @@ class test_ldap(object):
entry_attrs = self.conn.get_entry(self.dn, ['usercertificate'])
cert = entry_attrs.get('usercertificate')
cert = cert[0]
serial = unicode(x509.get_serial_number(cert, x509.DER))
serial = x509.load_certificate(cert, x509.DER).serial
assert serial is not None

View File

@@ -20,7 +20,6 @@
import os
import pytest
from nss import nss
from ipalib.x509 import initialize_nss_database
from ipaserver.install.ipa_otptoken_import import PSKCDocument, ValidationError
@@ -30,9 +29,6 @@ basename = os.path.join(os.path.dirname(__file__), "data")
@pytest.mark.tier1
class test_otptoken_import(object):
def teardown(self):
initialize_nss_database()
def test_figure3(self):
doc = PSKCDocument(os.path.join(basename, "pskc-figure3.xml"))
assert doc.keyname is None