Master master into letsencrypt-auto-release so Travis will build it.

This commit is contained in:
Erik Rose
2016-01-11 21:45:16 -05:00
46 changed files with 899 additions and 151 deletions
+8
View File
@@ -22,11 +22,19 @@ env:
matrix: matrix:
- TOXENV=py26 BOULDER_INTEGRATION=1 - TOXENV=py26 BOULDER_INTEGRATION=1
- TOXENV=py27 BOULDER_INTEGRATION=1 - TOXENV=py27 BOULDER_INTEGRATION=1
- TOXENV=py26-oldest BOULDER_INTEGRATION=1
- TOXENV=py27-oldest BOULDER_INTEGRATION=1
- TOXENV=py33
- TOXENV=py34
- TOXENV=lint - TOXENV=lint
- TOXENV=cover - TOXENV=cover
# Disabled for now due to requiring sudo -> causing more boulder integration # Disabled for now due to requiring sudo -> causing more boulder integration
# DNS timeouts :( # DNS timeouts :(
# - TOXENV=apacheconftest # - TOXENV=apacheconftest
matrix:
include:
- env: TOXENV=py35
python: 3.5
# Only build pushes to the master branch, PRs, and branches beginning with # Only build pushes to the master branch, PRs, and branches beginning with
+383
View File
@@ -0,0 +1,383 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Profiled execution.
profile=no
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=linter_plugin
# DEPRECATED
include-ids=no
# DEPRECATED
symbols=no
# Use multiple processes to speed up Pylint.
jobs=1
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code
extension-pkg-whitelist=
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
confidence=
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=fixme,locally-disabled,abstract-class-not-used
# bstract-class-not-used cannot be disabled locally (at least in
# pylint 1.4.1/2)
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=no
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=100
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# List of optional constructs for which whitespace checking is disabled
no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging,logger
[SPELLING]
# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis
ignored-modules=
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=SQLObject
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=_$|dummy|unused
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,_cb
[BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,input
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_,logger
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Include a hint for the correct naming format with invalid-name
include-naming-hint=no
# Regular expression matching correct function names
function-rgx=[a-z_][a-z0-9_]{2,40}$
# Naming hint for function names
function-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for variable names
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct constant names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Naming hint for constant names
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression matching correct attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for attribute names
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for argument names
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct class attribute names
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Naming hint for class attribute names
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression matching correct inline iteration names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Naming hint for inline iteration names
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
# Regular expression matching correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Naming hint for class names
class-name-hint=[A-Z_][a-zA-Z0-9]+$
# Regular expression matching correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Naming hint for module names
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression matching correct method names
method-rgx=[a-z_][a-z0-9_]{2,49}$
# Naming hint for method names
method-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=__.*__|test_[A-Za-z0-9_]*|_.*|.*Test
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,_fields,_replace,_source,_make
[DESIGN]
# Maximum number of arguments for function / method
max-args=6
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=12
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception
+9 -2
View File
@@ -336,7 +336,7 @@ class TLSSNI01Response(KeyAuthorizationChallengeResponse):
""" """
@property @property
def z(self): def z(self): # pylint: disable=invalid-name
"""``z`` value used for verification. """``z`` value used for verification.
:rtype bytes: :rtype bytes:
@@ -391,7 +391,14 @@ class TLSSNI01Response(KeyAuthorizationChallengeResponse):
return crypto_util.probe_sni(**kwargs) return crypto_util.probe_sni(**kwargs)
def verify_cert(self, cert): def verify_cert(self, cert):
"""Verify tls-sni-01 challenge certificate.""" """Verify tls-sni-01 challenge certificate.
:param OpensSSL.crypto.X509 cert: Challenge certificate.
:returns: Whether the certificate was successfully verified.
:rtype: bool
"""
# pylint: disable=protected-access # pylint: disable=protected-access
sans = crypto_util._pyopenssl_cert_or_req_san(cert) sans = crypto_util._pyopenssl_cert_or_req_san(cert)
logging.debug('Certificate %s. SANs: %s', cert.digest('sha1'), sans) logging.debug('Certificate %s. SANs: %s', cert.digest('sha1'), sans)
+2 -2
View File
@@ -13,7 +13,7 @@ from acme import other
from acme import test_util from acme import test_util
CERT = test_util.load_cert('cert.pem') CERT = test_util.load_comparable_cert('cert.pem')
KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem')) KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem'))
@@ -421,7 +421,7 @@ class ProofOfPossessionHintsTest(unittest.TestCase):
'jwk': jwk, 'jwk': jwk,
'certFingerprints': cert_fingerprints, 'certFingerprints': cert_fingerprints,
'certs': (jose.encode_b64jose(OpenSSL.crypto.dump_certificate( 'certs': (jose.encode_b64jose(OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, CERT)),), OpenSSL.crypto.FILETYPE_ASN1, CERT.wrapped)),),
'subjectKeyIdentifiers': subject_key_identifiers, 'subjectKeyIdentifiers': subject_key_identifiers,
'serialNumbers': serial_numbers, 'serialNumbers': serial_numbers,
'issuers': issuers, 'issuers': issuers,
+22 -23
View File
@@ -1,11 +1,10 @@
"""Crypto utilities.""" """Crypto utilities."""
import contextlib import contextlib
import logging import logging
import re
import socket import socket
import sys import sys
from six.moves import range # pylint: disable=import-error,redefined-builtin
import OpenSSL import OpenSSL
from acme import errors from acme import errors
@@ -70,7 +69,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
class FakeConnection(object): class FakeConnection(object):
"""Fake OpenSSL.SSL.Connection.""" """Fake OpenSSL.SSL.Connection."""
# pylint: disable=missing-docstring # pylint: disable=too-few-public-methods,missing-docstring
def __init__(self, connection): def __init__(self, connection):
self._wrapped = connection self._wrapped = connection
@@ -161,31 +160,31 @@ def _pyopenssl_cert_or_req_san(cert_or_req):
:rtype: `list` of `unicode` :rtype: `list` of `unicode`
""" """
# constants based on implementation of # This function finds SANs by dumping the certificate/CSR to text and
# OpenSSL.crypto.X509Error._subjectAltNameString # searching for "X509v3 Subject Alternative Name" in the text. This method
parts_separator = ", " # is used to support PyOpenSSL version 0.13 where the
# `_subjectAltNameString` and `get_extensions` methods are not available
# for CSRs.
# constants based on PyOpenSSL certificate/CSR text dump
part_separator = ":" part_separator = ":"
extension_short_name = b"subjectAltName" parts_separator = ", "
prefix = "DNS" + part_separator
if hasattr(cert_or_req, 'get_extensions'): # X509Req if isinstance(cert_or_req, OpenSSL.crypto.X509):
extensions = cert_or_req.get_extensions() func = OpenSSL.crypto.dump_certificate
else: # X509 else:
extensions = [cert_or_req.get_extension(i) func = OpenSSL.crypto.dump_certificate_request
for i in range(cert_or_req.get_extension_count())] text = func(OpenSSL.crypto.FILETYPE_TEXT, cert_or_req).decode("utf-8")
# WARNING: this function does not support multiple SANs extensions.
# pylint: disable=protected-access,no-member # Multiple X509v3 extensions of the same type is disallowed by RFC 5280.
label = OpenSSL.crypto.X509Extension._prefixes[OpenSSL.crypto._lib.GEN_DNS] match = re.search(r"X509v3 Subject Alternative Name:\s*(.*)", text)
assert parts_separator not in label
prefix = label + part_separator
san_extensions = [
ext._subjectAltNameString().split(parts_separator)
for ext in extensions if ext.get_short_name() == extension_short_name]
# WARNING: this function assumes that no SAN can include # WARNING: this function assumes that no SAN can include
# parts_separator, hence the split! # parts_separator, hence the split!
sans_parts = [] if match is None else match.group(1).split(parts_separator)
return [part.split(part_separator)[1] for parts in san_extensions return [part.split(part_separator)[1]
for part in parts if part.startswith(prefix)] for part in sans_parts if part.startswith(prefix)]
def gen_ss_cert(key, domains, not_before=None, def gen_ss_cert(key, domains, not_before=None,
+33 -6
View File
@@ -1,9 +1,11 @@
"""Tests for acme.crypto_util.""" """Tests for acme.crypto_util."""
import itertools
import socket import socket
import threading import threading
import time import time
import unittest import unittest
import six
from six.moves import socketserver # pylint: disable=import-error from six.moves import socketserver # pylint: disable=import-error
from acme import errors from acme import errors
@@ -15,10 +17,10 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
"""Tests for acme.crypto_util.SSLSocket/probe_sni.""" """Tests for acme.crypto_util.SSLSocket/probe_sni."""
def setUp(self): def setUp(self):
self.cert = test_util.load_cert('cert.pem') self.cert = test_util.load_comparable_cert('cert.pem')
key = test_util.load_pyopenssl_private_key('rsa512_key.pem') key = test_util.load_pyopenssl_private_key('rsa512_key.pem')
# pylint: disable=protected-access # pylint: disable=protected-access
certs = {b'foo': (key, self.cert._wrapped)} certs = {b'foo': (key, self.cert.wrapped)}
from acme.crypto_util import SSLSocket from acme.crypto_util import SSLSocket
@@ -69,6 +71,15 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
from acme.crypto_util import _pyopenssl_cert_or_req_san from acme.crypto_util import _pyopenssl_cert_or_req_san
return _pyopenssl_cert_or_req_san(loader(name)) return _pyopenssl_cert_or_req_san(loader(name))
@classmethod
def _get_idn_names(cls):
"""Returns expected names from '{cert,csr}-idnsans.pem'."""
chars = [six.unichr(i) for i in itertools.chain(range(0x3c3, 0x400),
range(0x641, 0x6fc),
range(0x1820, 0x1877))]
return [''.join(chars[i: i + 45]) + '.invalid'
for i in range(0, len(chars), 45)]
def _call_cert(self, name): def _call_cert(self, name):
return self._call(test_util.load_cert, name) return self._call(test_util.load_cert, name)
@@ -82,6 +93,14 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
self.assertEqual(self._call_cert('cert-san.pem'), self.assertEqual(self._call_cert('cert-san.pem'),
['example.com', 'www.example.com']) ['example.com', 'www.example.com'])
def test_cert_hundred_sans(self):
self.assertEqual(self._call_cert('cert-100sans.pem'),
['example{0}.com'.format(i) for i in range(1, 101)])
def test_cert_idn_sans(self):
self.assertEqual(self._call_cert('cert-idnsans.pem'),
self._get_idn_names())
def test_csr_no_sans(self): def test_csr_no_sans(self):
self.assertEqual(self._call_csr('csr-nosans.pem'), []) self.assertEqual(self._call_csr('csr-nosans.pem'), [])
@@ -94,10 +113,18 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
def test_csr_six_sans(self): def test_csr_six_sans(self):
self.assertEqual(self._call_csr('csr-6sans.pem'), self.assertEqual(self._call_csr('csr-6sans.pem'),
["example.com", "example.org", "example.net", ['example.com', 'example.org', 'example.net',
"example.info", "subdomain.example.com", 'example.info', 'subdomain.example.com',
"other.subdomain.example.com"]) 'other.subdomain.example.com'])
def test_csr_hundred_sans(self):
self.assertEqual(self._call_csr('csr-100sans.pem'),
['example{0}.com'.format(i) for i in range(1, 101)])
def test_csr_idn_sans(self):
self.assertEqual(self._call_csr('csr-idnsans.pem'),
self._get_idn_names())
if __name__ == "__main__": if __name__ == '__main__':
unittest.main() # pragma: no cover unittest.main() # pragma: no cover
+2 -2
View File
@@ -373,7 +373,7 @@ def encode_cert(cert):
""" """
return encode_b64jose(OpenSSL.crypto.dump_certificate( return encode_b64jose(OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, cert)) OpenSSL.crypto.FILETYPE_ASN1, cert.wrapped))
def decode_cert(b64der): def decode_cert(b64der):
@@ -398,7 +398,7 @@ def encode_csr(csr):
""" """
return encode_b64jose(OpenSSL.crypto.dump_certificate_request( return encode_b64jose(OpenSSL.crypto.dump_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, csr)) OpenSSL.crypto.FILETYPE_ASN1, csr.wrapped))
def decode_csr(b64der): def decode_csr(b64der):
+2 -2
View File
@@ -12,8 +12,8 @@ from acme.jose import interfaces
from acme.jose import util from acme.jose import util
CERT = test_util.load_cert('cert.pem') CERT = test_util.load_comparable_cert('cert.pem')
CSR = test_util.load_csr('csr.pem') CSR = test_util.load_comparable_csr('csr.pem')
class FieldTest(unittest.TestCase): class FieldTest(unittest.TestCase):
+1 -1
View File
@@ -124,7 +124,7 @@ class Header(json_util.JSONObjectWithFields):
@x5c.encoder @x5c.encoder
def x5c(value): # pylint: disable=missing-docstring,no-self-argument def x5c(value): # pylint: disable=missing-docstring,no-self-argument
return [base64.b64encode(OpenSSL.crypto.dump_certificate( return [base64.b64encode(OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, cert)) for cert in value] OpenSSL.crypto.FILETYPE_ASN1, cert.wrapped)) for cert in value]
@x5c.decoder @x5c.decoder
def x5c(value): # pylint: disable=missing-docstring,no-self-argument def x5c(value): # pylint: disable=missing-docstring,no-self-argument
+5 -6
View File
@@ -13,7 +13,7 @@ from acme.jose import jwa
from acme.jose import jwk from acme.jose import jwk
CERT = test_util.load_cert('cert.pem') CERT = test_util.load_comparable_cert('cert.pem')
KEY = jwk.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) KEY = jwk.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
@@ -68,13 +68,12 @@ class HeaderTest(unittest.TestCase):
from acme.jose.jws import Header from acme.jose.jws import Header
header = Header(x5c=(CERT, CERT)) header = Header(x5c=(CERT, CERT))
jobj = header.to_partial_json() jobj = header.to_partial_json()
cert_b64 = base64.b64encode(OpenSSL.crypto.dump_certificate( cert_asn1 = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, CERT)) OpenSSL.crypto.FILETYPE_ASN1, CERT.wrapped)
cert_b64 = base64.b64encode(cert_asn1)
self.assertEqual(jobj, {'x5c': [cert_b64, cert_b64]}) self.assertEqual(jobj, {'x5c': [cert_b64, cert_b64]})
self.assertEqual(header, Header.from_json(jobj)) self.assertEqual(header, Header.from_json(jobj))
jobj['x5c'][0] = base64.b64encode( jobj['x5c'][0] = base64.b64encode(b'xxx' + cert_asn1)
b'xxx' + OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, CERT))
self.assertRaises(errors.DeserializationError, Header.from_json, jobj) self.assertRaises(errors.DeserializationError, Header.from_json, jobj)
def test_find_key(self): def test_find_key(self):
+20 -11
View File
@@ -29,32 +29,41 @@ class abstractclassmethod(classmethod):
class ComparableX509(object): # pylint: disable=too-few-public-methods class ComparableX509(object): # pylint: disable=too-few-public-methods
"""Wrapper for OpenSSL.crypto.X509** objects that supports __eq__. """Wrapper for OpenSSL.crypto.X509** objects that supports __eq__.
Wraps around: :ivar wrapped: Wrapped certificate or certificate request.
:type wrapped: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
- :class:`OpenSSL.crypto.X509`
- :class:`OpenSSL.crypto.X509Req`
""" """
def __init__(self, wrapped): def __init__(self, wrapped):
assert isinstance(wrapped, OpenSSL.crypto.X509) or isinstance( assert isinstance(wrapped, OpenSSL.crypto.X509) or isinstance(
wrapped, OpenSSL.crypto.X509Req) wrapped, OpenSSL.crypto.X509Req)
self._wrapped = wrapped self.wrapped = wrapped
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self._wrapped, name) return getattr(self.wrapped, name)
def _dump(self, filetype=OpenSSL.crypto.FILETYPE_ASN1): def _dump(self, filetype=OpenSSL.crypto.FILETYPE_ASN1):
# pylint: disable=missing-docstring,protected-access """Dumps the object into a buffer with the specified encoding.
if isinstance(self._wrapped, OpenSSL.crypto.X509):
:param int filetype: The desired encoding. Should be one of
`OpenSSL.crypto.FILETYPE_ASN1`,
`OpenSSL.crypto.FILETYPE_PEM`, or
`OpenSSL.crypto.FILETYPE_TEXT`.
:returns: Encoded X509 object.
:rtype: str
"""
if isinstance(self.wrapped, OpenSSL.crypto.X509):
func = OpenSSL.crypto.dump_certificate func = OpenSSL.crypto.dump_certificate
else: # assert in __init__ makes sure this is X509Req else: # assert in __init__ makes sure this is X509Req
func = OpenSSL.crypto.dump_certificate_request func = OpenSSL.crypto.dump_certificate_request
return func(filetype, self._wrapped) return func(filetype, self.wrapped)
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, self.__class__): if not isinstance(other, self.__class__):
return NotImplemented return NotImplemented
return self._dump() == other._dump() # pylint: disable=protected-access # pylint: disable=protected-access
return self._dump() == other._dump()
def __hash__(self): def __hash__(self):
return hash((self.__class__, self._dump())) return hash((self.__class__, self._dump()))
@@ -63,7 +72,7 @@ class ComparableX509(object): # pylint: disable=too-few-public-methods
return not self == other return not self == other
def __repr__(self): def __repr__(self):
return '<{0}({1!r})>'.format(self.__class__.__name__, self._wrapped) return '<{0}({1!r})>'.format(self.__class__.__name__, self.wrapped)
class ComparableKey(object): # pylint: disable=too-few-public-methods class ComparableKey(object): # pylint: disable=too-few-public-methods
+12 -9
View File
@@ -11,14 +11,17 @@ class ComparableX509Test(unittest.TestCase):
"""Tests for acme.jose.util.ComparableX509.""" """Tests for acme.jose.util.ComparableX509."""
def setUp(self): def setUp(self):
# test_util.load_{csr,cert} return ComparableX509 # test_util.load_comparable_{csr,cert} return ComparableX509
self.req1 = test_util.load_csr('csr.pem') self.req1 = test_util.load_comparable_csr('csr.pem')
self.req2 = test_util.load_csr('csr.pem') self.req2 = test_util.load_comparable_csr('csr.pem')
self.req_other = test_util.load_csr('csr-san.pem') self.req_other = test_util.load_comparable_csr('csr-san.pem')
self.cert1 = test_util.load_cert('cert.pem') self.cert1 = test_util.load_comparable_cert('cert.pem')
self.cert2 = test_util.load_cert('cert.pem') self.cert2 = test_util.load_comparable_cert('cert.pem')
self.cert_other = test_util.load_cert('cert-san.pem') self.cert_other = test_util.load_comparable_cert('cert-san.pem')
def test_getattr_proxy(self):
self.assertTrue(self.cert1.has_expired())
def test_eq(self): def test_eq(self):
self.assertEqual(self.req1, self.req2) self.assertEqual(self.req1, self.req2)
@@ -41,8 +44,8 @@ class ComparableX509Test(unittest.TestCase):
def test_repr(self): def test_repr(self):
for x509 in self.req1, self.cert1: for x509 in self.req1, self.cert1:
self.assertTrue(repr(x509).startswith( self.assertEqual(repr(x509),
'<ComparableX509(<OpenSSL.crypto.X509')) '<ComparableX509({0!r})>'.format(x509.wrapped))
class ComparableRSAKeyTest(unittest.TestCase): class ComparableRSAKeyTest(unittest.TestCase):
+2 -2
View File
@@ -23,7 +23,7 @@ class Error(jose.JSONObjectWithFields, errors.Error):
('badCSR', 'The CSR is unacceptable (e.g., due to a short key)'), ('badCSR', 'The CSR is unacceptable (e.g., due to a short key)'),
('badNonce', 'The client sent an unacceptable anti-replay nonce'), ('badNonce', 'The client sent an unacceptable anti-replay nonce'),
('connection', 'The server could not connect to the client to ' ('connection', 'The server could not connect to the client to '
'verify the domain'), 'verify the domain'),
('dnssec', 'The server could not validate a DNSSEC signed domain'), ('dnssec', 'The server could not validate a DNSSEC signed domain'),
('invalidEmail', ('invalidEmail',
'The provided email for a registration was invalid'), 'The provided email for a registration was invalid'),
@@ -31,7 +31,7 @@ class Error(jose.JSONObjectWithFields, errors.Error):
('rateLimited', 'There were too many requests of a given type'), ('rateLimited', 'There were too many requests of a given type'),
('serverInternal', 'The server experienced an internal error'), ('serverInternal', 'The server experienced an internal error'),
('tls', 'The server experienced a TLS error during domain ' ('tls', 'The server experienced a TLS error during domain '
'verification'), 'verification'),
('unauthorized', 'The client lacks sufficient authorization'), ('unauthorized', 'The client lacks sufficient authorization'),
('unknownHost', 'The server could not resolve a domain name'), ('unknownHost', 'The server could not resolve a domain name'),
) )
+2 -2
View File
@@ -8,8 +8,8 @@ from acme import jose
from acme import test_util from acme import test_util
CERT = test_util.load_cert('cert.der') CERT = test_util.load_comparable_cert('cert.der')
CSR = test_util.load_csr('csr.der') CSR = test_util.load_comparable_csr('csr.der')
KEY = test_util.load_rsa_private_key('rsa512_key.pem') KEY = test_util.load_rsa_private_key('rsa512_key.pem')
+2 -2
View File
@@ -35,7 +35,7 @@ class TLSSNI01ServerTest(unittest.TestCase):
self.certs = { self.certs = {
b'localhost': (test_util.load_pyopenssl_private_key('rsa512_key.pem'), b'localhost': (test_util.load_pyopenssl_private_key('rsa512_key.pem'),
# pylint: disable=protected-access # pylint: disable=protected-access
test_util.load_cert('cert.pem')._wrapped), test_util.load_cert('cert.pem')),
} }
from acme.standalone import TLSSNI01Server from acme.standalone import TLSSNI01Server
self.server = TLSSNI01Server(("", 0), certs=self.certs) self.server = TLSSNI01Server(("", 0), certs=self.certs)
@@ -146,7 +146,7 @@ class TestSimpleTLSSNI01Server(unittest.TestCase):
time.sleep(1) # wait until thread starts time.sleep(1) # wait until thread starts
else: else:
self.assertEqual(jose.ComparableX509(cert), self.assertEqual(jose.ComparableX509(cert),
test_util.load_cert('cert.pem')) test_util.load_comparable_cert('cert.pem'))
break break
+12 -4
View File
@@ -40,16 +40,24 @@ def load_cert(*names):
"""Load certificate.""" """Load certificate."""
loader = _guess_loader( loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return jose.ComparableX509(OpenSSL.crypto.load_certificate( return OpenSSL.crypto.load_certificate(loader, load_vector(*names))
loader, load_vector(*names)))
def load_comparable_cert(*names):
"""Load ComparableX509 cert."""
return jose.ComparableX509(load_cert(*names))
def load_csr(*names): def load_csr(*names):
"""Load certificate request.""" """Load certificate request."""
loader = _guess_loader( loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return jose.ComparableX509(OpenSSL.crypto.load_certificate_request( return OpenSSL.crypto.load_certificate_request(loader, load_vector(*names))
loader, load_vector(*names)))
def load_comparable_csr(*names):
"""Load ComparableX509 certificate request."""
return jose.ComparableX509(load_csr(*names))
def load_rsa_private_key(*names): def load_rsa_private_key(*names):
+44
View File
@@ -0,0 +1,44 @@
-----BEGIN CERTIFICATE-----
MIIHxDCCB26gAwIBAgIJAOGrG1Un9lHiMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV
BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv
bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X
DTE2MDEwNjE5MDkzN1oXDTE2MDEwNzE5MDkzN1owZDELMAkGA1UECAwCQ0ExFjAU
BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp
ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B
AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580
rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IGATCCBf0wCQYDVR0T
BAIwADALBgNVHQ8EBAMCBeAwggXhBgNVHREEggXYMIIF1IIMZXhhbXBsZTEuY29t
ggxleGFtcGxlMi5jb22CDGV4YW1wbGUzLmNvbYIMZXhhbXBsZTQuY29tggxleGFt
cGxlNS5jb22CDGV4YW1wbGU2LmNvbYIMZXhhbXBsZTcuY29tggxleGFtcGxlOC5j
b22CDGV4YW1wbGU5LmNvbYINZXhhbXBsZTEwLmNvbYINZXhhbXBsZTExLmNvbYIN
ZXhhbXBsZTEyLmNvbYINZXhhbXBsZTEzLmNvbYINZXhhbXBsZTE0LmNvbYINZXhh
bXBsZTE1LmNvbYINZXhhbXBsZTE2LmNvbYINZXhhbXBsZTE3LmNvbYINZXhhbXBs
ZTE4LmNvbYINZXhhbXBsZTE5LmNvbYINZXhhbXBsZTIwLmNvbYINZXhhbXBsZTIx
LmNvbYINZXhhbXBsZTIyLmNvbYINZXhhbXBsZTIzLmNvbYINZXhhbXBsZTI0LmNv
bYINZXhhbXBsZTI1LmNvbYINZXhhbXBsZTI2LmNvbYINZXhhbXBsZTI3LmNvbYIN
ZXhhbXBsZTI4LmNvbYINZXhhbXBsZTI5LmNvbYINZXhhbXBsZTMwLmNvbYINZXhh
bXBsZTMxLmNvbYINZXhhbXBsZTMyLmNvbYINZXhhbXBsZTMzLmNvbYINZXhhbXBs
ZTM0LmNvbYINZXhhbXBsZTM1LmNvbYINZXhhbXBsZTM2LmNvbYINZXhhbXBsZTM3
LmNvbYINZXhhbXBsZTM4LmNvbYINZXhhbXBsZTM5LmNvbYINZXhhbXBsZTQwLmNv
bYINZXhhbXBsZTQxLmNvbYINZXhhbXBsZTQyLmNvbYINZXhhbXBsZTQzLmNvbYIN
ZXhhbXBsZTQ0LmNvbYINZXhhbXBsZTQ1LmNvbYINZXhhbXBsZTQ2LmNvbYINZXhh
bXBsZTQ3LmNvbYINZXhhbXBsZTQ4LmNvbYINZXhhbXBsZTQ5LmNvbYINZXhhbXBs
ZTUwLmNvbYINZXhhbXBsZTUxLmNvbYINZXhhbXBsZTUyLmNvbYINZXhhbXBsZTUz
LmNvbYINZXhhbXBsZTU0LmNvbYINZXhhbXBsZTU1LmNvbYINZXhhbXBsZTU2LmNv
bYINZXhhbXBsZTU3LmNvbYINZXhhbXBsZTU4LmNvbYINZXhhbXBsZTU5LmNvbYIN
ZXhhbXBsZTYwLmNvbYINZXhhbXBsZTYxLmNvbYINZXhhbXBsZTYyLmNvbYINZXhh
bXBsZTYzLmNvbYINZXhhbXBsZTY0LmNvbYINZXhhbXBsZTY1LmNvbYINZXhhbXBs
ZTY2LmNvbYINZXhhbXBsZTY3LmNvbYINZXhhbXBsZTY4LmNvbYINZXhhbXBsZTY5
LmNvbYINZXhhbXBsZTcwLmNvbYINZXhhbXBsZTcxLmNvbYINZXhhbXBsZTcyLmNv
bYINZXhhbXBsZTczLmNvbYINZXhhbXBsZTc0LmNvbYINZXhhbXBsZTc1LmNvbYIN
ZXhhbXBsZTc2LmNvbYINZXhhbXBsZTc3LmNvbYINZXhhbXBsZTc4LmNvbYINZXhh
bXBsZTc5LmNvbYINZXhhbXBsZTgwLmNvbYINZXhhbXBsZTgxLmNvbYINZXhhbXBs
ZTgyLmNvbYINZXhhbXBsZTgzLmNvbYINZXhhbXBsZTg0LmNvbYINZXhhbXBsZTg1
LmNvbYINZXhhbXBsZTg2LmNvbYINZXhhbXBsZTg3LmNvbYINZXhhbXBsZTg4LmNv
bYINZXhhbXBsZTg5LmNvbYINZXhhbXBsZTkwLmNvbYINZXhhbXBsZTkxLmNvbYIN
ZXhhbXBsZTkyLmNvbYINZXhhbXBsZTkzLmNvbYINZXhhbXBsZTk0LmNvbYINZXhh
bXBsZTk1LmNvbYINZXhhbXBsZTk2LmNvbYINZXhhbXBsZTk3LmNvbYINZXhhbXBs
ZTk4LmNvbYINZXhhbXBsZTk5LmNvbYIOZXhhbXBsZTEwMC5jb20wDQYJKoZIhvcN
AQELBQADQQBEunJbKUXcyNKTSfA0pKRyWNiKmkoBqYgfZS6eHNrNH/hjFzHtzyDQ
XYHHK6kgEWBvHfRXGmqhFvht+b1tQKkG
-----END CERTIFICATE-----
+30
View File
@@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFNjCCBOCgAwIBAgIJAP4rNqqOKifCMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV
BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv
bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X
DTE2MDEwNjIwMDg1OFoXDTE2MDEwNzIwMDg1OFowZDELMAkGA1UECAwCQ0ExFjAU
BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp
ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B
AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580
rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IDczCCA28wCQYDVR0T
BAIwADALBgNVHQ8EBAMCBeAwggNTBgNVHREEggNKMIIDRoJiz4PPhM+Fz4bPh8+I
z4nPis+Lz4zPjc+Oz4/PkM+Rz5LPk8+Uz5XPls+Xz5jPmc+az5vPnM+dz57Pn8+g
z6HPos+jz6TPpc+mz6fPqM+pz6rPq8+sz63Prs+vLmludmFsaWSCYs+wz7HPss+z
z7TPtc+2z7fPuM+5z7rPu8+8z73Pvs+/2YHZgtmD2YTZhdmG2YfZiNmJ2YrZi9mM
2Y3ZjtmP2ZDZkdmS2ZPZlNmV2ZbZl9mY2ZnZmtmb2ZzZnS5pbnZhbGlkgmLZntmf
2aDZodmi2aPZpNml2abZp9mo2anZqtmr2azZrdmu2a/ZsNmx2bLZs9m02bXZttm3
2bjZudm62bvZvNm92b7Zv9qA2oHagtqD2oTahdqG2ofaiNqJ2oouaW52YWxpZIJi
2ovajNqN2o7aj9qQ2pHaktqT2pTaldqW2pfamNqZ2pram9qc2p3antqf2qDaodqi
2qPapNql2qbap9qo2qnaqtqr2qzardqu2q/asNqx2rLas9q02rXattq3LmludmFs
aWSCYtq42rnautq72rzavdq+2r/bgNuB24Lbg9uE24XbhtuH24jbiduK24vbjNuN
247bj9uQ25HbktuT25TblduW25fbmNuZ25rbm9uc253bntuf26Dbodui26PbpC5p
bnZhbGlkgnjbpdum26fbqNup26rbq9us263brtuv27Dbsduy27PbtNu127bbt9u4
27nbutu74aCg4aCh4aCi4aCj4aCk4aCl4aCm4aCn4aCo4aCp4aCq4aCr4aCs4aCt
4aCu4aCv4aCw4aCx4aCy4aCz4aC04aC1LmludmFsaWSCgY/hoLbhoLfhoLjhoLnh
oLrhoLvhoLzhoL3hoL7hoL/hoYDhoYHhoYLhoYPhoYThoYXhoYbhoYfhoYjhoYnh
oYrhoYvhoYzhoY3hoY7hoY/hoZDhoZHhoZLhoZPhoZThoZXhoZbhoZfhoZjhoZnh
oZrhoZvhoZzhoZ3hoZ7hoZ/hoaDhoaHhoaIuaW52YWxpZIJE4aGj4aGk4aGl4aGm
4aGn4aGo4aGp4aGq4aGr4aGs4aGt4aGu4aGv4aGw4aGx4aGy4aGz4aG04aG14aG2
LmludmFsaWQwDQYJKoZIhvcNAQELBQADQQAzOQL/54yXxln87/YvEQbBm9ik9zoT
TxEkvnZ4kmTRhDsUPtRjMXhY2FH7LOtXKnJQ7POUB7AsJ2Z6uq2w623G
-----END CERTIFICATE-----
+41
View File
@@ -0,0 +1,41 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIHNTCCBt8CAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz
Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG
A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt
H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6
lUTor4R0T+3C5QIDAQABoIIGFDCCBhAGCSqGSIb3DQEJDjGCBgEwggX9MAkGA1Ud
EwQCMAAwCwYDVR0PBAQDAgXgMIIF4QYDVR0RBIIF2DCCBdSCDGV4YW1wbGUxLmNv
bYIMZXhhbXBsZTIuY29tggxleGFtcGxlMy5jb22CDGV4YW1wbGU0LmNvbYIMZXhh
bXBsZTUuY29tggxleGFtcGxlNi5jb22CDGV4YW1wbGU3LmNvbYIMZXhhbXBsZTgu
Y29tggxleGFtcGxlOS5jb22CDWV4YW1wbGUxMC5jb22CDWV4YW1wbGUxMS5jb22C
DWV4YW1wbGUxMi5jb22CDWV4YW1wbGUxMy5jb22CDWV4YW1wbGUxNC5jb22CDWV4
YW1wbGUxNS5jb22CDWV4YW1wbGUxNi5jb22CDWV4YW1wbGUxNy5jb22CDWV4YW1w
bGUxOC5jb22CDWV4YW1wbGUxOS5jb22CDWV4YW1wbGUyMC5jb22CDWV4YW1wbGUy
MS5jb22CDWV4YW1wbGUyMi5jb22CDWV4YW1wbGUyMy5jb22CDWV4YW1wbGUyNC5j
b22CDWV4YW1wbGUyNS5jb22CDWV4YW1wbGUyNi5jb22CDWV4YW1wbGUyNy5jb22C
DWV4YW1wbGUyOC5jb22CDWV4YW1wbGUyOS5jb22CDWV4YW1wbGUzMC5jb22CDWV4
YW1wbGUzMS5jb22CDWV4YW1wbGUzMi5jb22CDWV4YW1wbGUzMy5jb22CDWV4YW1w
bGUzNC5jb22CDWV4YW1wbGUzNS5jb22CDWV4YW1wbGUzNi5jb22CDWV4YW1wbGUz
Ny5jb22CDWV4YW1wbGUzOC5jb22CDWV4YW1wbGUzOS5jb22CDWV4YW1wbGU0MC5j
b22CDWV4YW1wbGU0MS5jb22CDWV4YW1wbGU0Mi5jb22CDWV4YW1wbGU0My5jb22C
DWV4YW1wbGU0NC5jb22CDWV4YW1wbGU0NS5jb22CDWV4YW1wbGU0Ni5jb22CDWV4
YW1wbGU0Ny5jb22CDWV4YW1wbGU0OC5jb22CDWV4YW1wbGU0OS5jb22CDWV4YW1w
bGU1MC5jb22CDWV4YW1wbGU1MS5jb22CDWV4YW1wbGU1Mi5jb22CDWV4YW1wbGU1
My5jb22CDWV4YW1wbGU1NC5jb22CDWV4YW1wbGU1NS5jb22CDWV4YW1wbGU1Ni5j
b22CDWV4YW1wbGU1Ny5jb22CDWV4YW1wbGU1OC5jb22CDWV4YW1wbGU1OS5jb22C
DWV4YW1wbGU2MC5jb22CDWV4YW1wbGU2MS5jb22CDWV4YW1wbGU2Mi5jb22CDWV4
YW1wbGU2My5jb22CDWV4YW1wbGU2NC5jb22CDWV4YW1wbGU2NS5jb22CDWV4YW1w
bGU2Ni5jb22CDWV4YW1wbGU2Ny5jb22CDWV4YW1wbGU2OC5jb22CDWV4YW1wbGU2
OS5jb22CDWV4YW1wbGU3MC5jb22CDWV4YW1wbGU3MS5jb22CDWV4YW1wbGU3Mi5j
b22CDWV4YW1wbGU3My5jb22CDWV4YW1wbGU3NC5jb22CDWV4YW1wbGU3NS5jb22C
DWV4YW1wbGU3Ni5jb22CDWV4YW1wbGU3Ny5jb22CDWV4YW1wbGU3OC5jb22CDWV4
YW1wbGU3OS5jb22CDWV4YW1wbGU4MC5jb22CDWV4YW1wbGU4MS5jb22CDWV4YW1w
bGU4Mi5jb22CDWV4YW1wbGU4My5jb22CDWV4YW1wbGU4NC5jb22CDWV4YW1wbGU4
NS5jb22CDWV4YW1wbGU4Ni5jb22CDWV4YW1wbGU4Ny5jb22CDWV4YW1wbGU4OC5j
b22CDWV4YW1wbGU4OS5jb22CDWV4YW1wbGU5MC5jb22CDWV4YW1wbGU5MS5jb22C
DWV4YW1wbGU5Mi5jb22CDWV4YW1wbGU5My5jb22CDWV4YW1wbGU5NC5jb22CDWV4
YW1wbGU5NS5jb22CDWV4YW1wbGU5Ni5jb22CDWV4YW1wbGU5Ny5jb22CDWV4YW1w
bGU5OC5jb22CDWV4YW1wbGU5OS5jb22CDmV4YW1wbGUxMDAuY29tMA0GCSqGSIb3
DQEBCwUAA0EAW05UMFavHn2rkzMyUfzsOvWzVNlm43eO2yHu5h5TzDb23gkDnNEo
duUAbQ+CLJHYd+MvRCmPQ+3ZnaPy7l/0Hg==
-----END CERTIFICATE REQUEST-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIEpzCCBFECAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz
Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG
A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt
H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6
lUTor4R0T+3C5QIDAQABoIIDhjCCA4IGCSqGSIb3DQEJDjGCA3MwggNvMAkGA1Ud
EwQCMAAwCwYDVR0PBAQDAgXgMIIDUwYDVR0RBIIDSjCCA0aCYs+Dz4TPhc+Gz4fP
iM+Jz4rPi8+Mz43Pjs+Pz5DPkc+Sz5PPlM+Vz5bPl8+Yz5nPms+bz5zPnc+ez5/P
oM+hz6LPo8+kz6XPps+nz6jPqc+qz6vPrM+tz67Pry5pbnZhbGlkgmLPsM+xz7LP
s8+0z7XPts+3z7jPuc+6z7vPvM+9z77Pv9mB2YLZg9mE2YXZhtmH2YjZidmK2YvZ
jNmN2Y7Zj9mQ2ZHZktmT2ZTZldmW2ZfZmNmZ2ZrZm9mc2Z0uaW52YWxpZIJi2Z7Z
n9mg2aHZotmj2aTZpdmm2afZqNmp2arZq9ms2a3Zrtmv2bDZsdmy2bPZtNm12bbZ
t9m42bnZutm72bzZvdm+2b/agNqB2oLag9qE2oXahtqH2ojaidqKLmludmFsaWSC
YtqL2ozajdqO2o/akNqR2pLak9qU2pXaltqX2pjamdqa2pvanNqd2p7an9qg2qHa
otqj2qTapdqm2qfaqNqp2qraq9qs2q3artqv2rDasdqy2rPatNq12rbaty5pbnZh
bGlkgmLauNq52rrau9q82r3avtq/24DbgduC24PbhNuF24bbh9uI24nbituL24zb
jduO24/bkNuR25Lbk9uU25XbltuX25jbmdua25vbnNud257bn9ug26Hbotuj26Qu
aW52YWxpZIJ426Xbptun26jbqduq26vbrNut267br9uw27Hbstuz27Tbtdu227fb
uNu527rbu+GgoOGgoeGgouGgo+GgpOGgpeGgpuGgp+GgqOGgqeGgquGgq+GgrOGg
reGgruGgr+GgsOGgseGgsuGgs+GgtOGgtS5pbnZhbGlkgoGP4aC24aC34aC44aC5
4aC64aC74aC84aC94aC+4aC/4aGA4aGB4aGC4aGD4aGE4aGF4aGG4aGH4aGI4aGJ
4aGK4aGL4aGM4aGN4aGO4aGP4aGQ4aGR4aGS4aGT4aGU4aGV4aGW4aGX4aGY4aGZ
4aGa4aGb4aGc4aGd4aGe4aGf4aGg4aGh4aGiLmludmFsaWSCROGho+GhpOGhpeGh
puGhp+GhqOGhqeGhquGhq+GhrOGhreGhruGhr+GhsOGhseGhsuGhs+GhtOGhteGh
ti5pbnZhbGlkMA0GCSqGSIb3DQEBCwUAA0EAeNkY0M0+kMnjRo6dEUoGE4dX9fEr
dfGrpPUBcwG0P5QBdZJWvZxTfRl14yuPYHbGHULXeGqRdkU6HK5pOlzpng==
-----END CERTIFICATE REQUEST-----
+5 -3
View File
@@ -6,12 +6,13 @@ from setuptools import find_packages
version = '0.2.0.dev0' version = '0.2.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [ install_requires = [
# load_pem_private/public_key (>=0.6) # load_pem_private/public_key (>=0.6)
# rsa_recover_prime_factors (>=0.8) # rsa_recover_prime_factors (>=0.8)
'cryptography>=0.8,<1.2', 'cryptography>=0.8',
# Connection.set_tlsext_host_name (>=0.13), X509Req.get_extensions (>=0.15) # Connection.set_tlsext_host_name (>=0.13)
'PyOpenSSL>=0.15', 'PyOpenSSL>=0.13',
'pyrfc3339', 'pyrfc3339',
'pytz', 'pytz',
'requests', 'requests',
@@ -65,6 +66,7 @@ setup(
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP',
'Topic :: Security', 'Topic :: Security',
], ],
+14 -1
View File
@@ -139,9 +139,20 @@ Would obtain a single certificate for all of those names, using the
``/var/www/example`` webroot directory for the first two, and ``/var/www/example`` webroot directory for the first two, and
``/var/www/eg`` for the second two. ``/var/www/eg`` for the second two.
The webroot plugin works by creating a temporary file for each of your requested
domains in ``${webroot-path}/.well-known/acme-challenge``. Then the Let's
Encrypt validation server makes HTTP requests to validate that the DNS for each
requested domain resolves to the server running letsencrypt. An example request
made to your web server would look like:
::
66.133.109.36 - - [05/Jan/2016:20:11:24 -0500] "GET /.well-known/acme-challenge/HGr8U1IeTW4kY_Z6UIyaakzOkyQgPr_7ArlLgtZE8SX HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
Note that to use the webroot plugin, your server must be configured to serve Note that to use the webroot plugin, your server must be configured to serve
files from hidden directories. files from hidden directories.
Manual Manual
------ ------
@@ -237,7 +248,9 @@ The following files are available:
server certificate, i.e. root and intermediate certificates only. server certificate, i.e. root and intermediate certificates only.
This is what Apache < 2.4.8 needs for `SSLCertificateChainFile This is what Apache < 2.4.8 needs for `SSLCertificateChainFile
<https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatechainfile>`_. <https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatechainfile>`_,
and what nginx >= 1.3.7 needs for `ssl_trusted_certificate
<http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_trusted_certificate>`_.
``fullchain.pem`` ``fullchain.pem``
All certificates, **including** server certificate. This is All certificates, **including** server certificate. This is
@@ -488,15 +488,27 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:rtype: list :rtype: list
""" """
# Search vhost-root, httpd.conf for possible virtual hosts # Search base config, and all included paths for VirtualHosts
paths = self.aug.match(
("/files%s//*[label()=~regexp('%s')]" %
(self.conf("vhost-root"), parser.case_i("VirtualHost"))))
vhs = [] vhs = []
vhost_paths = {}
for vhost_path in self.parser.parser_paths.keys():
paths = self.aug.match(
("/files%s//*[label()=~regexp('%s')]" %
(vhost_path, parser.case_i("VirtualHost"))))
for path in paths:
new_vhost = self._create_vhost(path)
realpath = os.path.realpath(new_vhost.filep)
if realpath not in vhost_paths.keys():
vhs.append(new_vhost)
vhost_paths[realpath] = new_vhost.filep
elif realpath == new_vhost.filep:
# Prefer "real" vhost paths instead of symlinked ones
# ex: sites-enabled/vh.conf -> sites-available/vh.conf
for path in paths: # remove old (most likely) symlinked one
vhs.append(self._create_vhost(path)) vhs = [v for v in vhs if v.filep != vhost_paths[realpath]]
vhs.append(new_vhost)
vhost_paths[realpath] = realpath
return vhs return vhs
@@ -35,6 +35,7 @@ class ApacheParser(object):
# https://httpd.apache.org/docs/2.4/mod/core.html#define # https://httpd.apache.org/docs/2.4/mod/core.html#define
# https://httpd.apache.org/docs/2.4/mod/core.html#ifdefine # https://httpd.apache.org/docs/2.4/mod/core.html#ifdefine
# This only handles invocation parameters and Define directives! # This only handles invocation parameters and Define directives!
self.parser_paths = {}
self.variables = {} self.variables = {}
if version >= (2, 4): if version >= (2, 4):
self.update_runtime_variables() self.update_runtime_variables()
@@ -471,16 +472,63 @@ class ApacheParser(object):
:param str filepath: Apache config file path :param str filepath: Apache config file path
""" """
use_new, remove_old = self._check_path_actions(filepath)
# Test if augeas included file for Httpd.lens # Test if augeas included file for Httpd.lens
# Note: This works for augeas globs, ie. *.conf # Note: This works for augeas globs, ie. *.conf
inc_test = self.aug.match( if use_new:
"/augeas/load/Httpd/incl [. ='%s']" % filepath) inc_test = self.aug.match(
if not inc_test: "/augeas/load/Httpd/incl [. ='%s']" % filepath)
# Load up files if not inc_test:
# This doesn't seem to work on TravisCI # Load up files
# self.aug.add_transform("Httpd.lns", [filepath]) # This doesn't seem to work on TravisCI
self._add_httpd_transform(filepath) # self.aug.add_transform("Httpd.lns", [filepath])
self.aug.load() if remove_old:
self._remove_httpd_transform(filepath)
self._add_httpd_transform(filepath)
self.aug.load()
def _check_path_actions(self, filepath):
"""Determine actions to take with a new augeas path
This helper function will return a tuple that defines
if we should try to append the new filepath to augeas
parser paths, and / or remove the old one with more
narrow matching.
:param str filepath: filepath to check the actions for
"""
try:
new_file_match = os.path.basename(filepath)
existing_matches = self.parser_paths[os.path.dirname(filepath)]
if "*" in existing_matches:
use_new = False
else:
use_new = True
if new_file_match == "*":
remove_old = True
else:
remove_old = False
except KeyError:
use_new = True
remove_old = False
return use_new, remove_old
def _remove_httpd_transform(self, filepath):
"""Remove path from Augeas transform
:param str filepath: filepath to remove
"""
remove_basenames = self.parser_paths[os.path.dirname(filepath)]
remove_dirname = os.path.dirname(filepath)
for name in remove_basenames:
remove_path = remove_dirname + "/" + name
remove_inc = self.aug.match(
"/augeas/load/Httpd/incl [. ='%s']" % remove_path)
self.aug.remove(remove_inc[0])
self.parser_paths.pop(remove_dirname)
def _add_httpd_transform(self, incl): def _add_httpd_transform(self, incl):
"""Add a transform to Augeas. """Add a transform to Augeas.
@@ -502,6 +550,13 @@ class ApacheParser(object):
# Augeas uses base 1 indexing... insert at beginning... # Augeas uses base 1 indexing... insert at beginning...
self.aug.set("/augeas/load/Httpd/lens", "Httpd.lns") self.aug.set("/augeas/load/Httpd/lens", "Httpd.lns")
self.aug.set("/augeas/load/Httpd/incl", incl) self.aug.set("/augeas/load/Httpd/incl", incl)
# Add included path to paths dictionary
try:
self.parser_paths[os.path.dirname(incl)].append(
os.path.basename(incl))
except KeyError:
self.parser_paths[os.path.dirname(incl)] = [
os.path.basename(incl)]
def standardize_excl(self): def standardize_excl(self):
"""Standardize the excl arguments for the Httpd lens in Augeas. """Standardize the excl arguments for the Httpd lens in Augeas.
@@ -49,7 +49,8 @@ if [ "$1" = --debian-modules ] ; then
sudo apt-get install -y libapache2-mod-wsgi sudo apt-get install -y libapache2-mod-wsgi
sudo apt-get install -y libapache2-mod-macro sudo apt-get install -y libapache2-mod-macro
for mod in ssl rewrite macro wsgi deflate userdir version mime ; do for mod in ssl rewrite macro wsgi deflate userdir version mime setenvif ; do
echo -n enabling $mod
sudo a2enmod $mod sudo a2enmod $mod
done done
fi fi
@@ -128,20 +128,10 @@ class TwoVhost80Test(util.ApacheTest):
self.assertEqual(found, 6) self.assertEqual(found, 6)
# Handle case of non-debian layout get_virtual_hosts # Handle case of non-debian layout get_virtual_hosts
orig_conf = self.config.conf
with mock.patch( with mock.patch(
"letsencrypt_apache.configurator.ApacheConfigurator.conf" "letsencrypt_apache.configurator.ApacheConfigurator.conf"
) as mock_conf: ) as mock_conf:
def conf_sideeffect(key): mock_conf.return_value = False
"""Handle calls to configurator.conf()
:param key: configuration key
:return: configuration value
"""
if key == "handle-sites":
return False
else:
return orig_conf(key)
mock_conf.side_effect = conf_sideeffect
vhs = self.config.get_virtual_hosts() vhs = self.config.get_virtual_hosts()
self.assertEqual(len(vhs), 6) self.assertEqual(len(vhs), 6)
@@ -36,7 +36,7 @@ class BasicParserTest(util.ParserTest):
""" """
file_path = os.path.join( file_path = os.path.join(
self.config_path, "sites-available", "letsencrypt.conf") self.config_path, "not-parsed-by-default", "letsencrypt.conf")
self.parser._parse_file(file_path) # pylint: disable=protected-access self.parser._parse_file(file_path) # pylint: disable=protected-access
+1
View File
@@ -6,6 +6,7 @@ from setuptools import find_packages
version = '0.2.0.dev0' version = '0.2.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [ install_requires = [
'acme=={0}'.format(version), 'acme=={0}'.format(version),
'letsencrypt=={0}'.format(version), 'letsencrypt=={0}'.format(version),
@@ -31,28 +31,42 @@ BootstrapDebCommon() {
virtualenv="$virtualenv python-virtualenv" virtualenv="$virtualenv python-virtualenv"
fi fi
augeas_pkg=libaugeas0 augeas_pkg="libaugeas0 augeas-lenses"
AUGVERSION=`apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2` AUGVERSION=`apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2`
AddBackportRepo() {
# ARGS:
BACKPORT_NAME="$1"
BACKPORT_SOURCELINE="$2"
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
sleep 1s
if echo $BACKPORT_NAME | grep -q wheezy ; then
/bin/echo '(Backports are only installed if explicitly requested via "apt-get install -t wheezy-backports")'
fi
sudo sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO apt-get update
fi
fi
$SUDO apt-get install -y --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
augeas_pkg=
}
if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then
if lsb_release -a | grep -q wheezy ; then if lsb_release -a | grep -q wheezy ; then
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q wheezy-backports ; then AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main"
# This can theoretically error if sources.list.d is empty, but in that case we don't care. elif lsb_release -a | grep -q precise ; then
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q wheezy-backports ; then # XXX add ARM case
/bin/echo -n "Installing augeas from wheezy-backports in 3 seconds..." AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
sleep 1s
/bin/echo -ne "\e[0K\rInstalling augeas from wheezy-backports in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rInstalling augeas from wheezy-backports in 1 second ..."
sleep 1s
/bin/echo '(Backports are only installed if explicitly requested via "apt-get install -t wheezy-backports")'
sudo sh -c 'echo deb http://http.debian.net/debian wheezy-backports main >> /etc/apt/sources.list.d/wheezy-backports.list'
$SUDO apt-get update
fi
fi
$SUDO apt-get install -y --no-install-recommends -t wheezy-backports libaugeas0
augeas_pkg=
else else
echo "No libaugeas0 version is available that's new enough to run the" echo "No libaugeas0 version is available that's new enough to run the"
echo "Let's Encrypt apache plugin..." echo "Let's Encrypt apache plugin..."
@@ -122,7 +122,7 @@ class NginxConfigurator(common.Plugin):
# Entry point in main.py for installing cert # Entry point in main.py for installing cert
def deploy_cert(self, domain, cert_path, key_path, def deploy_cert(self, domain, cert_path, key_path,
chain_path, fullchain_path): chain_path=None, fullchain_path=None):
# pylint: disable=unused-argument # pylint: disable=unused-argument
"""Deploys certificate to specified virtual host. """Deploys certificate to specified virtual host.
@@ -136,7 +136,15 @@ class NginxConfigurator(common.Plugin):
.. note:: This doesn't save the config files! .. note:: This doesn't save the config files!
:raises errors.PluginError: When unable to deploy certificate due to
a lack of directives or configuration
""" """
if not fullchain_path:
raise errors.PluginError(
"The nginx plugin currently requires --fullchain-path to "
"install a cert.")
vhost = self.choose_vhost(domain) vhost = self.choose_vhost(domain)
cert_directives = [['ssl_certificate', fullchain_path], cert_directives = [['ssl_certificate', fullchain_path],
['ssl_certificate_key', key_path]] ['ssl_certificate_key', key_path]]
@@ -150,6 +158,12 @@ class NginxConfigurator(common.Plugin):
['ssl_stapling', 'on'], ['ssl_stapling', 'on'],
['ssl_stapling_verify', 'on']] ['ssl_stapling_verify', 'on']]
if len(stapling_directives) != 0 and not chain_path:
raise errors.PluginError(
"--chain-path is required to enable "
"Online Certificate Status Protocol (OCSP) stapling "
"on nginx >= 1.3.7.")
try: try:
self.parser.add_server_directives(vhost.filep, vhost.names, self.parser.add_server_directives(vhost.filep, vhost.names,
cert_directives, replace=True) cert_directives, replace=True)
@@ -168,7 +182,7 @@ class NginxConfigurator(common.Plugin):
self.save_notes += ("Changed vhost at %s with addresses of %s\n" % self.save_notes += ("Changed vhost at %s with addresses of %s\n" %
(vhost.filep, (vhost.filep,
", ".join(str(addr) for addr in vhost.addrs))) ", ".join(str(addr) for addr in vhost.addrs)))
self.save_notes += "\tssl_certificate %s\n" % cert_path self.save_notes += "\tssl_certificate %s\n" % fullchain_path
self.save_notes += "\tssl_certificate_key %s\n" % key_path self.save_notes += "\tssl_certificate_key %s\n" % key_path
####################### #######################
@@ -113,7 +113,7 @@ class NginxParser(object):
for filename in servers: for filename in servers:
for server in servers[filename]: for server in servers[filename]:
# Parse the server block into a VirtualHost object # Parse the server block into a VirtualHost object
parsed_server = _parse_server(server) parsed_server = parse_server(server)
vhost = obj.VirtualHost(filename, vhost = obj.VirtualHost(filename,
parsed_server['addrs'], parsed_server['addrs'],
parsed_server['ssl'], parsed_server['ssl'],
@@ -451,7 +451,7 @@ def _get_servernames(names):
return names.split(' ') return names.split(' ')
def _parse_server(server): def parse_server(server):
"""Parses a list of server directives. """Parses a list of server directives.
:param list server: list of directives in a server block :param list server: list of directives in a server block
@@ -471,6 +471,8 @@ def _parse_server(server):
elif directive[0] == 'server_name': elif directive[0] == 'server_name':
parsed_server['names'].update( parsed_server['names'].update(
_get_servernames(directive[1])) _get_servernames(directive[1]))
elif directive[0] == 'ssl' and directive[1] == 'on':
parsed_server['ssl'] = True
return parsed_server return parsed_server
@@ -159,6 +159,24 @@ class NginxConfiguratorTest(util.NginxTest):
self.assertTrue(util.contains_at_depth(generated_conf, self.assertTrue(util.contains_at_depth(generated_conf,
['ssl_trusted_certificate', 'example/chain.pem'], 2)) ['ssl_trusted_certificate', 'example/chain.pem'], 2))
def test_deploy_cert_stapling_requires_chain_path(self):
self.config.version = (1, 3, 7)
self.assertRaises(errors.PluginError, self.config.deploy_cert,
"www.example.com",
"example/cert.pem",
"example/key.pem",
None,
"example/fullchain.pem")
def test_deploy_cert_requires_fullchain_path(self):
self.config.version = (1, 3, 1)
self.assertRaises(errors.PluginError, self.config.deploy_cert,
"www.example.com",
"example/cert.pem",
"example/key.pem",
"example/chain.pem",
None)
def test_deploy_cert(self): def test_deploy_cert(self):
server_conf = self.config.parser.abs_path('server.conf') server_conf = self.config.parser.abs_path('server.conf')
nginx_conf = self.config.parser.abs_path('nginx.conf') nginx_conf = self.config.parser.abs_path('nginx.conf')
@@ -228,6 +228,26 @@ class NginxParserTest(util.NginxTest):
c_k = nparser.get_all_certs_keys() c_k = nparser.get_all_certs_keys()
self.assertEqual(set([('foo.pem', 'bar.key', filep)]), c_k) self.assertEqual(set([('foo.pem', 'bar.key', filep)]), c_k)
def test_parse_server_ssl(self):
server = parser.parse_server([
['listen', '443']
])
self.assertFalse(server['ssl'])
server = parser.parse_server([
['listen', '443 ssl']
])
self.assertTrue(server['ssl'])
server = parser.parse_server([
['listen', '443'], ['ssl', 'off']
])
self.assertFalse(server['ssl'])
server = parser.parse_server([
['listen', '443'], ['ssl', 'on']
])
self.assertTrue(server['ssl'])
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() # pragma: no cover unittest.main() # pragma: no cover
+1
View File
@@ -6,6 +6,7 @@ from setuptools import find_packages
version = '0.2.0.dev0' version = '0.2.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [ install_requires = [
'acme=={0}'.format(version), 'acme=={0}'.format(version),
'letsencrypt=={0}'.format(version), 'letsencrypt=={0}'.format(version),
+1 -1
View File
@@ -392,7 +392,7 @@ def _auth_from_domains(le_client, config, domains):
# TODO: Check whether it worked! <- or make sure errors are thrown (jdk) # TODO: Check whether it worked! <- or make sure errors are thrown (jdk)
lineage.save_successor( lineage.save_successor(
lineage.latest_common_version(), OpenSSL.crypto.dump_certificate( lineage.latest_common_version(), OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, new_certr.body), OpenSSL.crypto.FILETYPE_PEM, new_certr.body.wrapped),
new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain)) new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain))
lineage.update_all_links_to(lineage.latest_common_version()) lineage.update_all_links_to(lineage.latest_common_version())
+2 -2
View File
@@ -300,7 +300,7 @@ class Client(object):
lineage = storage.RenewableCert.new_lineage( lineage = storage.RenewableCert.new_lineage(
domains[0], OpenSSL.crypto.dump_certificate( domains[0], OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, certr.body), OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped),
key.pem, crypto_util.dump_pyopenssl_chain(chain), key.pem, crypto_util.dump_pyopenssl_chain(chain),
params, config, cli_config) params, config, cli_config)
return lineage return lineage
@@ -330,7 +330,7 @@ class Client(object):
self.config.strict_permissions) self.config.strict_permissions)
cert_pem = OpenSSL.crypto.dump_certificate( cert_pem = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, certr.body) OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped)
cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644) cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644)
try: try:
cert_file.write(cert_pem) cert_file.write(cert_pem)
+1 -1
View File
@@ -271,7 +271,7 @@ def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
def _dump_cert(cert): def _dump_cert(cert):
if isinstance(cert, jose.ComparableX509): if isinstance(cert, jose.ComparableX509):
# pylint: disable=protected-access # pylint: disable=protected-access
cert = cert._wrapped cert = cert.wrapped
return OpenSSL.crypto.dump_certificate(filetype, cert) return OpenSSL.crypto.dump_certificate(filetype, cert)
# assumes that OpenSSL.crypto.dump_certificate includes ending # assumes that OpenSSL.crypto.dump_certificate includes ending
+1 -1
View File
@@ -150,7 +150,7 @@ class Authenticator(common.Plugin):
# one self-signed key for all tls-sni-01 certificates # one self-signed key for all tls-sni-01 certificates
self.key = OpenSSL.crypto.PKey() self.key = OpenSSL.crypto.PKey()
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, bits=2048) self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
self.served = collections.defaultdict(set) self.served = collections.defaultdict(set)
+1 -1
View File
@@ -103,7 +103,7 @@ def renew(cert, old_version):
# already understands this distinction!) # already understands this distinction!)
return cert.save_successor( return cert.save_successor(
old_version, OpenSSL.crypto.dump_certificate( old_version, OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, new_certr.body), OpenSSL.crypto.FILETYPE_PEM, new_certr.body.wrapped),
new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain)) new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain))
# TODO: Notify results # TODO: Notify results
else: else:
+3 -2
View File
@@ -564,8 +564,9 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
logger.debug("Should renew, certificate is revoked.") logger.debug("Should renew, certificate is revoked.")
return True return True
# Renewals on the basis of expiry time # Renews some period before expiry time
interval = self.configuration.get("renew_before_expiry", "10 days") default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"]
interval = self.configuration.get("renew_before_expiry", default_interval)
expiry = crypto_util.notAfter(self.version( expiry = crypto_util.notAfter(self.version(
"cert", self.latest_common_version())) "cert", self.latest_common_version()))
now = pytz.UTC.fromutc(datetime.datetime.utcnow()) now = pytz.UTC.fromutc(datetime.datetime.utcnow())
+2 -2
View File
@@ -417,11 +417,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
chain_path = '/etc/letsencrypt/live/foo.bar/fullchain.pem' chain_path = '/etc/letsencrypt/live/foo.bar/fullchain.pem'
mock_lineage = mock.MagicMock(cert=cert_path, fullchain=chain_path) mock_lineage = mock.MagicMock(cert=cert_path, fullchain=chain_path)
mock_cert = mock.MagicMock(body='body') mock_certr = mock.MagicMock()
mock_key = mock.MagicMock(pem='pem_key') mock_key = mock.MagicMock(pem='pem_key')
mock_renewal.return_value = ("renew", mock_lineage) mock_renewal.return_value = ("renew", mock_lineage)
mock_client = mock.MagicMock() mock_client = mock.MagicMock()
mock_client.obtain_certificate.return_value = (mock_cert, 'chain', mock_client.obtain_certificate.return_value = (mock_certr, 'chain',
mock_key, 'csr') mock_key, 'csr')
mock_init.return_value = mock_client mock_init.return_value = mock_client
with mock.patch('letsencrypt.cli.OpenSSL'): with mock.patch('letsencrypt.cli.OpenSSL'):
+3 -3
View File
@@ -141,9 +141,9 @@ class ClientTest(unittest.TestCase):
tmp_path = tempfile.mkdtemp() tmp_path = tempfile.mkdtemp()
os.chmod(tmp_path, 0o755) # TODO: really?? os.chmod(tmp_path, 0o755) # TODO: really??
certr = mock.MagicMock(body=test_util.load_cert(certs[0])) certr = mock.MagicMock(body=test_util.load_comparable_cert(certs[0]))
chain_cert = [test_util.load_cert(certs[1]), chain_cert = [test_util.load_comparable_cert(certs[1]),
test_util.load_cert(certs[2])] test_util.load_comparable_cert(certs[2])]
candidate_cert_path = os.path.join(tmp_path, "certs", "cert.pem") candidate_cert_path = os.path.join(tmp_path, "certs", "cert.pem")
candidate_chain_path = os.path.join(tmp_path, "chains", "chain.pem") candidate_chain_path = os.path.join(tmp_path, "chains", "chain.pem")
candidate_fullchain_path = os.path.join(tmp_path, "chains", "fullchain.pem") candidate_fullchain_path = os.path.join(tmp_path, "chains", "fullchain.pem")
+5 -2
View File
@@ -9,6 +9,8 @@ import unittest
import configobj import configobj
import mock import mock
from acme import jose
from letsencrypt import configuration from letsencrypt import configuration
from letsencrypt import errors from letsencrypt import errors
from letsencrypt.storage import ALL_FOUR from letsencrypt.storage import ALL_FOUR
@@ -702,9 +704,10 @@ class RenewableCertTests(BaseRenewableCertTest):
self.test_rc.configfile["renewalparams"]["authenticator"] = "apache" self.test_rc.configfile["renewalparams"]["authenticator"] = "apache"
mock_client = mock.MagicMock() mock_client = mock.MagicMock()
# pylint: disable=star-args # pylint: disable=star-args
comparable_cert = jose.ComparableX509(CERT)
mock_client.obtain_certificate.return_value = ( mock_client.obtain_certificate.return_value = (
mock.MagicMock(body=CERT), [CERT], mock.Mock(pem="key"), mock.MagicMock(body=comparable_cert), [comparable_cert],
mock.sentinel.csr) mock.Mock(pem="key"), mock.sentinel.csr)
mock_c.return_value = mock_client mock_c.return_value = mock_client
self.assertEqual(2, renewer.renew(self.test_rc, 1)) self.assertEqual(2, renewer.renew(self.test_rc, 1))
# TODO: We could also make several assertions about calls that should # TODO: We could also make several assertions about calls that should
+12 -4
View File
@@ -40,16 +40,24 @@ def load_cert(*names):
"""Load certificate.""" """Load certificate."""
loader = _guess_loader( loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return jose.ComparableX509(OpenSSL.crypto.load_certificate( return OpenSSL.crypto.load_certificate(loader, load_vector(*names))
loader, load_vector(*names)))
def load_comparable_cert(*names):
"""Load ComparableX509 cert."""
return jose.ComparableX509(load_cert(*names))
def load_csr(*names): def load_csr(*names):
"""Load certificate request.""" """Load certificate request."""
loader = _guess_loader( loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return jose.ComparableX509(OpenSSL.crypto.load_certificate_request( return OpenSSL.crypto.load_certificate_request(loader, load_vector(*names))
loader, load_vector(*names)))
def load_comparable_csr(*names):
"""Load ComparableX509 certificate request."""
return jose.ComparableX509(load_csr(*names))
def load_rsa_private_key(*names): def load_rsa_private_key(*names):
+2 -1
View File
@@ -30,10 +30,11 @@ readme = read_file(os.path.join(here, 'README.rst'))
changes = read_file(os.path.join(here, 'CHANGES.rst')) changes = read_file(os.path.join(here, 'CHANGES.rst'))
version = meta['version'] version = meta['version']
# Please update tox.ini when modifying dependency version requirements
install_requires = [ install_requires = [
'acme=={0}'.format(version), 'acme=={0}'.format(version),
'configobj', 'configobj',
'cryptography>=0.7,<1.2', # load_pem_x509_certificate 'cryptography>=0.7', # load_pem_x509_certificate
'parsedatetime', 'parsedatetime',
'psutil>=2.1.0', # net_connections introduced in 2.1.0 'psutil>=2.1.0', # net_connections introduced in 2.1.0
'PyOpenSSL', 'PyOpenSSL',
+9 -2
View File
@@ -6,7 +6,7 @@
# acme and letsencrypt are not yet on pypi, so when Tox invokes # acme and letsencrypt are not yet on pypi, so when Tox invokes
# "install *.zip", it will not find deps # "install *.zip", it will not find deps
skipsdist = true skipsdist = true
envlist = py26,py27,py33,py34,py35,cover,lint envlist = py{26,27,33,34,35},py{26,27}-oldest,cover,lint
[testenv] [testenv]
# packages installed separately to ensure that downstream deps problems # packages installed separately to ensure that downstream deps problems
@@ -28,6 +28,13 @@ setenv =
PYTHONHASHSEED = 0 PYTHONHASHSEED = 0
# https://testrun.org/tox/latest/example/basic.html#special-handling-of-pythonhas # https://testrun.org/tox/latest/example/basic.html#special-handling-of-pythonhas
deps =
py{26,27}-oldest: cryptography==0.8
py{26,27}-oldest: configargparse==0.10.0
py{26,27}-oldest: psutil==2.1.0
py{26,27}-oldest: PyOpenSSL==0.13
py{26,27}-oldest: python2-pythondialog==3.2.2rc1
[testenv:py33] [testenv:py33]
commands = commands =
pip install -e acme pip install -e acme
@@ -59,7 +66,7 @@ commands =
pip install -e acme -e .[dev] -e letsencrypt-apache -e letsencrypt-nginx -e letsencrypt-compatibility-test -e letshelp-letsencrypt pip install -e acme -e .[dev] -e letsencrypt-apache -e letsencrypt-nginx -e letsencrypt-compatibility-test -e letshelp-letsencrypt
./pep8.travis.sh ./pep8.travis.sh
pylint --rcfile=.pylintrc letsencrypt pylint --rcfile=.pylintrc letsencrypt
pylint --rcfile=.pylintrc acme/acme pylint --rcfile=acme/.pylintrc acme/acme
pylint --rcfile=.pylintrc letsencrypt-apache/letsencrypt_apache pylint --rcfile=.pylintrc letsencrypt-apache/letsencrypt_apache
pylint --rcfile=.pylintrc letsencrypt-nginx/letsencrypt_nginx pylint --rcfile=.pylintrc letsencrypt-nginx/letsencrypt_nginx
pylint --rcfile=.pylintrc letsencrypt-compatibility-test/letsencrypt_compatibility_test pylint --rcfile=.pylintrc letsencrypt-compatibility-test/letsencrypt_compatibility_test