Hello community,

here is the log from the commit of package python-acme for openSUSE:Factory 
checked in at 2017-05-17 17:18:46
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-acme (Old)
 and      /work/SRC/openSUSE:Factory/.python-acme.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-acme"

Wed May 17 17:18:46 2017 rev:5 rq:495251 version:0.14.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-acme/python-acme.changes  2017-04-06 
11:01:48.770444504 +0200
+++ /work/SRC/openSUSE:Factory/.python-acme.new/python-acme.changes     
2017-05-17 17:19:30.751273964 +0200
@@ -1,0 +2,17 @@
+Tue May 16 10:50:51 UTC 2017 - ec...@opensuse.org
+
+- fix build error in Tumbleweed
+
+-------------------------------------------------------------------
+Mon May 15 10:51:46 UTC 2017 - ec...@opensuse.org
+
+- update to 0.14.0
+  - No changelog provides by upstream
+
+-------------------------------------------------------------------
+Tue Apr 25 20:53:13 UTC 2017 - ec...@opensuse.org
+
+- update to 0.13.0
+  - No changelog provides by upstream
+
+-------------------------------------------------------------------

Old:
----
  acme-0.12.0.tar.gz
  acme-0.12.0.tar.gz.asc

New:
----
  acme-0.14.0.tar.gz
  acme-0.14.0.tar.gz.asc

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-acme.spec ++++++
--- /var/tmp/diff_new_pack.TsT36B/_old  2017-05-17 17:19:31.391183720 +0200
+++ /var/tmp/diff_new_pack.TsT36B/_new  2017-05-17 17:19:31.395183156 +0200
@@ -18,7 +18,7 @@
 
 %define libname acme
 Name:           python-%{libname}
-Version:        0.12.0
+Version:        0.14.0
 Release:        0
 Summary:        Python library for the ACME protocol
 License:        Apache-2.0
@@ -28,16 +28,22 @@
 Source1:        
https://files.pythonhosted.org/packages/source/a/%{libname}/%{libname}-%{version}.tar.gz.asc
 Source2:        %{name}.keyring
 BuildRequires:  fdupes
+BuildRequires:  python-argparse
 BuildRequires:  python-cryptography >= 0.8
 BuildRequires:  python-devel
 BuildRequires:  python-dnspython >= 1.12
 BuildRequires:  python-mock
 BuildRequires:  python-ndg-httpsclient
 BuildRequires:  python-nose
+BuildRequires:  python-packaging
 BuildRequires:  python-pyOpenSSL >= 0.13
 BuildRequires:  python-pyRFC3339
 BuildRequires:  python-pytz
+%if 0%{?suse_version} >= 1330
+BuildRequires:  python2-requests >= 2.10
+%else
 BuildRequires:  python-requests >= 2.10
+%endif
 BuildRequires:  python-setuptools >= 11.3
 BuildRequires:  python-six >= 1.5.2
 BuildRequires:  python-sphinx
@@ -45,7 +51,7 @@
 BuildRequires:  python-sphinxcontrib-programoutput
 BuildRequires:  python-tox
 BuildRequires:  python-werkzeug
-BuildRequires:  python-packaging
+Requires:       python-argparse
 Requires:       python-cryptography >= 0.8
 Requires:       python-dnspython >= 1.12
 Requires:       python-mock
@@ -54,7 +60,11 @@
 Requires:       python-pyRFC3339
 Requires:       python-pyasn1
 Requires:       python-pytz
+%if 0%{?suse_version} >= 1330
+Requires:       python2-requests >= 2.10
+%else
 Requires:       python-requests >= 2.10
+%endif
 Requires:       python-six >= 1.5.2
 Requires:       python-werkzeug
 BuildArch:      noarch

++++++ acme-0.12.0.tar.gz -> acme-0.14.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/PKG-INFO new/acme-0.14.0/PKG-INFO
--- old/acme-0.12.0/PKG-INFO    2017-03-02 23:01:44.000000000 +0100
+++ new/acme-0.14.0/PKG-INFO    2017-05-05 01:45:31.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: acme
-Version: 0.12.0
+Version: 0.14.0
 Summary: ACME protocol implementation in Python
 Home-page: https://github.com/letsencrypt/letsencrypt
 Author: Certbot Project
@@ -19,5 +19,6 @@
 Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
 Classifier: Topic :: Internet :: WWW/HTTP
 Classifier: Topic :: Security
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/challenges.py 
new/acme-0.14.0/acme/challenges.py
--- old/acme-0.12.0/acme/challenges.py  2017-03-02 23:01:28.000000000 +0100
+++ new/acme-0.14.0/acme/challenges.py  2017-05-05 01:45:16.000000000 +0200
@@ -5,7 +5,7 @@
 import logging
 import socket
 
-from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives import hashes  # type: ignore
 import OpenSSL
 import requests
 
@@ -23,7 +23,7 @@
 class Challenge(jose.TypedJSONObjectWithFields):
     # _fields_to_partial_json | pylint: disable=abstract-method
     """ACME challenge."""
-    TYPES = {}
+    TYPES = {}  # type: dict
 
     @classmethod
     def from_json(cls, jobj):
@@ -37,7 +37,7 @@
 class ChallengeResponse(jose.TypedJSONObjectWithFields):
     # _fields_to_partial_json | pylint: disable=abstract-method
     """ACME challenge response."""
-    TYPES = {}
+    TYPES = {}  # type: dict
     resource_type = 'challenge'
     resource = fields.Resource(resource_type)
 
