1 # -*- test-case-name: twisted.test.test_sslverify -*-
2 # Copyright (c) 2005 Divmod, Inc.
3 # Copyright (c) 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 certificate objects to
631 use to verify the peer's certificate. Only used if verify is
632 C{True}, and if verify is C{True}, this must be specified. Since
633 verify is C{False} by default, this is C{None} by default.
635 @type caCerts: C{list} of L{OpenSSL.crypto.X509}
637 @param verifyDepth: Depth in certificate chain down to which to verify.
638 If unspecified, use the underlying default (9).
640 @param requireCertificate: If True, do not allow anonymous sessions.
642 @param verifyOnce: If True, do not re-verify the certificate
643 on session resumption.
645 @param enableSingleUseKeys: If True, generate a new key whenever
646 ephemeral DH parameters are used to prevent small subgroup attacks.
648 @param enableSessions: If True, set a session ID on each context. This
649 allows a shortened handshake to be used when a known client reconnects.
651 @param fixBrokenPeers: If True, enable various non-spec protocol fixes
652 for broken SSL implementations. This should be entirely safe,
653 according to the OpenSSL documentation, but YMMV. This option is now
654 off by default, because it causes problems with connections between
655 peers using OpenSSL 0.9.8a.
657 @param enableSessionTickets: If True, enable session ticket extension
658 for session resumption per RFC 5077. Note there is no support for
659 controlling session tickets. This option is off by default, as some
660 server implementations don't correctly process incoming empty session
661 ticket extensions in the hello.
664 assert (privateKey is None) == (certificate is None), "Specify neither or both of privateKey and certificate"
665 self.privateKey = privateKey
666 self.certificate = certificate
667 if method is not None:
671 assert ((verify and caCerts) or
672 (not verify)), "Specify client CA certificate information if and only if enabling certificate verification"
674 self.caCerts = caCerts
675 self.verifyDepth = verifyDepth
676 self.requireCertificate = requireCertificate
677 self.verifyOnce = verifyOnce
678 self.enableSingleUseKeys = enableSingleUseKeys
679 self.enableSessions = enableSessions
680 self.fixBrokenPeers = fixBrokenPeers
681 self.enableSessionTickets = enableSessionTickets
684 def __getstate__(self):
685 d = self.__dict__.copy()
693 def __setstate__(self, state):
694 self.__dict__ = state
697 def getContext(self):
698 """Return a SSL.Context object.
700 if self._context is None:
701 self._context = self._makeContext()
705 def _makeContext(self):
706 ctx = SSL.Context(self.method)
708 if self.certificate is not None and self.privateKey is not None:
709 ctx.use_certificate(self.certificate)
710 ctx.use_privatekey(self.privateKey)
712 ctx.check_privatekey()
714 verifyFlags = SSL.VERIFY_NONE
716 verifyFlags = SSL.VERIFY_PEER
717 if self.requireCertificate:
718 verifyFlags |= SSL.VERIFY_FAIL_IF_NO_PEER_CERT
720 verifyFlags |= SSL.VERIFY_CLIENT_ONCE
722 store = ctx.get_cert_store()
723 for cert in self.caCerts:
726 # It'd be nice if pyOpenSSL let us pass None here for this behavior (as
727 # the underlying OpenSSL API call allows NULL to be passed). It
728 # doesn't, so we'll supply a function which does the same thing.
729 def _verifyCallback(conn, cert, errno, depth, preverify_ok):
731 ctx.set_verify(verifyFlags, _verifyCallback)
733 if self.verifyDepth is not None:
734 ctx.set_verify_depth(self.verifyDepth)
736 if self.enableSingleUseKeys:
737 ctx.set_options(SSL.OP_SINGLE_DH_USE)
739 if self.fixBrokenPeers:
740 ctx.set_options(self._OP_ALL)
742 if self.enableSessions:
743 sessionName = md5("%s-%d" % (reflect.qual(self.__class__), _sessionCounter())).hexdigest()
744 ctx.set_session_id(sessionName)
746 if not self.enableSessionTickets:
747 ctx.set_options(self._OP_NO_TICKET)