mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Clean up crypto code, take advantage of new nss-python capabilities
This patch does the following: - drops our in-tree x509v3 parser to use the python-nss one - return more information on certificates - make an API change, renaming cert-get to cert-show - Drop a lot of duplicated code
This commit is contained in:
286
ipalib/x509.py
286
ipalib/x509.py
@@ -1,199 +1,31 @@
|
||||
"""
|
||||
Imported from pyasn1 project:
|
||||
# Authors:
|
||||
# Rob Crittenden <rcritten@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2010 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; version 2 only
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Copyright (c) 2005-2009 Ilya Etingof <ilya@glas.net>, all rights reserved.
|
||||
|
||||
THIS SOFTWARE IS NOT FAULT TOLERANT AND SHOULD NOT BE USED IN ANY SITUATION
|
||||
ENDANGERING HUMAN LIFE OR PROPERTY.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* The name of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
|
||||
"""
|
||||
Enhancements released under IPA GPLv2 only license
|
||||
"""
|
||||
|
||||
# Read ASN.1/PEM X.509 certificates on stdin, parse each into plain text,
|
||||
# then build substrate from it
|
||||
import sys, string, base64
|
||||
from pyasn1.type import tag,namedtype,namedval,univ,constraint,char,useful
|
||||
from pyasn1.codec.der import decoder, encoder
|
||||
from pyasn1 import error
|
||||
|
||||
# Would be autogenerated from ASN.1 source by a ASN.1 parser
|
||||
# X.509 spec (rfc2459)
|
||||
import sys
|
||||
import base64
|
||||
import nss.nss as nss
|
||||
from ipapython import ipautil
|
||||
from ipalib import api
|
||||
|
||||
PEM = 0
|
||||
DER = 1
|
||||
|
||||
# Common OIDs found in a subject
|
||||
oidtable = { "2.5.4.3": "CN",
|
||||
"2.5.4.6": "C",
|
||||
"2.5.4.7": "L",
|
||||
"2.5.4.8": "ST",
|
||||
"2.5.4.10": "O",
|
||||
"2.5.4.11": "OU",
|
||||
"1.2.840.113549.1.9.1": "E",
|
||||
"0.9.2342.19200300.100.1.25": "DC",
|
||||
}
|
||||
|
||||
MAX = 64 # XXX ?
|
||||
|
||||
class DirectoryString(univ.Choice):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
|
||||
namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
|
||||
namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
|
||||
namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
|
||||
namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
|
||||
namedtype.NamedType('ia5String', char.IA5String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))) # hm, this should not be here!? XXX
|
||||
)
|
||||
|
||||
class AttributeValue(DirectoryString): pass
|
||||
|
||||
class AttributeType(univ.ObjectIdentifier): pass
|
||||
|
||||
class AttributeTypeAndValue(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('type', AttributeType()),
|
||||
namedtype.NamedType('value', AttributeValue())
|
||||
)
|
||||
|
||||
class RelativeDistinguishedName(univ.SetOf):
|
||||
componentType = AttributeTypeAndValue()
|
||||
|
||||
class RDNSequence(univ.SequenceOf):
|
||||
componentType = RelativeDistinguishedName()
|
||||
|
||||
class Name(univ.Choice):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('', RDNSequence())
|
||||
)
|
||||
|
||||
def get_components(self):
|
||||
components = self.getComponentByPosition(0)
|
||||
complist = []
|
||||
for idx in range(len(components)):
|
||||
attrandvalue = components[idx].getComponentByPosition(0)
|
||||
oid = attrandvalue.getComponentByPosition(0)
|
||||
# FIXME, should handle any string type
|
||||
value = attrandvalue.getComponentByPosition(1).getComponentByType(char.PrintableString.tagSet)
|
||||
if value is None:
|
||||
value = attrandvalue.getComponentByPosition(1).getComponentByType(char.UTF8String.tagSet)
|
||||
if value is None:
|
||||
value = attrandvalue.getComponentByPosition(1).getComponentByType(char.IA5String.tagSet)
|
||||
vout = value.prettyOut(value).decode('utf-8')
|
||||
oidout = oid.prettyOut(oid).decode('utf-8')
|
||||
c = ((oidtable.get(oidout, oidout), vout))
|
||||
complist.append(c)
|
||||
|
||||
return tuple(complist)
|
||||
|
||||
class AlgorithmIdentifier(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('algorithm', univ.ObjectIdentifier()),
|
||||
namedtype.OptionalNamedType('parameters', univ.Null())
|
||||
# XXX syntax screwed?
|
||||
# namedtype.OptionalNamedType('parameters', univ.ObjectIdentifier())
|
||||
)
|
||||
|
||||
class Extension(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('extnID', univ.ObjectIdentifier()),
|
||||
namedtype.DefaultedNamedType('critical', univ.Boolean('False')),
|
||||
namedtype.NamedType('extnValue', univ.OctetString())
|
||||
)
|
||||
|
||||
class Extensions(univ.SequenceOf):
|
||||
componentType = Extension()
|
||||
sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)
|
||||
|
||||
class SubjectPublicKeyInfo(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('algorithm', AlgorithmIdentifier()),
|
||||
namedtype.NamedType('subjectPublicKey', univ.BitString())
|
||||
)
|
||||
|
||||
class UniqueIdentifier(univ.BitString): pass
|
||||
|
||||
class Time(univ.Choice):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('utcTime', useful.UTCTime()),
|
||||
namedtype.NamedType('generalTime', useful.GeneralizedTime())
|
||||
)
|
||||
|
||||
class Validity(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('notBefore', Time()),
|
||||
namedtype.NamedType('notAfter', Time())
|
||||
)
|
||||
|
||||
class CertificateSerialNumber(univ.Integer): pass
|
||||
|
||||
class Version(univ.Integer):
|
||||
namedValues = namedval.NamedValues(
|
||||
('v1', 0), ('v2', 1), ('v3', 2)
|
||||
)
|
||||
|
||||
class TBSCertificate(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.DefaultedNamedType('version', Version('v1', tagSet=Version.tagSet.tagExplicitly(tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))),
|
||||
namedtype.NamedType('serialNumber', CertificateSerialNumber()),
|
||||
namedtype.NamedType('signature', AlgorithmIdentifier()),
|
||||
namedtype.NamedType('issuer', Name()),
|
||||
namedtype.NamedType('validity', Validity()),
|
||||
namedtype.NamedType('subject', Name()),
|
||||
namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()),
|
||||
namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
||||
namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
|
||||
namedtype.OptionalNamedType('extensions', Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
|
||||
)
|
||||
|
||||
class Certificate(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('tbsCertificate', TBSCertificate()),
|
||||
namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
|
||||
namedtype.NamedType('signatureValue', univ.BitString())
|
||||
)
|
||||
|
||||
def get_version(self):
|
||||
info = self.getComponentByName('tbsCertificate')
|
||||
version = info.getComponentByName('version')
|
||||
return version._value
|
||||
|
||||
def get_subject(self):
|
||||
info = self.getComponentByName('tbsCertificate')
|
||||
return info.getComponentByName('subject')
|
||||
|
||||
def get_serial_number(self):
|
||||
'return the serial number as a Python long object'
|
||||
info = self.getComponentByName('tbsCertificate')
|
||||
return long(info.getComponentByName('serialNumber'))
|
||||
|
||||
# end of ASN.1 data structures
|
||||
|
||||
def strip_header(pem):
|
||||
"""
|
||||
Remove the header and footer from a certificate.
|
||||
@@ -205,69 +37,53 @@ def strip_header(pem):
|
||||
|
||||
return pem
|
||||
|
||||
|
||||
def load_certificate(data, type=PEM):
|
||||
def load_certificate(data, datatype=PEM, dbdir=None):
|
||||
"""
|
||||
Given a base64-encoded certificate, with or without the
|
||||
header/footer, return a request object.
|
||||
|
||||
Returns a nss.Certificate type
|
||||
"""
|
||||
if (type == PEM):
|
||||
if type(data) in (tuple, list):
|
||||
data = data[0]
|
||||
|
||||
if (datatype == PEM):
|
||||
data = strip_header(data)
|
||||
data = base64.b64decode(data)
|
||||
|
||||
return decoder.decode(data, asn1Spec=Certificate())[0]
|
||||
if dbdir is None:
|
||||
if api.env.in_tree:
|
||||
dbdir = api.env.dot_ipa + os.sep + 'alias'
|
||||
else:
|
||||
dbdir = "/etc/httpd/alias"
|
||||
|
||||
def get_subject_components(certificate, type=PEM):
|
||||
nss.nss_init(dbdir)
|
||||
return nss.Certificate(buffer(data))
|
||||
|
||||
def get_subject(certificate, datatype=PEM):
|
||||
"""
|
||||
Load an X509.3 certificate and get the subject.
|
||||
|
||||
Return a tuple of a certificate subject.
|
||||
(('CN', u'www.example.com'), ('O', u'IPA'))
|
||||
"""
|
||||
|
||||
x509cert = load_certificate(certificate, type)
|
||||
return x509cert.get_subject().get_components()
|
||||
cert = load_certificate(certificate, datatype)
|
||||
return cert.subject
|
||||
|
||||
def get_serial_number(certificate, type=PEM):
|
||||
def get_serial_number(certificate, datatype=PEM):
|
||||
"""
|
||||
Return the serial number of a certificate as a Python long object.
|
||||
Return the decimal value of the serial number.
|
||||
"""
|
||||
x509cert = load_certificate(certificate, type)
|
||||
return x509cert.get_serial_number()
|
||||
cert = load_certificate(certificate, datatype)
|
||||
return cert.serial_number
|
||||
|
||||
if __name__ == '__main__':
|
||||
certType = Certificate()
|
||||
|
||||
# Read PEM certs from stdin and print them out in plain text
|
||||
nss.nss_init_nodb()
|
||||
|
||||
stSpam, stHam, stDump = 0, 1, 2
|
||||
state = stSpam
|
||||
certCnt = 0
|
||||
# Read PEM certs from stdin and print out its components
|
||||
|
||||
for certLine in sys.stdin.readlines():
|
||||
certLine = string.strip(certLine)
|
||||
if state == stSpam:
|
||||
if state == stSpam:
|
||||
if certLine == '-----BEGIN CERTIFICATE-----':
|
||||
certLines = []
|
||||
state = stHam
|
||||
continue
|
||||
if state == stHam:
|
||||
if certLine == '-----END CERTIFICATE-----':
|
||||
state = stDump
|
||||
else:
|
||||
certLines.append(certLine)
|
||||
if state == stDump:
|
||||
substrate = ''
|
||||
for certLine in certLines:
|
||||
substrate = substrate + base64.b64decode(certLine)
|
||||
certlines = sys.stdin.readlines()
|
||||
cert = ''.join(certlines)
|
||||
|
||||
cert = decoder.decode(substrate, asn1Spec=certType)[0]
|
||||
print cert.prettyPrint()
|
||||
cert = load_certificate(cert)
|
||||
|
||||
assert encoder.encode(cert) == substrate, 'cert recode fails'
|
||||
|
||||
certCnt = certCnt + 1
|
||||
state = stSpam
|
||||
|
||||
print '*** %s PEM cert(s) de/serialized' % certCnt
|
||||
print cert
|
||||
|
||||
Reference in New Issue
Block a user