@@ -445,7 +445,7 @@
         """
         # pylint: disable=protected-access
         sans = crypto_util._pyopenssl_cert_or_req_san(cert)
-        logger.debug('Certificate %s. SANs: %s', cert.digest('sha1'), sans)
+        logger.debug('Certificate %s. SANs: %s', cert.digest('sha256'), sans)
         return self.z_domain.decode() in sans
 
     def simple_verify(self, chall, domain, account_public_key,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/client.py 
new/acme-0.14.0/acme/client.py
--- old/acme-0.12.0/acme/client.py      2017-03-02 23:01:28.000000000 +0100
+++ new/acme-0.14.0/acme/client.py      2017-05-05 01:45:16.000000000 +0200
@@ -28,11 +28,13 @@
 # 
https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning
 if sys.version_info < (2, 7, 9):  # pragma: no cover
     try:
-        requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3()
+        requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3()  # 
type: ignore
     except AttributeError:
         import urllib3.contrib.pyopenssl  # pylint: disable=import-error
         urllib3.contrib.pyopenssl.inject_into_urllib3()
 
+DEFAULT_NETWORK_TIMEOUT = 45
+
 DER_CONTENT_TYPE = 'application/pkix-cert'
 
 
@@ -71,20 +73,13 @@
             self.directory = directory
 
     @classmethod
-    def _regr_from_response(cls, response, uri=None, new_authzr_uri=None,
-                            terms_of_service=None):
+    def _regr_from_response(cls, response, uri=None, terms_of_service=None):
         if 'terms-of-service' in response.links:
             terms_of_service = response.links['terms-of-service']['url']
-        if 'next' in response.links:
-            new_authzr_uri = response.links['next']['url']
-
-        if new_authzr_uri is None:
-            raise errors.ClientError('"next" link missing')
 
         return messages.RegistrationResource(
             body=messages.Registration.from_json(response.json()),
             uri=response.headers.get('Location', uri),
-            new_authzr_uri=new_authzr_uri,
             terms_of_service=terms_of_service)
 
     def register(self, new_reg=None):
@@ -117,7 +112,7 @@
         # (c.f. acme-spec #94)
 
         return self._regr_from_response(
-            response, uri=regr.uri, new_authzr_uri=regr.new_authzr_uri,
+            response, uri=regr.uri,
             terms_of_service=regr.terms_of_service)
 
     def update_registration(self, regr, update=None):
@@ -134,8 +129,6 @@
         update = regr.body if update is None else update
         body = messages.UpdateRegistration(**dict(update))
         updated_regr = self._send_recv_regr(regr, body=body)
-        if updated_regr != regr:
-            raise errors.UnexpectedUpdate(regr)
         return updated_regr
 
     def deactivate_registration(self, regr):
@@ -174,19 +167,10 @@
         return self.update_registration(
             
regr.update(body=regr.body.update(agreement=regr.terms_of_service)))
 
-    def _authzr_from_response(self, response, identifier,
-                              uri=None, new_cert_uri=None):
-        # pylint: disable=no-self-use
-        if new_cert_uri is None:
-            try:
-                new_cert_uri = response.links['next']['url']
-            except KeyError:
-                raise errors.ClientError('"next" link missing')
-
+    def _authzr_from_response(self, response, identifier, uri=None):
         authzr = messages.AuthorizationResource(
             body=messages.Authorization.from_json(response.json()),
-            uri=response.headers.get('Location', uri),
-            new_cert_uri=new_cert_uri)
+            uri=response.headers.get('Location', uri))
         if authzr.body.identifier != identifier:
             raise errors.UnexpectedUpdate(authzr)
         return authzr
@@ -195,17 +179,16 @@
         """Request challenges.
 
         :param .messages.Identifier identifier: Identifier to be challenged.
-        :param str new_authzr_uri: ``new-authorization`` URI. If omitted,
-            will default to value found in ``directory``.
+        :param str new_authzr_uri: Deprecated. Do not use.
 
         :returns: Authorization Resource.
         :rtype: `.AuthorizationResource`
 
         """
+        if new_authzr_uri is not None:
+            logger.debug("request_challenges with new_authzr_uri deprecated.")
         new_authz = messages.NewAuthorization(identifier=identifier)
-        response = self.net.post(self.directory.new_authz
-                                 if new_authzr_uri is None else new_authzr_uri,
-                                 new_authz)
+        response = self.net.post(self.directory.new_authz, new_authz)
         # TODO: handle errors
         assert response.status_code == http_client.CREATED
         return self._authzr_from_response(response, identifier)
@@ -219,6 +202,7 @@
         documentation.
 
         :param str domain: Domain name to be challenged.
+        :param str new_authzr_uri: Deprecated. Do not use.
 
         :returns: Authorization Resource.
         :rtype: `.AuthorizationResource`
@@ -300,8 +284,7 @@
         """
         response = self.net.get(authzr.uri)
         updated_authzr = self._authzr_from_response(
-            response, authzr.body.identifier, authzr.uri, authzr.new_cert_uri)
-        # TODO: check and raise UnexpectedUpdate
+            response, authzr.body.identifier, authzr.uri)
         return updated_authzr, response
 
     def request_issuance(self, csr, authzrs):
@@ -324,7 +307,7 @@
 
         content_type = DER_CONTENT_TYPE  # TODO: add 'cert_type 'argument
         response = self.net.post(
-            authzrs[0].new_cert_uri,  # TODO: acme-spec #90
+            self.directory.new_cert,
             req,
             content_type=content_type,
             headers={'Accept': content_type})
@@ -377,14 +360,18 @@
 
         # priority queue with datetime.datetime (based on Retry-After) as key,
         # and original Authorization Resource as value
-        waiting = [(datetime.datetime.now(), authzr) for authzr in authzrs]
+        waiting = [
+            (datetime.datetime.now(), index, authzr)
+            for index, authzr in enumerate(authzrs)
+        ]
+        heapq.heapify(waiting)
         # mapping between original Authorization Resource and the most
         # recently updated one
         updated = dict((authzr, authzr) for authzr in authzrs)
 
         while waiting:
             # find the smallest Retry-After, and sleep if necessary
