From dd69c7dbe68e8f8674994a54ea913f2dd2e52c32 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Wed, 8 Jun 2011 10:54:41 -0400 Subject: Make data type of certificates more obvious/predictable internally. For the most part certificates will be treated as being in DER format. When we load a certificate we will generally accept it in any format but will convert it to DER before proceeding in normalize_certificate(). This also re-arranges a bit of code to pull some certificate-specific functions out of ipalib/plugins/service.py into ipalib/x509.py. This also tries to use variable names to indicate what format the certificate is in at any given point: dercert: DER cert: PEM nsscert: a python-nss Certificate object rawcert: unknown format ticket 32 --- ipalib/x509.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 6 deletions(-) (limited to 'ipalib/x509.py') diff --git a/ipalib/x509.py b/ipalib/x509.py index fc6f9c12..77d6aabf 100644 --- a/ipalib/x509.py +++ b/ipalib/x509.py @@ -17,12 +17,30 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# Certificates should be stored internally DER-encoded. We can be passed +# a certificate several ways: read if from LDAP, read it from a 3rd party +# app (dogtag, candlepin, etc) or as user input. The normalize_certificate() +# function will convert an incoming certificate to DER-encoding. + +# Conventions +# +# Where possible the following naming conventions are used: +# +# 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 + import os import sys import base64 import nss.nss as nss +from nss.error import NSPRError from ipapython import ipautil from ipalib import api +from ipalib import _ +from ipalib import util +from ipalib import errors PEM = 0 DER = 1 @@ -66,17 +84,103 @@ def get_subject(certificate, datatype=PEM): Load an X509.3 certificate and get the subject. """ - cert = load_certificate(certificate, datatype) - return cert.subject + nsscert = load_certificate(certificate, datatype) + return nsscert.subject def get_serial_number(certificate, datatype=PEM): """ Return the decimal value of the serial number. """ - cert = load_certificate(certificate, datatype) - return cert.serial_number + nsscert = load_certificate(certificate, datatype) + return nsscert.serial_number + +def make_pem(data): + """ + Convert a raw base64-encoded blob into something that looks like a PE + file with lines split to 64 characters and proper headers. + """ + pemcert = '\n'.join([data[x:x+64] for x in range(0, len(data), 64)]) + return '-----BEGIN CERTIFICATE-----\n' + \ + pemcert + \ + '\n-----END CERTIFICATE-----' + +def normalize_certificate(rawcert): + """ + Incoming certificates should be DER-encoded. If not it is converted to + DER-format. + + Note that this can't be a normalizer on a Param because only unicode + variables are normalized. + """ + if not rawcert: + return None + + rawcert = strip_header(rawcert) + + if util.isvalid_base64(rawcert): + try: + dercert = base64.b64decode(rawcert) + except Exception, e: + raise errors.Base64DecodeError(reason=str(e)) + 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. + try: + serial = unicode(get_serial_number(dercert, DER)) + except NSPRError, 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)) + + return dercert + +def write_certificate(rawcert, filename): + """ + Write the certificate to a file in PEM format. + + The cert value can be either DER or PEM-encoded, it will be normalized + to DER regardless, then back out to PEM. + """ + dercert = normalize_certificate(rawcert) + + try: + fp = open(filename, 'w') + fp.write(make_pem(base64.b64encode(dercert))) + fp.close() + except (IOError, OSError), e: + raise errors.FileError(reason=str(e)) + +def verify_cert_subject(ldap, hostname, dercert): + """ + Verify that the certificate issuer we're adding matches the issuer + base of our installation. + + This assumes the certificate has already been normalized. + + This raises an exception on errors and returns nothing otherwise. + """ + nsscert = load_certificate(dercert, datatype=DER) + subject = str(nsscert.subject) + issuer = str(nsscert.issuer) + + # Handle both supported forms of issuer, from selfsign and dogtag. + if ((issuer != 'CN=%s Certificate Authority' % api.env.realm) and + (issuer != 'CN=Certificate Authority,O=%s' % api.env.realm)): + raise errors.CertificateOperationError(error=_('Issuer "%(issuer)s" does not match the expected issuer') % \ + {'issuer' : issuer}) if __name__ == '__main__': + # this can be run with: + # python ipalib/x509.py < /etc/ipa/ca.crt + + from ipalib import api + api.bootstrap() + api.finalize() nss.nss_init_nodb() @@ -85,6 +189,6 @@ if __name__ == '__main__': certlines = sys.stdin.readlines() cert = ''.join(certlines) - cert = load_certificate(cert) + nsscert = load_certificate(cert) - print cert + print nsscert -- cgit