1
# -*- test-case-name: twisted.test.test_sslverify -*-
2
# Copyright (c) 2005 Divmod, Inc.
3
# Copyright (c) 2008 Twisted Matrix Laboratories.
4
# See LICENSE for details.
5
# Copyright (c) 2005-2008 Twisted Matrix Laboratories.
8
from OpenSSL import SSL, crypto
10
from twisted.python import reflect, util
11
from twisted.python.hashlib import md5
12
from twisted.internet.defer import Deferred
13
from twisted.internet.error import VerifyError, CertificateError
15
# Private - shared between all OpenSSLCertificateOptions, counts up to provide
16
# a unique session id for each context
17
_sessionCounter = itertools.count().next
21
'commonName': 'commonName',
23
'O': 'organizationName',
24
'organizationName': 'organizationName',
26
'OU': 'organizationalUnitName',
27
'organizationalUnitName': 'organizationalUnitName',
30
'localityName': 'localityName',
32
'ST': 'stateOrProvinceName',
33
'stateOrProvinceName': 'stateOrProvinceName',
36
'countryName': 'countryName',
38
'emailAddress': 'emailAddress'}
41
class DistinguishedName(dict):
43
Identify and describe an entity.
45
Distinguished names are used to provide a minimal amount of identifying
46
information about a certificate issuer or subject. They are commonly
47
created with one or more of the following fields::
51
organizationalUnitName (OU)
53
stateOrProvinceName (ST)
59
def __init__(self, **kw):
60
for k, v in kw.iteritems():
64
def _copyFrom(self, x509name):
66
for name in _x509names:
67
value = getattr(x509name, name, None)
69
setattr(self, name, value)
72
def _copyInto(self, x509name):
73
for k, v in self.iteritems():
74
setattr(x509name, k, v)
78
return '<DN %s>' % (dict.__repr__(self)[1:-1])
81
def __getattr__(self, attr):
83
return self[_x509names[attr]]
85
raise AttributeError(attr)
88
def __setattr__(self, attr, value):
89
assert type(attr) is str
90
if not attr in _x509names:
91
raise AttributeError("%s is not a valid OpenSSL X509 name field" % (attr,))
92
realAttr = _x509names[attr]
93
value = value.encode('ascii')
94
assert type(value) is str
95
self[realAttr] = value
100
Return a multi-line, human-readable representation of this DN.
104
def uniqueValues(mapping):
105
return dict.fromkeys(mapping.itervalues()).keys()
106
for k in uniqueValues(_x509names):
107
label = util.nameToLabel(k)
108
lablen = max(len(label), lablen)
109
v = getattr(self, k, None)
113
for n, (label, attr) in enumerate(l):
114
l[n] = (label.rjust(lablen)+': '+ attr)
117
DN = DistinguishedName
121
def __init__(self, original):
122
self.original = original
124
def _copyName(self, suffix):
125
dn = DistinguishedName()
126
dn._copyFrom(getattr(self.original, 'get_'+suffix)())
129
def getSubject(self):
131
Retrieve the subject of this certificate.
133
@rtype: L{DistinguishedName}
134
@return: A copy of the subject of this certificate.
136
return self._copyName('subject')
140
def _handleattrhelper(Class, transport, methodName):
142
(private) Helper for L{Certificate.peerFromTransport} and
143
L{Certificate.hostFromTransport} which checks for incompatible handle types
144
and null certificates and raises the appropriate exception or returns the
145
appropriate certificate object.
147
method = getattr(transport.getHandle(),
148
"get_%s_certificate" % (methodName,), None)
150
raise CertificateError(
151
"non-TLS transport %r did not have %s certificate" % (transport, methodName))
154
raise CertificateError(
155
"TLS transport %r did not have %s certificate" % (transport, methodName))
159
class Certificate(CertBase):
164
return '<%s Subject=%s Issuer=%s>' % (self.__class__.__name__,
165
self.getSubject().commonName,
166
self.getIssuer().commonName)
168
def __eq__(self, other):
169
if isinstance(other, Certificate):
170
return self.dump() == other.dump()
174
def __ne__(self, other):
175
return not self.__eq__(other)
178
def load(Class, requestData, format=crypto.FILETYPE_ASN1, args=()):
180
Load a certificate from an ASN.1- or PEM-format string.
184
return Class(crypto.load_certificate(format, requestData), *args)
185
load = classmethod(load)
191
Dump this certificate to a PEM-format data string.
195
return self.dump(crypto.FILETYPE_PEM)
198
def loadPEM(Class, data):
200
Load a certificate from a PEM-format data string.
204
return Class.load(data, crypto.FILETYPE_PEM)
205
loadPEM = classmethod(loadPEM)
208
def peerFromTransport(Class, transport):
210
Get the certificate for the remote end of the given transport.
212
@type: L{ISystemHandle}
215
@raise: L{CertificateError}, if the given transport does not have a peer
218
return _handleattrhelper(Class, transport, 'peer')
219
peerFromTransport = classmethod(peerFromTransport)
222
def hostFromTransport(Class, transport):
224
Get the certificate for the local end of the given transport.
226
@param transport: an L{ISystemHandle} provider; the transport we will
230
@raise: L{CertificateError}, if the given transport does not have a host
233
return _handleattrhelper(Class, transport, 'host')
234
hostFromTransport = classmethod(hostFromTransport)
237
def getPublicKey(self):
239
Get the public key for this certificate.
243
return PublicKey(self.original.get_pubkey())
246
def dump(self, format=crypto.FILETYPE_ASN1):
247
return crypto.dump_certificate(format, self.original)
250
def serialNumber(self):
252
Retrieve the serial number of this certificate.
256
return self.original.get_serial_number()
259
def digest(self, method='md5'):
261
Return a digest hash of this certificate using the specified hash
264
@param method: One of C{'md5'} or C{'sha'}.
267
return self.original.digest(method)
271
return '\n'.join(['Certificate For Subject:',
272
self.getSubject().inspect(),
274
self.getIssuer().inspect(),
275
'\nSerial Number: %d' % self.serialNumber(),
276
'Digest: %s' % self.digest()])
281
Return a multi-line, human-readable representation of this
282
Certificate, including information about the subject, issuer, and
285
return '\n'.join((self._inspect(), self.getPublicKey().inspect()))
290
Retrieve the issuer of this certificate.
292
@rtype: L{DistinguishedName}
293
@return: A copy of the issuer of this certificate.
295
return self._copyName('issuer')
298
def options(self, *authorities):
299
raise NotImplementedError('Possible, but doubtful we need this yet')
303
class CertificateRequest(CertBase):
305
An x509 certificate request.
307
Certificate requests are given to certificate authorities to be signed and
308
returned resulting in an actual certificate.
310
def load(Class, requestData, requestFormat=crypto.FILETYPE_ASN1):
311
req = crypto.load_certificate_request(requestFormat, requestData)
312
dn = DistinguishedName()
313
dn._copyFrom(req.get_subject())
314
if not req.verify(req.get_pubkey()):
315
raise VerifyError("Can't verify that request for %r is self-signed." % (dn,))
317
load = classmethod(load)
320
def dump(self, format=crypto.FILETYPE_ASN1):
321
return crypto.dump_certificate_request(format, self.original)
325
class PrivateCertificate(Certificate):
327
An x509 certificate and private key.
330
return Certificate.__repr__(self) + ' with ' + repr(self.privateKey)
333
def _setPrivateKey(self, privateKey):
334
if not privateKey.matches(self.getPublicKey()):
336
"Certificate public and private keys do not match.")
337
self.privateKey = privateKey
341
def newCertificate(self, newCertData, format=crypto.FILETYPE_ASN1):
343
Create a new L{PrivateCertificate} from the given certificate data and
344
this instance's private key.
346
return self.load(newCertData, self.privateKey, format)
349
def load(Class, data, privateKey, format=crypto.FILETYPE_ASN1):
350
return Class._load(data, format)._setPrivateKey(privateKey)
351
load = classmethod(load)
355
return '\n'.join([Certificate._inspect(self),
356
self.privateKey.inspect()])
361
Dump both public and private parts of a private certificate to
364
return self.dump(crypto.FILETYPE_PEM) + self.privateKey.dump(crypto.FILETYPE_PEM)
367
def loadPEM(Class, data):
369
Load both private and public parts of a private certificate from a
370
chunk of PEM-format data.
372
return Class.load(data, KeyPair.load(data, crypto.FILETYPE_PEM),
374
loadPEM = classmethod(loadPEM)
377
def fromCertificateAndKeyPair(Class, certificateInstance, privateKey):
378
privcert = Class(certificateInstance.original)
379
return privcert._setPrivateKey(privateKey)
380
fromCertificateAndKeyPair = classmethod(fromCertificateAndKeyPair)
383
def options(self, *authorities):
384
options = dict(privateKey=self.privateKey.original,
385
certificate=self.original)
387
options.update(dict(verify=True,
388
requireCertificate=True,
389
caCerts=[auth.original for auth in authorities]))
390
return OpenSSLCertificateOptions(**options)
393
def certificateRequest(self, format=crypto.FILETYPE_ASN1,
394
digestAlgorithm='md5'):
395
return self.privateKey.certificateRequest(
401
def signCertificateRequest(self,
405
requestFormat=crypto.FILETYPE_ASN1,
406
certificateFormat=crypto.FILETYPE_ASN1):
407
issuer = self.getSubject()
408
return self.privateKey.signCertificateRequest(
417
def signRequestObject(self, certificateRequest, serialNumber,
418
secondsToExpiry=60 * 60 * 24 * 365, # One year
419
digestAlgorithm='md5'):
420
return self.privateKey.signRequestObject(self.getSubject(),
428
def __init__(self, osslpkey):
429
self.original = osslpkey
430
req1 = crypto.X509Req()
431
req1.set_pubkey(osslpkey)
432
self._emptyReq = crypto.dump_certificate_request(crypto.FILETYPE_ASN1, req1)
435
def matches(self, otherKey):
436
return self._emptyReq == otherKey._emptyReq
439
# XXX This could be a useful method, but sometimes it triggers a segfault,
440
# so we'll steer clear for now.
441
# def verifyCertificate(self, certificate):
443
# returns None, or raises a VerifyError exception if the certificate
444
# could not be verified.
446
# if not certificate.original.verify(self.original):
447
# raise VerifyError("We didn't sign that certificate.")
450
return '<%s %s>' % (self.__class__.__name__, self.keyHash())
455
MD5 hex digest of signature on an empty certificate request with this
458
return md5(self._emptyReq).hexdigest()
462
return 'Public Key with Hash: %s' % (self.keyHash(),)
466
class KeyPair(PublicKey):
468
def load(Class, data, format=crypto.FILETYPE_ASN1):
469
return Class(crypto.load_privatekey(format, data))
470
load = classmethod(load)
473
def dump(self, format=crypto.FILETYPE_ASN1):
474
return crypto.dump_privatekey(format, self.original)
477
def __getstate__(self):
481
def __setstate__(self, state):
482
self.__init__(crypto.load_privatekey(crypto.FILETYPE_ASN1, state))
486
t = self.original.type()
487
if t == crypto.TYPE_RSA:
489
elif t == crypto.TYPE_DSA:
492
ts = '(Unknown Type!)'
493
L = (self.original.bits(), ts, self.keyHash())
494
return '%s-bit %s Key Pair with Hash: %s' % L
497
def generate(Class, kind=crypto.TYPE_RSA, size=1024):
499
pkey.generate_key(kind, size)
503
def newCertificate(self, newCertData, format=crypto.FILETYPE_ASN1):
504
return PrivateCertificate.load(newCertData, self, format)
505
generate = classmethod(generate)
508
def requestObject(self, distinguishedName, digestAlgorithm='md5'):
509
req = crypto.X509Req()
510
req.set_pubkey(self.original)
511
distinguishedName._copyInto(req.get_subject())
512
req.sign(self.original, digestAlgorithm)
513
return CertificateRequest(req)
516
def certificateRequest(self, distinguishedName,
517
format=crypto.FILETYPE_ASN1,
518
digestAlgorithm='md5'):
519
"""Create a certificate request signed with this key.
521
@return: a string, formatted according to the 'format' argument.
523
return self.requestObject(distinguishedName, digestAlgorithm).dump(format)
526
def signCertificateRequest(self,
527
issuerDistinguishedName,
531
requestFormat=crypto.FILETYPE_ASN1,
532
certificateFormat=crypto.FILETYPE_ASN1,
533
secondsToExpiry=60 * 60 * 24 * 365, # One year
534
digestAlgorithm='md5'):
536
Given a blob of certificate request data and a certificate authority's
537
DistinguishedName, return a blob of signed certificate data.
539
If verifyDNCallback returns a Deferred, I will return a Deferred which
540
fires the data when that Deferred has completed.
542
hlreq = CertificateRequest.load(requestData, requestFormat)
544
dn = hlreq.getSubject()
545
vval = verifyDNCallback(dn)
549
raise VerifyError("DN callback %r rejected request DN %r" % (verifyDNCallback, dn))
550
return self.signRequestObject(issuerDistinguishedName, hlreq,
551
serialNumber, secondsToExpiry, digestAlgorithm).dump(certificateFormat)
553
if isinstance(vval, Deferred):
554
return vval.addCallback(verified)
556
return verified(vval)
559
def signRequestObject(self,
560
issuerDistinguishedName,
563
secondsToExpiry=60 * 60 * 24 * 365, # One year
564
digestAlgorithm='md5'):
566
Sign a CertificateRequest instance, returning a Certificate instance.
568
req = requestObject.original
569
dn = requestObject.getSubject()
571
issuerDistinguishedName._copyInto(cert.get_issuer())
572
cert.set_subject(req.get_subject())
573
cert.set_pubkey(req.get_pubkey())
574
cert.gmtime_adj_notBefore(0)
575
cert.gmtime_adj_notAfter(secondsToExpiry)
576
cert.set_serial_number(serialNumber)
577
cert.sign(self.original, digestAlgorithm)
578
return Certificate(cert)
581
def selfSignedCert(self, serialNumber, **kw):
583
return PrivateCertificate.fromCertificateAndKeyPair(
584
self.signRequestObject(dn, self.requestObject(dn), serialNumber),
589
class OpenSSLCertificateOptions(object):
591
A factory for SSL context objects for both SSL servers and clients.
595
# Older versions of PyOpenSSL didn't provide OP_ALL. Fudge it here, just in case.
596
_OP_ALL = getattr(SSL, 'OP_ALL', 0x0000FFFF)
597
# OP_NO_TICKET is not (yet) exposed by PyOpenSSL
598
_OP_NO_TICKET = 0x00004000
600
method = SSL.TLSv1_METHOD
609
requireCertificate=True,
611
enableSingleUseKeys=True,
613
fixBrokenPeers=False,
614
enableSessionTickets=False):
616
Create an OpenSSL context SSL connection context factory.
618
@param privateKey: A PKey object holding the private key.
620
@param certificate: An X509 object holding the certificate.
622
@param method: The SSL protocol to use, one of SSLv23_METHOD,
623
SSLv2_METHOD, SSLv3_METHOD, TLSv1_METHOD. Defaults to TLSv1_METHOD.
625
@param verify: If True, verify certificates received from the peer and
626
fail the handshake if verification fails. Otherwise, allow anonymous
627
sessions and sessions with certificates which fail validation. By
628
default this is False.
630
@param caCerts: List of certificate authority certificates to
631
send to the client when requesting a certificate. Only used if verify
632
is True, and if verify is True, either this must be specified or
633
caCertsFile must be given. Since verify is False by default,
634
this is None by default.
636
@param verifyDepth: Depth in certificate chain down to which to verify.
637
If unspecified, use the underlying default (9).
639
@param requireCertificate: If True, do not allow anonymous sessions.
641
@param verifyOnce: If True, do not re-verify the certificate
642
on session resumption.
644
@param enableSingleUseKeys: If True, generate a new key whenever
645
ephemeral DH parameters are used to prevent small subgroup attacks.
647
@param enableSessions: If True, set a session ID on each context. This
648
allows a shortened handshake to be used when a known client reconnects.
650
@param fixBrokenPeers: If True, enable various non-spec protocol fixes
651
for broken SSL implementations. This should be entirely safe,
652
according to the OpenSSL documentation, but YMMV. This option is now
653
off by default, because it causes problems with connections between
654
peers using OpenSSL 0.9.8a.
656
@param enableSessionTickets: If True, enable session ticket extension
657
for session resumption per RFC 5077. Note there is no support for
658
controlling session tickets. This option is off by default, as some
659
server implementations don't correctly process incoming empty session
660
ticket extensions in the hello.
663
assert (privateKey is None) == (certificate is None), "Specify neither or both of privateKey and certificate"
664
self.privateKey = privateKey
665
self.certificate = certificate
666
if method is not None:
670
assert ((verify and caCerts) or
671
(not verify)), "Specify client CA certificate information if and only if enabling certificate verification"
673
self.caCerts = caCerts
674
self.verifyDepth = verifyDepth
675
self.requireCertificate = requireCertificate
676
self.verifyOnce = verifyOnce
677
self.enableSingleUseKeys = enableSingleUseKeys
678
self.enableSessions = enableSessions
679
self.fixBrokenPeers = fixBrokenPeers
680
self.enableSessionTickets = enableSessionTickets
683
def __getstate__(self):
684
d = self.__dict__.copy()
692
def __setstate__(self, state):
693
self.__dict__ = state
696
def getContext(self):
697
"""Return a SSL.Context object.
699
if self._context is None:
700
self._context = self._makeContext()
704
def _makeContext(self):
705
ctx = SSL.Context(self.method)
707
if self.certificate is not None and self.privateKey is not None:
708
ctx.use_certificate(self.certificate)
709
ctx.use_privatekey(self.privateKey)
711
ctx.check_privatekey()
713
verifyFlags = SSL.VERIFY_NONE
715
verifyFlags = SSL.VERIFY_PEER
716
if self.requireCertificate:
717
verifyFlags |= SSL.VERIFY_FAIL_IF_NO_PEER_CERT
719
verifyFlags |= SSL.VERIFY_CLIENT_ONCE
721
store = ctx.get_cert_store()
722
for cert in self.caCerts:
725
# It'd be nice if pyOpenSSL let us pass None here for this behavior (as
726
# the underlying OpenSSL API call allows NULL to be passed). It
727
# doesn't, so we'll supply a function which does the same thing.
728
def _verifyCallback(conn, cert, errno, depth, preverify_ok):
730
ctx.set_verify(verifyFlags, _verifyCallback)
732
if self.verifyDepth is not None:
733
ctx.set_verify_depth(self.verifyDepth)
735
if self.enableSingleUseKeys:
736
ctx.set_options(SSL.OP_SINGLE_DH_USE)
738
if self.fixBrokenPeers:
739
ctx.set_options(self._OP_ALL)
741
if self.enableSessions:
742
sessionName = md5("%s-%d" % (reflect.qual(self.__class__), _sessionCounter())).hexdigest()
743
ctx.set_session_id(sessionName)
745
if not self.enableSessionTickets:
746
ctx.set_options(self._OP_NO_TICKET)