-            when, authzr = heapq.heappop(waiting)
+            when, index, authzr = heapq.heappop(waiting)
             now = datetime.datetime.now()
             if when > now:
                 seconds = (when - now).seconds
@@ -403,7 +390,7 @@
                 if attempts[authzr] < max_attempts:
                     # push back to the priority queue, with updated retry_after
                     heapq.heappush(waiting, (self.retry_after(
-                        response, default=mintime), authzr))
+                        response, default=mintime), index, authzr))
                 else:
                     exhausted.add(authzr)
 
@@ -522,13 +509,14 @@
     REPLAY_NONCE_HEADER = 'Replay-Nonce'
 
     def __init__(self, key, alg=jose.RS256, verify_ssl=True,
-                 user_agent='acme-python'):
+                 user_agent='acme-python', timeout=DEFAULT_NETWORK_TIMEOUT):
         self.key = key
         self.alg = alg
         self.verify_ssl = verify_ssl
         self._nonces = set()
         self.user_agent = user_agent
         self.session = requests.Session()
+        self._default_timeout = timeout
 
     def __del__(self):
         self.session.close()
@@ -627,7 +615,7 @@
         kwargs['verify'] = self.verify_ssl
         kwargs.setdefault('headers', {})
         kwargs['headers'].setdefault('User-Agent', self.user_agent)
-        kwargs.setdefault('timeout', 45) # timeout after 45 seconds
+        kwargs.setdefault('timeout', self._default_timeout)
         response = self.session.request(method, url, *args, **kwargs)
         # If content is DER, log the base64 of it instead of raw bytes, to keep
         # binary data out of the logs.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/client_test.py 
new/acme-0.14.0/acme/client_test.py
--- old/acme-0.12.0/acme/client_test.py 2017-03-02 23:01:28.000000000 +0100
+++ new/acme-0.14.0/acme/client_test.py 2017-05-05 01:45:16.000000000 +0200
@@ -40,6 +40,8 @@
                 'https://www.letsencrypt-demo.org/acme/revoke-cert',
             messages.NewAuthorization:
                 'https://www.letsencrypt-demo.org/acme/new-authz',
+            messages.CertificateRequest:
+                'https://www.letsencrypt-demo.org/acme/new-cert',
         })
 
         from acme.client import Client
@@ -56,7 +58,6 @@
         self.new_reg = messages.NewRegistration(**dict(reg))
         self.regr = messages.RegistrationResource(
             body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1',
-            new_authzr_uri='https://www.letsencrypt-demo.org/acme/new-reg',
             terms_of_service='https://www.letsencrypt-demo.org/tos')
 
         # Authorization
@@ -72,8 +73,7 @@
                 typ=messages.IDENTIFIER_FQDN, value='example.com'),
             challenges=(challb,), combinations=None)
         self.authzr = messages.AuthorizationResource(
-            body=self.authz, uri=authzr_uri,
-            new_cert_uri='https://www.letsencrypt-demo.org/acme/new-cert')
+            body=self.authz, uri=authzr_uri)
 
         # Request issuance
         self.certr = messages.CertificateResource(
@@ -98,18 +98,12 @@
         self.response.json.return_value = self.regr.body.to_json()
         self.response.headers['Location'] = self.regr.uri
         self.response.links.update({
-            'next': {'url': self.regr.new_authzr_uri},
             'terms-of-service': {'url': self.regr.terms_of_service},
         })
 
         self.assertEqual(self.regr, self.client.register(self.new_reg))
         # TODO: test POST call arguments
 
-    def test_register_missing_next(self):
-        self.response.status_code = http_client.CREATED
-        self.assertRaises(
-            errors.ClientError, self.client.register, self.new_reg)
-
     def test_update_registration(self):
         # "Instance of 'Field' has no to_json/update member" bug:
         # pylint: disable=no-member
@@ -121,8 +115,6 @@
         # TODO: split here and separate test
         self.response.json.return_value = self.regr.body.update(
             contact=()).to_json()
-        self.assertRaises(
-            errors.UnexpectedUpdate, self.client.update_registration, 
self.regr)
 
     def test_deactivate_account(self):
         self.response.headers['Location'] = self.regr.uri
@@ -130,25 +122,10 @@
         self.assertEqual(self.regr,
                          self.client.deactivate_registration(self.regr))
 
-    def test_deactivate_account_bad_registration_returned(self):
-        self.response.headers['Location'] = self.regr.uri
-        self.response.json.return_value = "some wrong registration thing"
-        self.assertRaises(
-            errors.UnexpectedUpdate,
-            self.client.deactivate_registration,
-            self.regr)
-
     def test_query_registration(self):
         self.response.json.return_value = self.regr.body.to_json()
         self.assertEqual(self.regr, self.client.query_registration(self.regr))
 
-    def test_query_registration_updates_new_authzr_uri(self):
-        self.response.json.return_value = self.regr.body.to_json()
-        self.response.links = {'next': {'url': 'UPDATED'}}
-        self.assertEqual(
-            'UPDATED',
-            self.client.query_registration(self.regr).new_authzr_uri)
-
     def test_agree_to_tos(self):
         self.client.update_registration = mock.Mock()
         self.client.agree_to_tos(self.regr)
@@ -159,9 +136,6 @@
         self.response.status_code = http_client.CREATED
         self.response.headers['Location'] = self.authzr.uri
         self.response.json.return_value = self.authz.to_json()
-        self.response.links = {
-            'next': {'url': self.authzr.new_cert_uri},
-        }
 
     def test_request_challenges(self):
         self._prepare_response_for_request_challenges()
@@ -170,10 +144,18 @@
             self.directory.new_authz,
             messages.NewAuthorization(identifier=self.identifier))
 
+    def test_request_challenges_deprecated_arg(self):
+        self._prepare_response_for_request_challenges()
+        self.client.request_challenges(self.identifier, new_authzr_uri="hi")
+        self.net.post.assert_called_once_with(
+            self.directory.new_authz,
+            messages.NewAuthorization(identifier=self.identifier))
+
     def test_request_challenges_custom_uri(self):
         self._prepare_response_for_request_challenges()
-        self.client.request_challenges(self.identifier, 'URI')
-        self.net.post.assert_called_once_with('URI', mock.ANY)
+        self.client.request_challenges(self.identifier)
+        self.net.post.assert_called_once_with(
+            'https://www.letsencrypt-demo.org/acme/new-authz', mock.ANY)
 
     def test_request_challenges_unexpected_update(self):
         self._prepare_response_for_request_challenges()
@@ -181,12 +163,7 @@
             identifier=self.identifier.update(value='foo')).to_json()
         self.assertRaises(
             errors.UnexpectedUpdate, self.client.request_challenges,
-            self.identifier, self.authzr.uri)
-
-    def test_request_challenges_missing_next(self):
-        self.response.status_code = http_client.CREATED
-        self.assertRaises(errors.ClientError, self.client.request_challenges,
-                          self.identifier)
+            self.identifier)
 
     def test_request_domain_challenges(self):
         self.client.request_challenges = mock.MagicMock()
@@ -194,12 +171,6 @@
             self.client.request_challenges(self.identifier),
             self.client.request_domain_challenges('example.com'))
 
-    def test_request_domain_challenges_custom_uri(self):
-        self.client.request_challenges = mock.MagicMock()
-        self.assertEqual(
-            self.client.request_challenges(self.identifier, 'URI'),
-            self.client.request_domain_challenges('example.com', 'URI'))
-
     def test_answer_challenge(self):
         self.response.links['up'] = {'url': self.challr.authzr_uri}
         self.response.json.return_value = self.challr.body.to_json()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/crypto_util.py 
new/acme-0.14.0/acme/crypto_util.py
--- old/acme-0.12.0/acme/crypto_util.py 2017-03-02 23:01:28.000000000 +0100
+++ new/acme-0.14.0/acme/crypto_util.py 2017-05-05 01:45:16.000000000 +0200
@@ -23,7 +23,7 @@
 # https://www.openssl.org/docs/ssl/SSLv23_method.html). _serve_sni
 # should be changed to use "set_options" to disable SSLv2 and SSLv3,
 # in case it's used for things other than probing/serving!
-_DEFAULT_TLSSNI01_SSL_METHOD = OpenSSL.SSL.SSLv23_METHOD
+_DEFAULT_TLSSNI01_SSL_METHOD = OpenSSL.SSL.SSLv23_METHOD  # type: ignore
 
 
 class SSLSocket(object):  # pylint: disable=too-few-public-methods
@@ -149,6 +149,36 @@
             raise errors.Error(error)
     return client_ssl.get_peer_certificate()
 
+def make_csr(private_key_pem, domains, must_staple=False):
+    """Generate a CSR containing a list of domains as subjectAltNames.
+
+    :param buffer private_key_pem: Private key, in PEM PKCS#8 format.
+    :param list domains: List of DNS names to include in subjectAltNames of 
CSR.
+    :param bool must_staple: Whether to include the TLS Feature extension (aka
+        OCSP Must Staple: https://tools.ietf.org/html/rfc7633).
+    :returns: buffer PEM-encoded Certificate Signing Request.
+    """
+    private_key = OpenSSL.crypto.load_privatekey(
+        OpenSSL.crypto.FILETYPE_PEM, private_key_pem)
+    csr = OpenSSL.crypto.X509Req()
+    extensions = [
+        OpenSSL.crypto.X509Extension(
+            b'subjectAltName',
+            critical=False,
+            value=', '.join('DNS:' + d for d in domains).encode('ascii')
+        ),
+    ]
+    if must_staple:
+        extensions.append(OpenSSL.crypto.X509Extension(
+            b"1.3.6.1.5.5.7.1.24",
+            critical=False,
+            value=b"DER:30:03:02:01:05"))
+    csr.add_extensions(extensions)
+    csr.set_pubkey(private_key)
+    csr.set_version(2)
+    csr.sign(private_key, 'sha256')
+    return OpenSSL.crypto.dump_certificate_request(
+        OpenSSL.crypto.FILETYPE_PEM, csr)
 
 def _pyopenssl_cert_or_req_san(cert_or_req):
     """Get Subject Alternative Names from certificate or CSR using pyOpenSSL.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/crypto_util_test.py 
new/acme-0.14.0/acme/crypto_util_test.py
--- old/acme-0.12.0/acme/crypto_util_test.py    2017-03-02 23:01:28.000000000 
+0100
+++ new/acme-0.14.0/acme/crypto_util_test.py    2017-05-05 01:45:16.000000000 
+0200
@@ -6,7 +6,7 @@
 import unittest
 
 import six
-from six.moves import socketserver  # pylint: disable=import-error
+from six.moves import socketserver  #type: ignore  # pylint: 
disable=import-error
 
 import OpenSSL
 
@@ -151,6 +151,53 @@
             self.serial_num.append(cert.get_serial_number())
         self.assertTrue(len(set(self.serial_num)) > 1)
 
+class MakeCSRTest(unittest.TestCase):
+    """Test for standalone functions."""
+
+    @classmethod
+    def _call_with_key(cls, *args, **kwargs):
+        privkey = OpenSSL.crypto.PKey()
+        privkey.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
+        privkey_pem = 
OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey)
+        from acme.crypto_util import make_csr
+        return make_csr(privkey_pem, *args, **kwargs)
+
+    def test_make_csr(self):
+        csr_pem = self._call_with_key(["a.example", "b.example"])
+        self.assertTrue(b'--BEGIN CERTIFICATE REQUEST--' in csr_pem)
+        self.assertTrue(b'--END CERTIFICATE REQUEST--' in csr_pem)
+        csr = OpenSSL.crypto.load_certificate_request(
+            OpenSSL.crypto.FILETYPE_PEM, csr_pem)
+        # In pyopenssl 0.13 (used with TOXENV=py26-oldest and py27-oldest), csr
+        # objects don't have a get_extensions() method, so we skip this test if
+        # the method isn't available.
+        if hasattr(csr, 'get_extensions'):
+            self.assertEquals(len(csr.get_extensions()), 1)
+            self.assertEquals(csr.get_extensions()[0].get_data(),
+                OpenSSL.crypto.X509Extension(
+                    b'subjectAltName',
+                    critical=False,
+                    value=b'DNS:a.example, DNS:b.example',
+                ).get_data(),
+            )
+
+    def test_make_csr_must_staple(self):
+        csr_pem = self._call_with_key(["a.example"], must_staple=True)
+        csr = OpenSSL.crypto.load_certificate_request(
+            OpenSSL.crypto.FILETYPE_PEM, csr_pem)
+
+        # In pyopenssl 0.13 (used with TOXENV=py26-oldest and py27-oldest), csr
+        # objects don't have a get_extensions() method, so we skip this test if
+        # the method isn't available.
+        if hasattr(csr, 'get_extensions'):
+            self.assertEquals(len(csr.get_extensions()), 2)
+            # NOTE: Ideally we would filter by the TLS Feature OID, but
+            # OpenSSL.crypto.X509Extension doesn't give us the extension's raw 
OID,
+            # and the shortname field is just "UNDEF"
+            must_staple_exts = [e for e in csr.get_extensions()
+                if e.get_data() == b"0\x03\x02\x01\x05"]
+            self.assertEqual(len(must_staple_exts), 1,
+                "Expected exactly one Must Staple extension")
 
 if __name__ == '__main__':
     unittest.main()  # pragma: no cover
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/jose/json_util.py 
new/acme-0.14.0/acme/jose/json_util.py
--- old/acme-0.12.0/acme/jose/json_util.py      2017-03-02 23:01:28.000000000 
+0100
+++ new/acme-0.14.0/acme/jose/json_util.py      2017-05-05 01:45:16.000000000 
+0200
@@ -267,7 +267,7 @@
 
         if missing:
             raise errors.DeserializationError(
-                'The following field are required: {0}'.format(
+                'The following fields are required: {0}'.format(
                     ','.join(missing)))
 
     @classmethod
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/jose/jwa.py 
new/acme-0.14.0/acme/jose/jwa.py
--- old/acme-0.12.0/acme/jose/jwa.py    2017-03-02 23:01:28.000000000 +0100
+++ new/acme-0.14.0/acme/jose/jwa.py    2017-05-05 01:45:16.000000000 +0200
@@ -9,9 +9,9 @@
 
 import cryptography.exceptions
 from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import hashes
-from cryptography.hazmat.primitives import hmac
-from cryptography.hazmat.primitives.asymmetric import padding
+from cryptography.hazmat.primitives import hashes  # type: ignore
+from cryptography.hazmat.primitives import hmac  # type: ignore
+from cryptography.hazmat.primitives.asymmetric import padding  # type: ignore
 
 from acme.jose import errors
 from acme.jose import interfaces
@@ -28,9 +28,9 @@
     """JSON Web Algorithm."""
 
 
-class JWASignature(JWA, collections.Hashable):
+class JWASignature(JWA, collections.Hashable):  # type: ignore
     """JSON Web Signature Algorithm."""
-    SIGNATURES = {}
+    SIGNATURES = {}  # type: dict
 
     def __init__(self, name):
         self.name = name
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/jose/jwk.py 
new/acme-0.14.0/acme/jose/jwk.py
--- old/acme-0.12.0/acme/jose/jwk.py    2017-03-02 23:01:28.000000000 +0100
+++ new/acme-0.14.0/acme/jose/jwk.py    2017-05-05 01:45:16.000000000 +0200
@@ -6,9 +6,9 @@
 
 import cryptography.exceptions
 from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives import hashes  # type: ignore
 from cryptography.hazmat.primitives import serialization
-from cryptography.hazmat.primitives.asymmetric import ec
+from cryptography.hazmat.primitives.asymmetric import ec  # type: ignore
 from cryptography.hazmat.primitives.asymmetric import rsa
 
 import six
@@ -25,8 +25,8 @@
     # pylint: disable=too-few-public-methods
     """JSON Web Key."""
     type_field_name = 'kty'
-    TYPES = {}
-    cryptography_key_types = ()
+    TYPES = {}  # type: dict
+    cryptography_key_types = ()  # type: tuple
     """Subclasses should override."""
 
     required = NotImplemented
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/jose/jws.py 
new/acme-0.14.0/acme/jose/jws.py
--- old/acme-0.12.0/acme/jose/jws.py    2017-03-02 23:01:28.000000000 +0100
+++ new/acme-0.14.0/acme/jose/jws.py    2017-05-05 01:45:16.000000000 +0200
@@ -121,12 +121,12 @@
 
     # x5c does NOT use JOSE Base64 (4.1.6)
 
-    @x5c.encoder
+    @x5c.encoder  # type: ignore
     def x5c(value):  # pylint: disable=missing-docstring,no-self-argument
         return [base64.b64encode(OpenSSL.crypto.dump_certificate(
             OpenSSL.crypto.FILETYPE_ASN1, cert.wrapped)) for cert in value]
 
-    @x5c.decoder
+    @x5c.decoder  # type: ignore
     def x5c(value):  # pylint: disable=missing-docstring,no-self-argument
         try:
             return tuple(util.ComparableX509(OpenSSL.crypto.load_certificate(
@@ -157,12 +157,12 @@
         'signature', decoder=json_util.decode_b64jose,
         encoder=json_util.encode_b64jose)
 
-    @protected.encoder
+    @protected.encoder  # type: ignore
     def protected(value):  # pylint: disable=missing-docstring,no-self-argument
         # wrong type guess (Signature, not bytes) | pylint: disable=no-member
         return json_util.encode_b64jose(value.encode('utf-8'))
 
-    @protected.decoder
+    @protected.decoder  # type: ignore
     def protected(value):  # pylint: disable=missing-docstring,no-self-argument
         return json_util.decode_b64jose(value).decode('utf-8')
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/jose/util.py 
new/acme-0.14.0/acme/jose/util.py
--- old/acme-0.12.0/acme/jose/util.py   2017-03-02 23:01:28.000000000 +0100
+++ new/acme-0.14.0/acme/jose/util.py   2017-05-05 01:45:16.000000000 +0200
@@ -134,7 +134,7 @@
             return hash((self.__class__, pub.n, pub.e))
 
 
-class ImmutableMap(collections.Mapping, collections.Hashable):
+class ImmutableMap(collections.Mapping, collections.Hashable):  # type: ignore
     # pylint: disable=too-few-public-methods
     """Immutable key to value mapping with attribute access."""
 
@@ -180,7 +180,7 @@
             for key, value in six.iteritems(self)))
 
 
-class frozendict(collections.Mapping, collections.Hashable):
+class frozendict(collections.Mapping, collections.Hashable):  # type: ignore
     # pylint: disable=invalid-name,too-few-public-methods
     """Frozen dictionary."""
     __slots__ = ('_items', '_keys')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/jws.py new/acme-0.14.0/acme/jws.py
--- old/acme-0.12.0/acme/jws.py 2017-03-02 23:01:28.000000000 +0100
+++ new/acme-0.14.0/acme/jws.py 2017-05-05 01:45:16.000000000 +0200
@@ -1,14 +1,18 @@
-"""ACME JOSE JWS."""
+"""ACME-specific JWS.
+
+The JWS implementation in acme.jose only implements the base JOSE standard. In
+order to support the new header fields defined in ACME, this module defines 
some
+ACME-specific classes that layer on top of acme.jose.
+"""
 from acme import jose
 
 
 class Header(jose.Header):
-    """ACME JOSE Header.
-
-    .. todo:: Implement ``acmePath``.
-
+    """ACME-specific JOSE Header. Implements nonce, kid, and url.
     """
     nonce = jose.Field('nonce', omitempty=True, encoder=jose.encode_b64jose)
+    kid = jose.Field('kid', omitempty=True)
+    url = jose.Field('url', omitempty=True)
 
     @nonce.decoder
     def nonce(value):  # pylint: disable=missing-docstring,no-self-argument
@@ -20,7 +24,7 @@
 
 
 class Signature(jose.Signature):
-    """ACME Signature."""
+    """ACME-specific Signature. Uses ACME-specific Header for customer 
fields."""
     __slots__ = jose.Signature._orig_slots  # pylint: disable=no-member
 
     # TODO: decoder/encoder should accept cls? Otherwise, subclassing
@@ -34,11 +38,17 @@
 
 
 class JWS(jose.JWS):
-    """ACME JWS."""
+    """ACME-specific JWS. Includes none, url, and kid in protected header."""
     signature_cls = Signature
     __slots__ = jose.JWS._orig_slots  # pylint: disable=no-member
 
     @classmethod
-    def sign(cls, payload, key, alg, nonce):  # pylint: 
disable=arguments-differ
+    # pylint: disable=arguments-differ,too-many-arguments
+    def sign(cls, payload, key, alg, nonce, url=None, kid=None):
+        # Per ACME spec, jwk and kid are mutually exclusive, so only include a
+        # jwk field if kid is not provided.
+        include_jwk = kid is None
         return super(JWS, cls).sign(payload, key=key, alg=alg,
-                                    protect=frozenset(['nonce']), nonce=nonce)
+                                    protect=frozenset(['nonce', 'url', 'kid']),
+                                    nonce=nonce, url=url, kid=kid,
+                                    include_jwk=include_jwk)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/jws_test.py 
new/acme-0.14.0/acme/jws_test.py
--- old/acme-0.12.0/acme/jws_test.py    2017-03-02 23:01:28.000000000 +0100
+++ new/acme-0.14.0/acme/jws_test.py    2017-05-05 01:45:16.000000000 +0200
@@ -37,16 +37,30 @@
         self.privkey = KEY
         self.pubkey = self.privkey.public_key()
         self.nonce = jose.b64encode(b'Nonce')
+        self.url = 'hi'
+        self.kid = 'baaaaa'
 
-    def test_it(self):
+    def test_kid_serialize(self):
         from acme.jws import JWS
         jws = JWS.sign(payload=b'foo', key=self.privkey,
-                       alg=jose.RS256, nonce=self.nonce)
+                       alg=jose.RS256, nonce=self.nonce,
+                       url=self.url, kid=self.kid)
         self.assertEqual(jws.signature.combined.nonce, self.nonce)
+        self.assertEqual(jws.signature.combined.url, self.url)
+        self.assertEqual(jws.signature.combined.kid, self.kid)
+        self.assertEqual(jws.signature.combined.jwk, None)
         # TODO: check that nonce is in protected header
 
         self.assertEqual(jws, JWS.from_json(jws.to_json()))
 
+    def test_jwk_serialize(self):
+        from acme.jws import JWS
+        jws = JWS.sign(payload=b'foo', key=self.privkey,
+                       alg=jose.RS256, nonce=self.nonce,
+                       url=self.url)
+        self.assertEqual(jws.signature.combined.kid, None)
+        self.assertEqual(jws.signature.combined.jwk, self.pubkey)
+
 
 if __name__ == '__main__':
     unittest.main()  # pragma: no cover
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/messages.py 
new/acme-0.14.0/acme/messages.py
--- old/acme-0.12.0/acme/messages.py    2017-03-02 23:01:28.000000000 +0100
+++ new/acme-0.14.0/acme/messages.py    2017-05-05 01:45:16.000000000 +0200
@@ -98,7 +98,7 @@
             if part is not None)
 
 
-class _Constant(jose.JSONDeSerializable, collections.Hashable):
+class _Constant(jose.JSONDeSerializable, collections.Hashable):  # type: ignore
     """ACME constant."""
     __slots__ = ('name',)
     POSSIBLE_NAMES = NotImplemented
@@ -132,7 +132,7 @@
 
 class Status(_Constant):
     """ACME "status" field."""
-    POSSIBLE_NAMES = {}
+    POSSIBLE_NAMES = {}  # type: dict
 STATUS_UNKNOWN = Status('unknown')
 STATUS_PENDING = Status('pending')
 STATUS_PROCESSING = Status('processing')
@@ -143,7 +143,7 @@
 
 class IdentifierType(_Constant):
     """ACME identifier type."""
-    POSSIBLE_NAMES = {}
+    POSSIBLE_NAMES = {}  # type: dict
 IDENTIFIER_FQDN = IdentifierType('dns')  # IdentifierDNS in Boulder
 
 
@@ -161,7 +161,7 @@
 class Directory(jose.JSONDeSerializable):
     """Directory."""
 
-    _REGISTERED_TYPES = {}
+    _REGISTERED_TYPES = {}  # type: dict
 
     class Meta(jose.JSONObjectWithFields):
         """Directory Meta."""
@@ -191,7 +191,7 @@
         try:
             return self[name.replace('_', '-')]
         except KeyError as error:
-            raise AttributeError(str(error))
+            raise AttributeError(str(error) + ': ' + name)
 
     def __getitem__(self, name):
         try:
@@ -237,10 +237,6 @@
     :ivar tuple contact: Contact information following ACME spec,
         `tuple` of `unicode`.
     :ivar unicode agreement:
-    :ivar unicode authorizations: URI where
-        `messages.Registration.Authorizations` can be found.
-    :ivar unicode certificates: URI where
-        `messages.Registration.Certificates` can be found.
 
     """
     # on new-reg key server ignores 'key' and populates it based on
@@ -248,26 +244,8 @@
     key = jose.Field('key', omitempty=True, decoder=jose.JWK.from_json)
     contact = jose.Field('contact', omitempty=True, default=())
     agreement = jose.Field('agreement', omitempty=True)
-    authorizations = jose.Field('authorizations', omitempty=True)
-    certificates = jose.Field('certificates', omitempty=True)
     status = jose.Field('status', omitempty=True)
 
-    class Authorizations(jose.JSONObjectWithFields):
-        """Authorizations granted to Account in the process of registration.
-
-        :ivar tuple authorizations: URIs to Authorization Resources.
-
-        """
-        authorizations = jose.Field('authorizations')
-
-    class Certificates(jose.JSONObjectWithFields):
-        """Certificates granted to Account in the process of registration.
-
-        :ivar tuple certificates: URIs to Certificate Resources.
-
-        """
-        certificates = jose.Field('certificates')
-
     phone_prefix = 'tel:'
     email_prefix = 'mailto:'
 
@@ -315,12 +293,12 @@
     """Registration Resource.
 
     :ivar acme.messages.Registration body:
-    :ivar unicode new_authzr_uri: URI found in the 'next' ``Link`` header
+    :ivar unicode new_authzr_uri: Deprecated. Do not use.
     :ivar unicode terms_of_service: URL for the CA TOS.
 
     """
     body = jose.Field('body', decoder=Registration.from_json)
-    new_authzr_uri = jose.Field('new_authzr_uri')
+    new_authzr_uri = jose.Field('new_authzr_uri', omitempty=True)
     terms_of_service = jose.Field('terms_of_service', omitempty=True)
 
 
@@ -425,11 +403,11 @@
     """Authorization Resource.
 
     :ivar acme.messages.Authorization body:
-    :ivar unicode new_cert_uri: URI found in the 'next' ``Link`` header
+    :ivar unicode new_cert_uri: Deprecated. Do not use.
 
     """
     body = jose.Field('body', decoder=Authorization.from_json)
-    new_cert_uri = jose.Field('new_cert_uri')
+    new_cert_uri = jose.Field('new_cert_uri', omitempty=True)
 
 
 @Directory.register
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/messages_test.py 
new/acme-0.14.0/acme/messages_test.py
--- old/acme-0.12.0/acme/messages_test.py       2017-03-02 23:01:28.000000000 
+0100
+++ new/acme-0.14.0/acme/messages_test.py       2017-05-05 01:45:16.000000000 
+0200
@@ -170,8 +170,7 @@
 
         from acme.messages import Registration
         self.reg = Registration(key=key, contact=contact, agreement=agreement)
-        self.reg_none = Registration(authorizations='uri/authorizations',
-                                     certificates='uri/certificates')
+        self.reg_none = Registration()
 
         self.jobj_to = {
             'contact': contact,
@@ -225,14 +224,12 @@
         from acme.messages import RegistrationResource
         self.regr = RegistrationResource(
             body=mock.sentinel.body, uri=mock.sentinel.uri,
-            new_authzr_uri=mock.sentinel.new_authzr_uri,
             terms_of_service=mock.sentinel.terms_of_service)
 
     def test_to_partial_json(self):
         self.assertEqual(self.regr.to_json(), {
             'body': mock.sentinel.body,
             'uri': mock.sentinel.uri,
-            'new_authzr_uri': mock.sentinel.new_authzr_uri,
             'terms_of_service': mock.sentinel.terms_of_service,
         })
 
@@ -346,9 +343,7 @@
         from acme.messages import AuthorizationResource
         authzr = AuthorizationResource(
             uri=mock.sentinel.uri,
-            body=mock.sentinel.body,
-            new_cert_uri=mock.sentinel.new_cert_uri,
-        )
+            body=mock.sentinel.body)
         self.assertTrue(isinstance(authzr, jose.JSONDeSerializable))
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/standalone.py 
new/acme-0.14.0/acme/standalone.py
--- old/acme-0.12.0/acme/standalone.py  2017-03-02 23:01:28.000000000 +0100
+++ new/acme-0.14.0/acme/standalone.py  2017-05-05 01:45:16.000000000 +0200
@@ -6,9 +6,9 @@
 import os
 import sys
 
-from six.moves import BaseHTTPServer  # pylint: disable=import-error
+from six.moves import BaseHTTPServer  # type: ignore  # pylint: 
disable=import-error
 from six.moves import http_client  # pylint: disable=import-error
-from six.moves import socketserver  # pylint: disable=import-error
+from six.moves import socketserver  # type: ignore  # pylint: 
disable=import-error
 
 import OpenSSL
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme/standalone_test.py 
new/acme-0.14.0/acme/standalone_test.py
--- old/acme-0.12.0/acme/standalone_test.py     2017-03-02 23:01:28.000000000 
+0100
+++ new/acme-0.14.0/acme/standalone_test.py     2017-05-05 01:45:16.000000000 
+0200
@@ -7,7 +7,7 @@
 import unittest
 
 from six.moves import http_client  # pylint: disable=import-error
-from six.moves import socketserver  # pylint: disable=import-error
+from six.moves import socketserver  # type: ignore  # pylint: 
disable=import-error
 
 import requests
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme.egg-info/PKG-INFO 
new/acme-0.14.0/acme.egg-info/PKG-INFO
--- old/acme-0.12.0/acme.egg-info/PKG-INFO      2017-03-02 23:01:44.000000000 
+0100
+++ new/acme-0.14.0/acme.egg-info/PKG-INFO      2017-05-05 01:45:31.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: acme
-Version: 0.12.0
+Version: 0.14.0
 Summary: ACME protocol implementation in Python
 Home-page: https://github.com/letsencrypt/letsencrypt
 Author: Certbot Project
@@ -19,5 +19,6 @@
 Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
 Classifier: Topic :: Internet :: WWW/HTTP
 Classifier: Topic :: Security
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/acme.egg-info/requires.txt 
new/acme-0.14.0/acme.egg-info/requires.txt
--- old/acme-0.12.0/acme.egg-info/requires.txt  2017-03-02 23:01:44.000000000 
+0100
+++ new/acme-0.14.0/acme.egg-info/requires.txt  2017-05-05 01:45:31.000000000 
+0200
@@ -1,11 +1,11 @@
 cryptography>=0.8
+mock
 PyOpenSSL>=0.13
 pyrfc3339
 pytz
 requests[security]>=2.10
 setuptools>=1.0
 six
-mock
 
 [dev]
 nose
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/examples/example_client.py 
new/acme-0.14.0/examples/example_client.py
--- old/acme-0.12.0/examples/example_client.py  2017-03-02 23:01:28.000000000 
+0100
+++ new/acme-0.14.0/examples/example_client.py  2017-05-05 01:45:16.000000000 
+0200
@@ -32,8 +32,7 @@
 logging.debug(regr)
 
 authzr = acme.request_challenges(
-    identifier=messages.Identifier(typ=messages.IDENTIFIER_FQDN, value=DOMAIN),
-    new_authzr_uri=regr.new_authzr_uri)
+    identifier=messages.Identifier(typ=messages.IDENTIFIER_FQDN, value=DOMAIN))
 logging.debug(authzr)
 
 authzr, authzr_response = acme.poll(authzr)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.12.0/setup.py new/acme-0.14.0/setup.py
--- old/acme-0.12.0/setup.py    2017-03-02 23:01:28.000000000 +0100
+++ new/acme-0.14.0/setup.py    2017-05-05 01:45:16.000000000 +0200
@@ -4,7 +4,7 @@
 from setuptools import find_packages
 
 
-version = '0.12.0'
+version = '0.14.0'
 
 # Please update tox.ini when modifying dependency version requirements
 install_requires = [
@@ -12,6 +12,7 @@
     # rsa_recover_prime_factors (>=0.8)
     'cryptography>=0.8',
     # Connection.set_tlsext_host_name (>=0.13)
+    'mock',
     'PyOpenSSL>=0.13',
     'pyrfc3339',
     'pytz',
@@ -26,16 +27,12 @@
     'six',
 ]
 
-# env markers in extras_require cause problems with older pip: #517
-# Keep in sync with conditional_requirements.py.
+# env markers cause problems with older pip and setuptools
 if sys.version_info < (2, 7):
     install_requires.extend([
-        # only some distros recognize stdlib argparse as already satisfying
         'argparse',
-        'mock<1.1.0',
+        'ordereddict',
     ])
-else:
-    install_requires.append('mock')
 
 dev_extras = [
     'nose',
@@ -68,6 +65,7 @@
         'Programming Language :: Python :: 3.3',
         'Programming Language :: Python :: 3.4',
         'Programming Language :: Python :: 3.5',
+        'Programming Language :: Python :: 3.6',
         'Topic :: Internet :: WWW/HTTP',
         'Topic :: Security',
     ],



Reply via email to