mirror of
https://github.com/certbot/certbot.git
synced 2026-05-28 04:34:11 -04:00
Merge pull request #8444 from certbot/ecdsa
Integrate the ECDSA certificates feature on master
This commit is contained in:
commit
9ca7f76505
59 changed files with 932 additions and 106 deletions
|
|
@ -154,6 +154,7 @@ Authors
|
|||
* [Luca Olivetti](https://github.com/olivluca)
|
||||
* [Luke Rogers](https://github.com/lukeroge)
|
||||
* [Maarten](https://github.com/mrtndwrd)
|
||||
* [Mads Jensen](https://github.com/atombrella)
|
||||
* [Maikel Martens](https://github.com/krukas)
|
||||
* [Malte Janduda](https://github.com/MalteJ)
|
||||
* [Mantas Mikulėnas](https://github.com/grawity)
|
||||
|
|
@ -213,6 +214,7 @@ Authors
|
|||
* [Richard Barnes](https://github.com/r-barnes)
|
||||
* [Richard Panek](https://github.com/kernelpanek)
|
||||
* [Robert Buchholz](https://github.com/rbu)
|
||||
* [Robert Dailey](https://github.com/pahrohfit)
|
||||
* [Robert Habermann](https://github.com/frennkie)
|
||||
* [Robert Xiao](https://github.com/nneonneo)
|
||||
* [Roland Shoemaker](https://github.com/rolandshoemaker)
|
||||
|
|
|
|||
|
|
@ -108,11 +108,11 @@ class ConstantTest(unittest.TestCase):
|
|||
|
||||
def test_equality(self):
|
||||
const_a_prime = self.MockConstant('a')
|
||||
self.assertFalse(self.const_a == self.const_b)
|
||||
self.assertTrue(self.const_a == const_a_prime)
|
||||
self.assertNotEqual(self.const_a, self.const_b)
|
||||
self.assertEqual(self.const_a, const_a_prime)
|
||||
|
||||
self.assertTrue(self.const_a != self.const_b)
|
||||
self.assertFalse(self.const_a != const_a_prime)
|
||||
self.assertNotEqual(self.const_a, self.const_b)
|
||||
self.assertEqual(self.const_a, const_a_prime)
|
||||
|
||||
|
||||
class DirectoryTest(unittest.TestCase):
|
||||
|
|
|
|||
|
|
@ -1350,10 +1350,10 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
|
||||
# And the actual returned values
|
||||
self.assertEqual(len(vhs), 1)
|
||||
self.assertTrue(vhs[0].name == "certbot.demo")
|
||||
self.assertEqual(vhs[0].name, "certbot.demo")
|
||||
self.assertTrue(vhs[0].ssl)
|
||||
|
||||
self.assertFalse(vhs[0] == self.vh_truth[3])
|
||||
self.assertNotEqual(vhs[0], self.vh_truth[3])
|
||||
|
||||
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl")
|
||||
def test_choose_vhosts_wildcard_no_ssl(self, mock_makessl):
|
||||
|
|
@ -1464,10 +1464,10 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
self.config.parser.aug.match = mock_match
|
||||
vhs = self.config.get_virtual_hosts()
|
||||
self.assertEqual(len(vhs), 2)
|
||||
self.assertTrue(vhs[0] == self.vh_truth[1])
|
||||
self.assertEqual(vhs[0], self.vh_truth[1])
|
||||
# mock_vhost should have replaced the vh_truth[0], because its filepath
|
||||
# isn't a symlink
|
||||
self.assertTrue(vhs[1] == mock_vhost)
|
||||
self.assertEqual(vhs[1], mock_vhost)
|
||||
|
||||
|
||||
class AugeasVhostsTest(util.ApacheTest):
|
||||
|
|
|
|||
|
|
@ -412,9 +412,9 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
|
|||
ancestor=self.block,
|
||||
filepath="/path/to/whatever",
|
||||
metadata=self.metadata)
|
||||
self.assertFalse(self.block == ne_block)
|
||||
self.assertFalse(self.directive == ne_directive)
|
||||
self.assertFalse(self.comment == ne_comment)
|
||||
self.assertNotEqual(self.block, ne_block)
|
||||
self.assertNotEqual(self.directive, ne_directive)
|
||||
self.assertNotEqual(self.comment, ne_comment)
|
||||
|
||||
def test_parsed_paths(self):
|
||||
mock_p = mock.MagicMock(return_value=['/path/file.conf',
|
||||
|
|
|
|||
|
|
@ -27,14 +27,14 @@ class VirtualHostTest(unittest.TestCase):
|
|||
"certbot_apache._internal.obj.Addr(('127.0.0.1', '443'))")
|
||||
|
||||
def test_eq(self):
|
||||
self.assertTrue(self.vhost1b == self.vhost1)
|
||||
self.assertFalse(self.vhost1 == self.vhost2)
|
||||
self.assertEqual(self.vhost1b, self.vhost1)
|
||||
self.assertNotEqual(self.vhost1, self.vhost2)
|
||||
self.assertEqual(str(self.vhost1b), str(self.vhost1))
|
||||
self.assertFalse(self.vhost1b == 1234)
|
||||
self.assertNotEqual(self.vhost1b, 1234)
|
||||
|
||||
def test_ne(self):
|
||||
self.assertTrue(self.vhost1 != self.vhost2)
|
||||
self.assertFalse(self.vhost1 != self.vhost1b)
|
||||
self.assertNotEqual(self.vhost1, self.vhost2)
|
||||
self.assertEqual(self.vhost1, self.vhost1b)
|
||||
|
||||
def test_conflicts(self):
|
||||
from certbot_apache._internal.obj import Addr
|
||||
|
|
@ -128,13 +128,13 @@ class AddrTest(unittest.TestCase):
|
|||
self.assertTrue(self.addr1.conflicts(self.addr2))
|
||||
|
||||
def test_equal(self):
|
||||
self.assertTrue(self.addr1 == self.addr2)
|
||||
self.assertFalse(self.addr == self.addr1)
|
||||
self.assertFalse(self.addr == 123)
|
||||
self.assertEqual(self.addr1, self.addr2)
|
||||
self.assertNotEqual(self.addr, self.addr1)
|
||||
self.assertNotEqual(self.addr, 123)
|
||||
|
||||
def test_not_equal(self):
|
||||
self.assertFalse(self.addr1 != self.addr2)
|
||||
self.assertTrue(self.addr != self.addr1)
|
||||
self.assertEqual(self.addr1, self.addr2)
|
||||
self.assertNotEqual(self.addr, self.addr1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
This directory contains your keys and certificates.
|
||||
|
||||
`privkey.pem` : the private key for your certificate.
|
||||
`fullchain.pem`: the certificate file used in most server software.
|
||||
`chain.pem` : used for OCSP stapling in Nginx >=1.3.7.
|
||||
`cert.pem` : will break many server configurations, and should not be used
|
||||
without reading further documentation (see link below).
|
||||
|
||||
WARNING: DO NOT MOVE OR RENAME THESE FILES!
|
||||
Certbot expects these files to remain in this location in order
|
||||
to function properly!
|
||||
|
||||
We recommend not moving these files. For more information, see the Certbot
|
||||
User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates.
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC2zCCAcOgAwIBAgIIBvrEnbPRYu8wDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
|
||||
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAxMjZjNGIwHhcNMjAxMDEyMjEwNzQw
|
||||
WhcNMjUxMDEyMjEwNzQwWjAjMSEwHwYDVQQDExhjLmVuY3J5cHRpb24tZXhhbXBs
|
||||
ZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARjMhuW0ENPPC33PjB5XsYU
|
||||
CRw640kPQENIDatcTJaENZIZdqKd6rI6jc+lpbmXot7Zi52clJlSJS+V6oDAt2Lh
|
||||
o4HYMIHVMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
|
||||
BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUj7Kd3ENqxlPf8B2bIGhsjydX
|
||||
mPswHwYDVR0jBBgwFoAUEiGxlkRsi+VvcogH5dVD3h1laAcwMQYIKwYBBQUHAQEE
|
||||
JTAjMCEGCCsGAQUFBzABhhVodHRwOi8vMTI3LjAuMC4xOjQwMDIwIwYDVR0RBBww
|
||||
GoIYYy5lbmNyeXB0aW9uLWV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQCl
|
||||
k0JXsa8y7fg41WWMDhw60bPW77O0FtOmTcnhdI5daYNemQVk+Q5EMaBLQ/oGjgXd
|
||||
9QXFzXH1PL904YEnSLt+iTpXn++7rQSNzQsdYqw0neWk4f5pEBiN+WORpb6mwobV
|
||||
ifMtBOkNEHvrJ2Pkci9U1lLwtKD/DSew6QtJU5DSkmH1XdGuMJiubygEIvELtvgq
|
||||
cP9S368ZvPmPGmKaJQXBiuaR8MTjY/Bkr79aXQMjKbf+mpn7h0POCcePk1DY/rm6
|
||||
Da+X16lf0hHyQhSUa7Vgyim6rK1/hlw+Z00i+sQCKD9Ih7kXuuGqfSDC33cfO8Tj
|
||||
o/MXO8lcxkrem5zU5QWP
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDUDCCAjigAwIBAgIIbi787yVrcMAwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVUGViYmxlIFJvb3QgQ0EgMGM1MjI1MCAXDTIwMTAxMjIwMjI0NloYDzIwNTAx
|
||||
MDEyMjEyMjQ2WjAoMSYwJAYDVQQDEx1QZWJibGUgSW50ZXJtZWRpYXRlIENBIDEy
|
||||
NmM0YjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGeVk1BMJraeqRq
|
||||
mJ2+hgso8VOAv2s2CVxUJjIVcn7f2adE8NyTsSQ1brlsnKCUYUw7yLTQH0izLQRB
|
||||
qKVIDFkUqo5/FuTJ2QlfA2EwBL8J7s/7L7vj3L0DiVpwgxPSyFEwdl/Y5y7ofsX5
|
||||
CIhCFcaMAmTIuKLiSfCJjGwkbEMuolm+lO8Mikxxc/JtDVUC479ugU7PU9O09bMH
|
||||
nm+sD6Bgd+KMoPkCCCoeShJS9X3Ziq9HGc7Z6nhM/zirFARt2XkonEdAZ8br01zY
|
||||
MRiY9txhlWQ7mUkOtzOSoEuYJNoUbvMUf0+tNzto26WRyF7dJmh7lTBsYrvAwUTx
|
||||
PzNyst0CAwEAAaOBgzCBgDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYB
|
||||
BQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBIhsZZE
|
||||
bIvlb3KIB+XVQ94dZWgHMB8GA1UdIwQYMBaAFOaKTaXg37vKgRt7d79YOjAoAtJT
|
||||
MA0GCSqGSIb3DQEBCwUAA4IBAQAU2mZii7PH2pkw2lNM0QqPbcW/UYyvFoUeM8Aq
|
||||
uCtsI2s+oxCJTqzfLsA0N8NY4nHLQ5wAlNJfJekngni8hbmJTKU4JFTMe7kLQO8P
|
||||
fJbk0pTzhhHVQw7CVwB6Pwq3u2m/JV+d6xDIDc+AVkuEl19ZJU0rTWyooClfFLZV
|
||||
EdZmEiUtA3PGlxoYwYhoGHYlhFxsoFONhCsBEdN7k7FKtFGVxN7oc5SKmKp0YZTW
|
||||
fcrEtrdNThATO4ymhCC2zh33NI/MT1O74fpaAc2k6LcTl57MKiLfTYX4LTL6v9JG
|
||||
9tlNqjFVRRmzEbtXTPcCb+w9g1VqoOGok7mGXYLTYtShCuvE
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC2zCCAcOgAwIBAgIILlmGtZhUFEwwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
|
||||
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAxMjZjNGIwHhcNMjAxMDEyMjA1MDM0
|
||||
WhcNMjUxMDEyMjA1MDM0WjAjMSEwHwYDVQQDExhjLmVuY3J5cHRpb24tZXhhbXBs
|
||||
ZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARHEzR8JPWrEmpmgM+F2bk5
|
||||
9mT0u6CjzmJG0QpbaqprLiG5NGpW84VQ5TFCrmC4KxYfigCfMhfHRNfFYvNUK3V/
|
||||
o4HYMIHVMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
|
||||
BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU1CsVL+bPnzaxxQ5jUENmQJIO
|
||||
lKwwHwYDVR0jBBgwFoAUEiGxlkRsi+VvcogH5dVD3h1laAcwMQYIKwYBBQUHAQEE
|
||||
JTAjMCEGCCsGAQUFBzABhhVodHRwOi8vMTI3LjAuMC4xOjQwMDIwIwYDVR0RBBww
|
||||
GoIYYy5lbmNyeXB0aW9uLWV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBn
|
||||
2D8loC7pfk28JYpFLr5lmFKJWWmtLGlpsWDj61fVjtTfGKLziJz+MM6il4Y3hIz5
|
||||
58qiFK0ue0M63dIBJ33N+XxSEXon4Q0gy/zRWfH9jtPJ3FwfjkU/RT9PAUClYi0G
|
||||
ptNWnTmgQkNzousbcAtRNXuuShH3856vhUnwkX+xM+cbIDi1JVmFjcGrEEQJ0rUF
|
||||
mv2ZTyfbWbUs3v4rReETi2NVzr1Ql6J+ByNcMvHODzFy3t0L6yelAw2ca1I+c9HU
|
||||
+Z0tnp/ykR7eXNuVLivok8UBf5OC413lh8ZO5g+Bgzh/LdtkUuavg1MYtEX0H6mX
|
||||
9U7y3nVI8WEbPGf+HDeu
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDUDCCAjigAwIBAgIIbi787yVrcMAwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVUGViYmxlIFJvb3QgQ0EgMGM1MjI1MCAXDTIwMTAxMjIwMjI0NloYDzIwNTAx
|
||||
MDEyMjEyMjQ2WjAoMSYwJAYDVQQDEx1QZWJibGUgSW50ZXJtZWRpYXRlIENBIDEy
|
||||
NmM0YjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGeVk1BMJraeqRq
|
||||
mJ2+hgso8VOAv2s2CVxUJjIVcn7f2adE8NyTsSQ1brlsnKCUYUw7yLTQH0izLQRB
|
||||
qKVIDFkUqo5/FuTJ2QlfA2EwBL8J7s/7L7vj3L0DiVpwgxPSyFEwdl/Y5y7ofsX5
|
||||
CIhCFcaMAmTIuKLiSfCJjGwkbEMuolm+lO8Mikxxc/JtDVUC479ugU7PU9O09bMH
|
||||
nm+sD6Bgd+KMoPkCCCoeShJS9X3Ziq9HGc7Z6nhM/zirFARt2XkonEdAZ8br01zY
|
||||
MRiY9txhlWQ7mUkOtzOSoEuYJNoUbvMUf0+tNzto26WRyF7dJmh7lTBsYrvAwUTx
|
||||
PzNyst0CAwEAAaOBgzCBgDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYB
|
||||
BQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBIhsZZE
|
||||
bIvlb3KIB+XVQ94dZWgHMB8GA1UdIwQYMBaAFOaKTaXg37vKgRt7d79YOjAoAtJT
|
||||
MA0GCSqGSIb3DQEBCwUAA4IBAQAU2mZii7PH2pkw2lNM0QqPbcW/UYyvFoUeM8Aq
|
||||
uCtsI2s+oxCJTqzfLsA0N8NY4nHLQ5wAlNJfJekngni8hbmJTKU4JFTMe7kLQO8P
|
||||
fJbk0pTzhhHVQw7CVwB6Pwq3u2m/JV+d6xDIDc+AVkuEl19ZJU0rTWyooClfFLZV
|
||||
EdZmEiUtA3PGlxoYwYhoGHYlhFxsoFONhCsBEdN7k7FKtFGVxN7oc5SKmKp0YZTW
|
||||
fcrEtrdNThATO4ymhCC2zh33NI/MT1O74fpaAc2k6LcTl57MKiLfTYX4LTL6v9JG
|
||||
9tlNqjFVRRmzEbtXTPcCb+w9g1VqoOGok7mGXYLTYtShCuvE
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgNgefv2dad4U1VYEi
|
||||
0WkdHuqywi5QXAe30OwNTTGjhbihRANCAARHEzR8JPWrEmpmgM+F2bk59mT0u6Cj
|
||||
zmJG0QpbaqprLiG5NGpW84VQ5TFCrmC4KxYfigCfMhfHRNfFYvNUK3V/
|
||||
-----END PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
This directory contains your keys and certificates.
|
||||
|
||||
`privkey.pem` : the private key for your certificate.
|
||||
`fullchain.pem`: the certificate file used in most server software.
|
||||
`chain.pem` : used for OCSP stapling in Nginx >=1.3.7.
|
||||
`cert.pem` : will break many server configurations, and should not be used
|
||||
without reading further documentation (see link below).
|
||||
|
||||
WARNING: DO NOT MOVE OR RENAME THESE FILES!
|
||||
Certbot expects these files to remain in this location in order
|
||||
to function properly!
|
||||
|
||||
We recommend not moving these files. For more information, see the Certbot
|
||||
User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates.
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../archive/c.encryption-example.com/cert.pem
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../archive/c.encryption-example.com/chain.pem
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../archive/c.encryption-example.com/fullchain.pem
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../archive/c.encryption-example.com/privkey.pem
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# renew_before_expiry = 30 days
|
||||
version = 1.10.0.dev0
|
||||
archive_dir = sample-config/archive/c.encryption-example.com
|
||||
cert = sample-config/live/c.encryption-example.com/cert.pem
|
||||
privkey = sample-config/live/c.encryption-example.com/privkey.pem
|
||||
chain = sample-config/live/c.encryption-example.com/chain.pem
|
||||
fullchain = sample-config/live/c.encryption-example.com/fullchain.pem
|
||||
|
||||
# Options used in the renewal process
|
||||
[renewalparams]
|
||||
authenticator = apache
|
||||
installer = apache
|
||||
account = 48d6b9e8d767eccf7e4d877d6ffa81e3
|
||||
key_type = ecdsa
|
||||
config_dir = sample-config-ec
|
||||
elliptic_curve = secp256r1
|
||||
manual_public_ip_logging_ok = True
|
||||
|
|
@ -2,6 +2,11 @@
|
|||
import io
|
||||
import os
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
|
||||
try:
|
||||
import grp
|
||||
POSIX_MODE = True
|
||||
|
|
@ -16,6 +21,33 @@ SYSTEM_SID = 'S-1-5-18'
|
|||
ADMINS_SID = 'S-1-5-32-544'
|
||||
|
||||
|
||||
def assert_elliptic_key(key, curve):
|
||||
"""
|
||||
Asserts that the key at the given path is an EC key using the given curve.
|
||||
:param key: path to key
|
||||
:param curve: name of the expected elliptic curve
|
||||
"""
|
||||
with open(key, 'rb') as file:
|
||||
privkey1 = file.read()
|
||||
|
||||
key = load_pem_private_key(data=privkey1, password=None, backend=default_backend())
|
||||
|
||||
assert isinstance(key, EllipticCurvePrivateKey)
|
||||
assert isinstance(key.curve, curve)
|
||||
|
||||
|
||||
def assert_rsa_key(key):
|
||||
"""
|
||||
Asserts that the key at the given path is an RSA key.
|
||||
:param key: path to key
|
||||
"""
|
||||
with open(key, 'rb') as file:
|
||||
privkey1 = file.read()
|
||||
|
||||
key = load_pem_private_key(data=privkey1, password=None, backend=default_backend())
|
||||
assert isinstance(key, RSAPrivateKey)
|
||||
|
||||
|
||||
def assert_hook_execution(probe_path, probe_content):
|
||||
"""
|
||||
Assert that a certbot hook has been executed
|
||||
|
|
|
|||
|
|
@ -9,12 +9,15 @@ import shutil
|
|||
import subprocess
|
||||
import time
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import SECP256R1, SECP384R1
|
||||
from cryptography.x509 import NameOID
|
||||
|
||||
import pytest
|
||||
|
||||
from certbot_integration_tests.certbot_tests import context as certbot_context
|
||||
from certbot_integration_tests.certbot_tests.assertions import assert_cert_count_for_lineage
|
||||
from certbot_integration_tests.certbot_tests.assertions import assert_elliptic_key
|
||||
from certbot_integration_tests.certbot_tests.assertions import assert_rsa_key
|
||||
from certbot_integration_tests.certbot_tests.assertions import assert_equals_group_owner
|
||||
from certbot_integration_tests.certbot_tests.assertions import assert_equals_group_permissions
|
||||
from certbot_integration_tests.certbot_tests.assertions import assert_equals_world_read_permissions
|
||||
|
|
@ -289,7 +292,7 @@ def test_renew_with_changed_private_key_complexity(context):
|
|||
assert_cert_count_for_lineage(context.config_dir, certname, 1)
|
||||
|
||||
context.certbot(['renew'])
|
||||
|
||||
|
||||
assert_cert_count_for_lineage(context.config_dir, certname, 2)
|
||||
key2 = join(context.config_dir, 'archive', certname, 'privkey2.pem')
|
||||
assert os.stat(key2).st_size > 3000
|
||||
|
|
@ -421,20 +424,93 @@ def test_reuse_key(context):
|
|||
assert len({cert1, cert2, cert3}) == 3
|
||||
|
||||
|
||||
def test_incorrect_key_type(context):
|
||||
with pytest.raises(subprocess.CalledProcessError):
|
||||
context.certbot(['--key-type="failwhale"'])
|
||||
|
||||
|
||||
def test_ecdsa(context):
|
||||
"""Test certificate issuance with ECDSA key."""
|
||||
"""Test issuance for ECDSA CSR based request (legacy supported mode)."""
|
||||
key_path = join(context.workspace, 'privkey-p384.pem')
|
||||
csr_path = join(context.workspace, 'csr-p384.der')
|
||||
cert_path = join(context.workspace, 'cert-p384.pem')
|
||||
chain_path = join(context.workspace, 'chain-p384.pem')
|
||||
|
||||
misc.generate_csr([context.get_domain('ecdsa')], key_path, csr_path, key_type=misc.ECDSA_KEY_TYPE)
|
||||
context.certbot(['auth', '--csr', csr_path, '--cert-path', cert_path, '--chain-path', chain_path])
|
||||
misc.generate_csr(
|
||||
[context.get_domain('ecdsa')],
|
||||
key_path, csr_path,
|
||||
key_type=misc.ECDSA_KEY_TYPE
|
||||
)
|
||||
context.certbot([
|
||||
'auth', '--csr', csr_path, '--cert-path', cert_path,
|
||||
'--chain-path', chain_path,
|
||||
])
|
||||
|
||||
certificate = misc.read_certificate(cert_path)
|
||||
assert 'ASN1 OID: secp384r1' in certificate
|
||||
|
||||
|
||||
def test_default_key_type(context):
|
||||
"""Test default key type is RSA"""
|
||||
certname = context.get_domain('renew')
|
||||
context.certbot([
|
||||
'certonly',
|
||||
'--cert-name', certname, '-d', certname
|
||||
])
|
||||
filename = join(context.config_dir, 'archive/{0}/privkey1.pem').format(certname)
|
||||
assert_rsa_key(filename)
|
||||
|
||||
|
||||
def test_default_curve_type(context):
|
||||
"""test that the curve used when not specifying any is secp256r1"""
|
||||
certname = context.get_domain('renew')
|
||||
context.certbot([
|
||||
'--key-type', 'ecdsa', '--cert-name', certname, '-d', certname
|
||||
])
|
||||
key1 = join(context.config_dir, 'archive/{0}/privkey1.pem'.format(certname))
|
||||
assert_elliptic_key(key1, SECP256R1)
|
||||
|
||||
|
||||
def test_renew_with_ec_keys(context):
|
||||
"""Test proper renew with updated private key complexity."""
|
||||
certname = context.get_domain('renew')
|
||||
context.certbot([
|
||||
'certonly',
|
||||
'--cert-name', certname,
|
||||
'--key-type', 'ecdsa', '--elliptic-curve', 'secp256r1',
|
||||
'--force-renewal', '-d', certname,
|
||||
])
|
||||
|
||||
key1 = join(context.config_dir, "archive", certname, 'privkey1.pem')
|
||||
assert 200 < os.stat(key1).st_size < 250 # ec keys of 256 bits are ~225 bytes
|
||||
assert_elliptic_key(key1, SECP256R1)
|
||||
assert_cert_count_for_lineage(context.config_dir, certname, 1)
|
||||
|
||||
context.certbot(['renew', '--elliptic-curve', 'secp384r1'])
|
||||
|
||||
assert_cert_count_for_lineage(context.config_dir, certname, 2)
|
||||
key2 = join(context.config_dir, 'archive', certname, 'privkey2.pem')
|
||||
assert_elliptic_key(key2, SECP384R1)
|
||||
assert 280 < os.stat(key2).st_size < 320 # ec keys of 384 bits are ~310 bytes
|
||||
|
||||
# We expect here that the command will fail because without --key-type specified,
|
||||
# Certbot must error out to prevent changing an existing certificate key type,
|
||||
# without explicit user consent (by specifying both --cert-name and --key-type).
|
||||
with pytest.raises(subprocess.CalledProcessError):
|
||||
context.certbot([
|
||||
'certonly',
|
||||
'--force-renewal',
|
||||
'-d', certname
|
||||
])
|
||||
|
||||
# We expect that the previous behavior of requiring both --cert-name and
|
||||
# --key-type to be set to not apply to the renew subcommand.
|
||||
context.certbot(['renew', '--force-renewal', '--key-type', 'rsa'])
|
||||
assert_cert_count_for_lineage(context.config_dir, certname, 3)
|
||||
key3 = join(context.config_dir, 'archive', certname, 'privkey3.pem')
|
||||
assert_rsa_key(key3)
|
||||
|
||||
|
||||
def test_ocsp_must_staple(context):
|
||||
"""Test that OCSP Must-Staple is correctly set in the generated certificate."""
|
||||
if context.acme_server == 'pebble':
|
||||
|
|
@ -658,4 +734,4 @@ def test_preferred_chain(context):
|
|||
|
||||
with open(conf_path, 'r') as f:
|
||||
assert 'preferred_chain = {}'.format(requested) in f.read(), \
|
||||
'Expected preferred_chain to be set in renewal config'
|
||||
'Expected preferred_chain to be set in renewal config'
|
||||
|
|
|
|||
|
|
@ -280,7 +280,11 @@ def load_sample_data_path(workspace):
|
|||
|
||||
if os.name == 'nt':
|
||||
# Fix the symlinks on Windows if GIT is not configured to create them upon checkout
|
||||
for lineage in ['a.encryption-example.com', 'b.encryption-example.com']:
|
||||
for lineage in [
|
||||
'a.encryption-example.com',
|
||||
'b.encryption-example.com',
|
||||
'c.encryption-example.com',
|
||||
]:
|
||||
current_live = os.path.join(copied, 'live', lineage)
|
||||
for name in os.listdir(current_live):
|
||||
if name != 'README':
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ class RFC2136ClientTest(unittest.TestCase):
|
|||
# _find_domain | pylint: disable=protected-access
|
||||
domain = self.rfc2136_client._find_domain('foo.bar.'+DOMAIN)
|
||||
|
||||
self.assertTrue(domain == DOMAIN)
|
||||
self.assertEqual(domain, DOMAIN)
|
||||
|
||||
def test_find_domain_wraps_errors(self):
|
||||
# _query_soa | pylint: disable=protected-access
|
||||
|
|
|
|||
|
|
@ -842,7 +842,7 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
self.config.recovery_routine()
|
||||
self.config.revert_challenge_config()
|
||||
self.config.rollback_checkpoints()
|
||||
self.assertTrue(mock_parser_load.call_count == 3)
|
||||
self.assertEqual(mock_parser_load.call_count, 3)
|
||||
|
||||
def test_choose_vhosts_wildcard(self):
|
||||
# pylint: disable=protected-access
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class AddrTest(unittest.TestCase):
|
|||
new_addr1 = Addr.fromstring("192.168.1.1 spdy")
|
||||
self.assertEqual(self.addr1, new_addr1)
|
||||
self.assertNotEqual(self.addr1, self.addr2)
|
||||
self.assertFalse(self.addr1 == 3333)
|
||||
self.assertNotEqual(self.addr1, 3333)
|
||||
|
||||
def test_equivalent_any_addresses(self):
|
||||
from certbot_nginx._internal.obj import Addr
|
||||
|
|
@ -168,7 +168,7 @@ class VirtualHostTest(unittest.TestCase):
|
|||
|
||||
self.assertEqual(vhost1b, self.vhost1)
|
||||
self.assertEqual(str(vhost1b), str(self.vhost1))
|
||||
self.assertFalse(vhost1b == 1234)
|
||||
self.assertNotEqual(vhost1b, 1234)
|
||||
|
||||
def test_str(self):
|
||||
stringified = '\n'.join(['file: filep', 'addrs: localhost',
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
|
|||
|
||||
* Added timeout to DNS query function calls for dns-rfc2136 plugin.
|
||||
* Confirmation when deleting certificates
|
||||
*
|
||||
* CLI flag `--key-type` has been added to specify 'rsa' or 'ecdsa' (default 'rsa').
|
||||
* CLI flag `--elliptic-curve` has been added which takes an NIST/SECG elliptic curve. Any of
|
||||
`secp256r1`, `secp284r1` and `secp521r1` are accepted values.
|
||||
* The command `certbot certficates` lists the which type of the private key that was used
|
||||
for the private key.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
@ -55,7 +59,7 @@ More details about these changes can be found on our GitHub repo.
|
|||
|
||||
### Added
|
||||
|
||||
* Added the ability to remove email and phone contact information from an account
|
||||
* Added the ability to remove email and phone contact information from an account
|
||||
using `update_account --register-unsafely-without-email`
|
||||
|
||||
### Changed
|
||||
|
|
@ -67,7 +71,7 @@ More details about these changes can be found on our GitHub repo.
|
|||
* The problem causing the Apache plugin in the Certbot snap on ARM systems to
|
||||
fail to load the Augeas library it depends on has been fixed.
|
||||
* The `acme` library can now tell the ACME server to clear contact information by passing an empty
|
||||
`tuple` to the `contact` field of a `Registration` message.
|
||||
`tuple` to the `contact` field of a `Registration` message.
|
||||
* Fixed the `*** stack smashing detected ***` error in the Certbot snap on some systems.
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
|
|
|||
|
|
@ -106,6 +106,8 @@ Current Features
|
|||
* Can get domain-validated (DV) certificates.
|
||||
* Can revoke certificates.
|
||||
* Adjustable RSA key bit-length (2048 (default), 4096, ...).
|
||||
* Adjustable [EC](https://en.wikipedia.org/wiki/Elliptic-curve_cryptography)
|
||||
key (`secp256r1` (default), `secp384r1`, `secp521r1`).
|
||||
* Can optionally install a http -> https redirect, so your site effectively
|
||||
runs https only (Apache only)
|
||||
* Fully automated.
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ def rename_lineage(config):
|
|||
disp.notification("Successfully renamed {0} to {1}."
|
||||
.format(certname, new_certname), pause=False)
|
||||
|
||||
|
||||
def certificates(config):
|
||||
"""Display information about certs configured with Certbot
|
||||
|
||||
|
|
@ -87,6 +88,7 @@ def certificates(config):
|
|||
# Describe all the certs
|
||||
_describe_certs(config, parsed_certs, parse_failures)
|
||||
|
||||
|
||||
def delete(config):
|
||||
"""Delete Certbot files associated with a certificate lineage."""
|
||||
certnames = get_certnames(config, "delete", allow_multiple=True)
|
||||
|
|
@ -123,11 +125,13 @@ def lineage_for_certname(cli_config, certname):
|
|||
logger.debug("Traceback was:\n%s", traceback.format_exc())
|
||||
return None
|
||||
|
||||
|
||||
def domains_for_certname(config, certname):
|
||||
"""Find the domains in the cert with name certname."""
|
||||
lineage = lineage_for_certname(config, certname)
|
||||
return lineage.names() if lineage else None
|
||||
|
||||
|
||||
def find_duplicative_certs(config, domains):
|
||||
"""Find existing certs that match the given domain names.
|
||||
|
||||
|
|
@ -172,6 +176,7 @@ def find_duplicative_certs(config, domains):
|
|||
|
||||
return _search_lineages(config, update_certs_for_domain_matches, (None, None))
|
||||
|
||||
|
||||
def _archive_files(candidate_lineage, filetype):
|
||||
""" In order to match things like:
|
||||
/etc/letsencrypt/archive/example.com/chain1.pem.
|
||||
|
|
@ -193,6 +198,7 @@ def _archive_files(candidate_lineage, filetype):
|
|||
return pattern
|
||||
return None
|
||||
|
||||
|
||||
def _acceptable_matches():
|
||||
""" Generates the list that's passed to match_and_check_overlaps. Is its own function to
|
||||
make unit testing easier.
|
||||
|
|
@ -203,6 +209,7 @@ def _acceptable_matches():
|
|||
return [lambda x: x.fullchain_path, lambda x: x.cert_path,
|
||||
lambda x: _archive_files(x, "cert"), lambda x: _archive_files(x, "fullchain")]
|
||||
|
||||
|
||||
def cert_path_to_lineage(cli_config):
|
||||
""" If config.cert_path is defined, try to find an appropriate value for config.certname.
|
||||
|
||||
|
|
@ -219,6 +226,7 @@ def cert_path_to_lineage(cli_config):
|
|||
lambda x: cli_config.cert_path[0], lambda x: x.lineagename)
|
||||
return match[0]
|
||||
|
||||
|
||||
def match_and_check_overlaps(cli_config, acceptable_matches, match_func, rv_func):
|
||||
""" Searches through all lineages for a match, and checks for duplicates.
|
||||
If a duplicate is found, an error is raised, as performing operations on lineages
|
||||
|
|
@ -284,20 +292,23 @@ def human_readable_cert_info(config, cert, skip_filter_checks=False):
|
|||
|
||||
valid_string = "{0} ({1})".format(cert.target_expiry, status)
|
||||
serial = format(crypto_util.get_serial_from_cert(cert.cert_path), 'x')
|
||||
certinfo.append(" Certificate Name: {0}\n"
|
||||
" Serial Number: {1}\n"
|
||||
" Domains: {2}\n"
|
||||
" Expiry Date: {3}\n"
|
||||
" Certificate Path: {4}\n"
|
||||
" Private Key Path: {5}".format(
|
||||
certinfo.append(" Certificate Name: {}\n"
|
||||
" Serial Number: {}\n"
|
||||
" Key Type: {}\n"
|
||||
" Domains: {}\n"
|
||||
" Expiry Date: {}\n"
|
||||
" Certificate Path: {}\n"
|
||||
" Private Key Path: {}".format(
|
||||
cert.lineagename,
|
||||
serial,
|
||||
cert.private_key_type,
|
||||
" ".join(cert.names()),
|
||||
valid_string,
|
||||
cert.fullchain,
|
||||
cert.privkey))
|
||||
return "".join(certinfo)
|
||||
|
||||
|
||||
def get_certnames(config, verb, allow_multiple=False, custom_prompt=None):
|
||||
"""Get certname from flag, interactively, or error out.
|
||||
"""
|
||||
|
|
@ -337,10 +348,12 @@ def get_certnames(config, verb, allow_multiple=False, custom_prompt=None):
|
|||
# Private Helpers
|
||||
###################
|
||||
|
||||
|
||||
def _report_lines(msgs):
|
||||
"""Format a results report for a category of single-line renewal outcomes"""
|
||||
return " " + "\n ".join(str(msg) for msg in msgs)
|
||||
|
||||
|
||||
def _report_human_readable(config, parsed_certs):
|
||||
"""Format a results report for a parsed cert"""
|
||||
certinfo = []
|
||||
|
|
@ -348,6 +361,7 @@ def _report_human_readable(config, parsed_certs):
|
|||
certinfo.append(human_readable_cert_info(config, cert))
|
||||
return "\n".join(certinfo)
|
||||
|
||||
|
||||
def _describe_certs(config, parsed_certs, parse_failures):
|
||||
"""Print information about the certs we know about"""
|
||||
out = [] # type: List[str]
|
||||
|
|
@ -369,6 +383,7 @@ def _describe_certs(config, parsed_certs, parse_failures):
|
|||
disp = zope.component.getUtility(interfaces.IDisplay)
|
||||
disp.notification("\n".join(out), pause=False, wrap=False)
|
||||
|
||||
|
||||
def _search_lineages(cli_config, func, initial_rv, *args):
|
||||
"""Iterate func over unbroken lineages, allowing custom return conditions.
|
||||
|
||||
|
|
|
|||
|
|
@ -313,6 +313,16 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False):
|
|||
helpful.add(
|
||||
"security", "--rsa-key-size", type=int, metavar="N",
|
||||
default=flag_default("rsa_key_size"), help=config_help("rsa_key_size"))
|
||||
helpful.add(
|
||||
"security", "--key-type", choices=['rsa', 'ecdsa'], type=str,
|
||||
default=flag_default("key_type"), help=config_help("key_type"))
|
||||
helpful.add(
|
||||
"security", "--elliptic-curve", type=str, choices=[
|
||||
'secp256r1',
|
||||
'secp384r1',
|
||||
'secp521r1',
|
||||
], metavar="N",
|
||||
default=flag_default("elliptic_curve"), help=config_help("elliptic_curve"))
|
||||
helpful.add(
|
||||
"security", "--must-staple", action="store_true",
|
||||
dest="must_staple", default=flag_default("must_staple"),
|
||||
|
|
|
|||
|
|
@ -230,6 +230,10 @@ class HelpfulArgumentParser(object):
|
|||
raise errors.Error(
|
||||
"Parameters --hsts and --auto-hsts cannot be used simultaneously.")
|
||||
|
||||
if isinstance(parsed_args.key_type, list) and len(parsed_args.key_type) > 1:
|
||||
raise errors.Error(
|
||||
"Only *one* --key-type type may be provided at this time.")
|
||||
|
||||
return parsed_args
|
||||
|
||||
def set_test_server(self, parsed_args):
|
||||
|
|
|
|||
|
|
@ -312,7 +312,6 @@ class Client(object):
|
|||
:rtype: tuple
|
||||
|
||||
"""
|
||||
|
||||
# We need to determine the key path, key PEM data, CSR path,
|
||||
# and CSR PEM data. For a dry run, the paths are None because
|
||||
# they aren't permanently saved to disk. For a lineage with
|
||||
|
|
@ -335,16 +334,41 @@ class Client(object):
|
|||
# The key is set to None here but will be created below.
|
||||
key = None
|
||||
|
||||
key_size = self.config.rsa_key_size
|
||||
elliptic_curve = None
|
||||
|
||||
# key-type defaults to a list, but we are only handling 1 currently
|
||||
if isinstance(self.config.key_type, list):
|
||||
self.config.key_type = self.config.key_type[0]
|
||||
if self.config.elliptic_curve and self.config.key_type == 'ecdsa':
|
||||
elliptic_curve = self.config.elliptic_curve
|
||||
self.config.auth_chain_path = "./chain-ecdsa.pem"
|
||||
self.config.auth_cert_path = "./cert-ecdsa.pem"
|
||||
self.config.key_path = "./key-ecdsa.pem"
|
||||
elif self.config.rsa_key_size and self.config.key_type.lower() == 'rsa':
|
||||
key_size = self.config.rsa_key_size
|
||||
|
||||
# Create CSR from names
|
||||
if self.config.dry_run:
|
||||
key = key or util.Key(file=None,
|
||||
pem=crypto_util.make_key(self.config.rsa_key_size))
|
||||
key = key or util.Key(
|
||||
file=None,
|
||||
pem=crypto_util.make_key(
|
||||
bits=key_size,
|
||||
elliptic_curve=elliptic_curve,
|
||||
key_type=self.config.key_type,
|
||||
|
||||
),
|
||||
)
|
||||
csr = util.CSR(file=None, form="pem",
|
||||
data=acme_crypto_util.make_csr(
|
||||
key.pem, domains, self.config.must_staple))
|
||||
else:
|
||||
key = key or crypto_util.init_save_key(self.config.rsa_key_size,
|
||||
self.config.key_dir)
|
||||
key = key or crypto_util.init_save_key(
|
||||
key_size=key_size,
|
||||
key_dir=self.config.key_dir,
|
||||
key_type=self.config.key_type,
|
||||
elliptic_curve=elliptic_curve,
|
||||
)
|
||||
csr = crypto_util.init_save_csr(key, domains, self.config.csr_dir)
|
||||
|
||||
orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names)
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ CLI_DEFAULTS = dict(
|
|||
https_port=443,
|
||||
break_my_certs=False,
|
||||
rsa_key_size=2048,
|
||||
elliptic_curve="secp256r1",
|
||||
key_type="rsa",
|
||||
must_staple=False,
|
||||
redirect=None,
|
||||
auto_hsts=False,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import josepy as jose
|
|||
import zope.component
|
||||
|
||||
from acme import errors as acme_errors
|
||||
from acme.magic_typing import Union, Iterable, Optional # pylint: disable=unused-import
|
||||
from acme.magic_typing import Union, Iterable, Optional, List, Tuple # pylint: disable=unused-import
|
||||
import certbot
|
||||
from certbot import crypto_util
|
||||
from certbot import errors
|
||||
|
|
@ -142,7 +142,33 @@ def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=N
|
|||
return lineage
|
||||
|
||||
|
||||
def _handle_subset_cert_request(config, domains, cert):
|
||||
def _handle_unexpected_key_type_migration(config, cert):
|
||||
# type: (configuration.NamespaceConfig, storage.RenewableCert) -> None
|
||||
"""
|
||||
This function ensures that the user will not implicitly migrate an existing key
|
||||
from one type to another in the situation where a certificate for that lineage
|
||||
already exist and they have not provided explicitly --key-type and --cert-name.
|
||||
:param config: Current configuration provided by the client
|
||||
:param cert: Matching certificate that could be renewed
|
||||
"""
|
||||
if not cli.set_by_cli("key_type") or not cli.set_by_cli("certname"):
|
||||
|
||||
new_key_type = config.key_type.upper()
|
||||
cur_key_type = cert.private_key_type.upper()
|
||||
|
||||
if new_key_type != cur_key_type:
|
||||
msg = ('Are you trying to change the key type of the certificate named {0} '
|
||||
'from {1} to {2}? Please provide both --cert-name and --key-type on '
|
||||
'the command line confirm the change you are trying to make.')
|
||||
msg = msg.format(cert.lineagename, cur_key_type, new_key_type)
|
||||
raise errors.Error(msg)
|
||||
|
||||
|
||||
def _handle_subset_cert_request(config, # type: configuration.NamespaceConfig
|
||||
domains, # type: List[str]
|
||||
cert # type: storage.RenewableCert
|
||||
):
|
||||
# type: (...) -> Tuple[str, Optional[storage.RenewableCert]]
|
||||
"""Figure out what to do if a previous cert had a subset of the names now requested
|
||||
|
||||
:param config: Configuration object
|
||||
|
|
@ -159,6 +185,8 @@ def _handle_subset_cert_request(config, domains, cert):
|
|||
:rtype: `tuple` of `str`
|
||||
|
||||
"""
|
||||
_handle_unexpected_key_type_migration(config, cert)
|
||||
|
||||
existing = ", ".join(cert.names())
|
||||
question = (
|
||||
"You have an existing certificate that contains a portion of "
|
||||
|
|
@ -187,7 +215,10 @@ def _handle_subset_cert_request(config, domains, cert):
|
|||
raise errors.Error(USER_CANCELLED)
|
||||
|
||||
|
||||
def _handle_identical_cert_request(config, lineage):
|
||||
def _handle_identical_cert_request(config, # type: configuration.NamespaceConfig
|
||||
lineage, # type: storage.RenewableCert
|
||||
):
|
||||
# type: (...) -> Tuple[str, Optional[storage.RenewableCert]]
|
||||
"""Figure out what to do if a lineage has the same names as a previously obtained one
|
||||
|
||||
:param config: Configuration object
|
||||
|
|
@ -201,6 +232,8 @@ def _handle_identical_cert_request(config, lineage):
|
|||
:rtype: `tuple` of `str`
|
||||
|
||||
"""
|
||||
_handle_unexpected_key_type_migration(config, lineage)
|
||||
|
||||
if not lineage.ensure_deployed():
|
||||
return "reinstall", lineage
|
||||
if renewal.should_renew(config, lineage):
|
||||
|
|
@ -276,6 +309,7 @@ def _find_lineage_for_domains(config, domains):
|
|||
return _handle_subset_cert_request(config, domains, subset_names_cert)
|
||||
return None, None
|
||||
|
||||
|
||||
def _find_cert(config, domains, certname):
|
||||
"""Finds an existing certificate object given domains and/or a certificate name.
|
||||
|
||||
|
|
@ -299,7 +333,12 @@ def _find_cert(config, domains, certname):
|
|||
logger.info("Keeping the existing certificate")
|
||||
return (action != "reinstall"), lineage
|
||||
|
||||
def _find_lineage_for_domains_and_certname(config, domains, certname):
|
||||
|
||||
def _find_lineage_for_domains_and_certname(config, # type: configuration.NamespaceConfig
|
||||
domains, # type: List[str]
|
||||
certname # type: str
|
||||
):
|
||||
# type: (...) -> Tuple[str, Optional[storage.RenewableCert]]
|
||||
"""Find appropriate lineage based on given domains and/or certname.
|
||||
|
||||
:param config: Configuration object
|
||||
|
|
@ -326,8 +365,9 @@ def _find_lineage_for_domains_and_certname(config, domains, certname):
|
|||
if lineage:
|
||||
if domains:
|
||||
if set(cert_manager.domains_for_certname(config, certname)) != set(domains):
|
||||
_handle_unexpected_key_type_migration(config, lineage)
|
||||
_ask_user_to_confirm_new_names(config, domains, certname,
|
||||
lineage.names()) # raises if no
|
||||
lineage.names()) # raises if no
|
||||
return "renew", lineage
|
||||
# unnecessarily specified domains or no domains specified
|
||||
return _handle_identical_cert_request(config, lineage)
|
||||
|
|
@ -396,6 +436,7 @@ def _ask_user_to_confirm_new_names(config, new_domains, certname, old_domains):
|
|||
if not obj.yesno(msg, "Update cert", "Cancel", default=True):
|
||||
raise errors.ConfigurationError("Specified mismatched cert name and domains.")
|
||||
|
||||
|
||||
def _find_domains_or_certname(config, installer, question=None):
|
||||
"""Retrieve domains and certname from config or user input.
|
||||
|
||||
|
|
@ -1014,6 +1055,7 @@ def delete(config, unused_plugins):
|
|||
"""
|
||||
cert_manager.delete(config)
|
||||
|
||||
|
||||
def certificates(config, unused_plugins):
|
||||
"""Display information about certs configured with Certbot
|
||||
|
||||
|
|
@ -1029,6 +1071,7 @@ def certificates(config, unused_plugins):
|
|||
"""
|
||||
cert_manager.certificates(config)
|
||||
|
||||
|
||||
# TODO: coop with renewal config
|
||||
def revoke(config, unused_plugins):
|
||||
"""Revoke a previously obtained certificate.
|
||||
|
|
@ -1161,6 +1204,7 @@ def _csr_get_and_save_cert(config, le_client):
|
|||
os.path.normpath(config.chain_path), os.path.normpath(config.fullchain_path))
|
||||
return cert_path, fullchain_path
|
||||
|
||||
|
||||
def renew_cert(config, plugins, lineage):
|
||||
"""Renew & save an existing cert. Do not install it.
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import time
|
|||
import traceback
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
import OpenSSL
|
||||
import six
|
||||
|
|
@ -40,7 +40,7 @@ logger = logging.getLogger(__name__)
|
|||
STR_CONFIG_ITEMS = ["config_dir", "logs_dir", "work_dir", "user_agent",
|
||||
"server", "account", "authenticator", "installer",
|
||||
"renew_hook", "pre_hook", "post_hook", "http01_address",
|
||||
"preferred_chain"]
|
||||
"preferred_chain", "key_type", "elliptic_curve"]
|
||||
INT_CONFIG_ITEMS = ["rsa_key_size", "http01_port"]
|
||||
BOOL_CONFIG_ITEMS = ["must_staple", "allow_subset_of_names", "reuse_key",
|
||||
"autorenew"]
|
||||
|
|
@ -506,6 +506,10 @@ def _update_renewal_params_from_key(key_path, config):
|
|||
with open(key_path, 'rb') as file_h:
|
||||
key = load_pem_private_key(file_h.read(), password=None, backend=default_backend())
|
||||
if isinstance(key, rsa.RSAPrivateKey):
|
||||
config.key_type = 'rsa'
|
||||
config.rsa_key_size = key.key_size
|
||||
elif isinstance(key, ec.EllipticCurvePrivateKey):
|
||||
config.key_type = 'ecdsa'
|
||||
config.elliptic_curve = key.curve.name
|
||||
else:
|
||||
raise errors.Error('Key at {0} is of an unsupported type: {1}.'.format(key_path, type(key)))
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import configobj
|
|||
import parsedatetime
|
||||
import pytz
|
||||
import six
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
|
||||
import certbot
|
||||
from certbot import crypto_util
|
||||
|
|
@ -46,6 +49,7 @@ def renewal_conf_files(config):
|
|||
result.sort()
|
||||
return result
|
||||
|
||||
|
||||
def renewal_file_for_certname(config, certname):
|
||||
"""Return /path/to/certname.conf in the renewal conf directory"""
|
||||
path = os.path.join(config.renewal_configs_dir, "{0}.conf".format(certname))
|
||||
|
|
@ -1055,6 +1059,23 @@ class RenewableCert(interfaces.RenewableCert):
|
|||
target, values)
|
||||
return cls(new_config.filename, cli_config)
|
||||
|
||||
@property
|
||||
def private_key_type(self):
|
||||
"""
|
||||
:returns: The type of algorithm for the private, RSA or ECDSA
|
||||
:rtype: str
|
||||
"""
|
||||
with open(self.configuration["privkey"], "rb") as priv_key_file:
|
||||
key = load_pem_private_key(
|
||||
data=priv_key_file.read(),
|
||||
password=None,
|
||||
backend=default_backend()
|
||||
)
|
||||
if isinstance(key, RSAPrivateKey):
|
||||
return "RSA"
|
||||
else:
|
||||
return "ECDSA"
|
||||
|
||||
def save_successor(self, prior_version, new_cert,
|
||||
new_privkey, new_chain, cli_config):
|
||||
"""Save new cert and chain as a successor of a prior version.
|
||||
|
|
|
|||
|
|
@ -11,14 +11,16 @@ import warnings
|
|||
import re
|
||||
# See https://github.com/pyca/cryptography/issues/4275
|
||||
from cryptography import x509 # type: ignore
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA, EllipticCurvePublicKey
|
||||
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat
|
||||
from OpenSSL import crypto
|
||||
from OpenSSL import SSL # type: ignore
|
||||
|
||||
import pyrfc3339
|
||||
import six
|
||||
import zope.component
|
||||
|
|
@ -34,7 +36,9 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
# High level functions
|
||||
def init_save_key(key_size, key_dir, keyname="key-certbot.pem"):
|
||||
def init_save_key(
|
||||
key_size, key_dir, key_type="rsa", elliptic_curve="secp256r1", keyname="key-certbot.pem"
|
||||
):
|
||||
"""Initializes and saves a privkey.
|
||||
|
||||
Inits key and saves it in PEM format on the filesystem.
|
||||
|
|
@ -42,8 +46,10 @@ def init_save_key(key_size, key_dir, keyname="key-certbot.pem"):
|
|||
.. note:: keyname is the attempted filename, it may be different if a file
|
||||
already exists at the path.
|
||||
|
||||
:param int key_size: RSA key size in bits
|
||||
:param int key_size: key size in bits if key size is rsa.
|
||||
:param str key_dir: Key save directory.
|
||||
:param str key_type: Key Type [rsa, ecdsa]
|
||||
:param str elliptic_curve: Name of the elliptic curve if key type is ecdsa.
|
||||
:param str keyname: Filename of key
|
||||
|
||||
:returns: Key
|
||||
|
|
@ -53,7 +59,9 @@ def init_save_key(key_size, key_dir, keyname="key-certbot.pem"):
|
|||
|
||||
"""
|
||||
try:
|
||||
key_pem = make_key(key_size)
|
||||
key_pem = make_key(
|
||||
bits=key_size, elliptic_curve=elliptic_curve or "secp256r1", key_type=key_type,
|
||||
)
|
||||
except ValueError as err:
|
||||
logger.error("", exc_info=True)
|
||||
raise err
|
||||
|
|
@ -65,7 +73,10 @@ def init_save_key(key_size, key_dir, keyname="key-certbot.pem"):
|
|||
os.path.join(key_dir, keyname), 0o600, "wb")
|
||||
with key_f:
|
||||
key_f.write(key_pem)
|
||||
logger.debug("Generating key (%d bits): %s", key_size, key_path)
|
||||
if key_type == 'rsa':
|
||||
logger.debug("Generating RSA key (%d bits): %s", key_size, key_path)
|
||||
else:
|
||||
logger.debug("Generating ECDSA key (%d bits): %s", key_size, key_path)
|
||||
|
||||
return util.Key(key_path, key_pem)
|
||||
|
||||
|
|
@ -174,18 +185,45 @@ def import_csr_file(csrfile, data):
|
|||
return PEM, util.CSR(file=csrfile, data=data_pem, form="pem"), domains
|
||||
|
||||
|
||||
def make_key(bits):
|
||||
"""Generate PEM encoded RSA key.
|
||||
def make_key(bits=1024, key_type="rsa", elliptic_curve=None):
|
||||
"""Generate PEM encoded RSA|EC key.
|
||||
|
||||
:param int bits: Number of bits, at least 1024.
|
||||
:param int bits: Number of bits if key_type=rsa. At least 1024 for RSA.
|
||||
|
||||
:returns: new RSA key in PEM form with specified number of bits
|
||||
:param str ec_curve: The elliptic curve to use.
|
||||
|
||||
:returns: new RSA or ECDSA key in PEM form with specified number of bits
|
||||
or of type ec_curve when key_type ecdsa is used.
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
assert bits >= 1024 # XXX
|
||||
key = crypto.PKey()
|
||||
key.generate_key(crypto.TYPE_RSA, bits)
|
||||
if key_type == 'rsa':
|
||||
if bits < 1024:
|
||||
raise errors.Error("Unsupported RSA key length: {}".format(bits))
|
||||
|
||||
key = crypto.PKey()
|
||||
key.generate_key(crypto.TYPE_RSA, bits)
|
||||
elif key_type == 'ecdsa':
|
||||
try:
|
||||
name = elliptic_curve.upper()
|
||||
if name in ('SECP256R1', 'SECP384R1', 'SECP512R1'):
|
||||
_key = ec.generate_private_key(
|
||||
curve=getattr(ec, elliptic_curve.upper(), None)(),
|
||||
backend=default_backend()
|
||||
)
|
||||
else:
|
||||
raise errors.Error("Unsupported elliptic curve: {}".format(elliptic_curve))
|
||||
except TypeError:
|
||||
raise errors.Error("Unsupported elliptic curve: {}".format(elliptic_curve))
|
||||
except UnsupportedAlgorithm as e:
|
||||
raise six.raise_from(e, errors.Error(str(e)))
|
||||
_key_pem = _key.private_bytes(
|
||||
encoding=Encoding.PEM,
|
||||
format=PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=NoEncryption()
|
||||
)
|
||||
key = crypto.load_privatekey(crypto.FILETYPE_PEM, _key_pem)
|
||||
else:
|
||||
raise errors.Error("Invalid key_type specified: {}. Use [rsa|ecdsa]".format(key_type))
|
||||
return crypto.dump_privatekey(crypto.FILETYPE_PEM, key)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -198,6 +198,13 @@ class IConfig(zope.interface.Interface):
|
|||
"register multiple emails, ex: u1@example.com,u2@example.com. "
|
||||
"(default: Ask).")
|
||||
rsa_key_size = zope.interface.Attribute("Size of the RSA key.")
|
||||
elliptic_curve = zope.interface.Attribute(
|
||||
"The SECG elliptic curve name to use. Please see RFC 8446 "
|
||||
"for supported values."
|
||||
)
|
||||
key_type = zope.interface.Attribute(
|
||||
"Type of generated private key"
|
||||
"(Only *ONE* per invocation can be provided at this time)")
|
||||
must_staple = zope.interface.Attribute(
|
||||
"Adds the OCSP Must Staple extension to the certificate. "
|
||||
"Autoconfigures OCSP Stapling for supported setups "
|
||||
|
|
@ -260,6 +267,7 @@ class IConfig(zope.interface.Interface):
|
|||
"offered chain will be used."
|
||||
)
|
||||
|
||||
|
||||
class IInstaller(IPlugin):
|
||||
"""Generic Certbot Installer Interface.
|
||||
|
||||
|
|
|
|||
8
certbot/certbot/tests/testdata/README
vendored
8
certbot/certbot/tests/testdata/README
vendored
|
|
@ -2,10 +2,16 @@ The following command has been used to generate test keys:
|
|||
|
||||
for x in 256 512 2048; do openssl genrsa -out rsa${k}_key.pem $k; done
|
||||
|
||||
For the elliptic curve private keys, this command was used:
|
||||
|
||||
for k in "prime256v1" "secp384r1" "secp521r1" do
|
||||
openssl genpkey -algorithm ${k} -out ec_${k}_key.pem
|
||||
done
|
||||
|
||||
and for the CSR PEM (Certificate Signing Request):
|
||||
|
||||
openssl req -new -out csr-Xsans_X.pem -key rsa512_key.pem [-config csr-Xsans_X.conf | -subj '/CN=example.com'] [-outform DER > csr_X.der]
|
||||
|
||||
and for the certificate:
|
||||
|
||||
openssl req -new -out cert_X.pem -key rsaX_key.pem -subj '/CN=example.com' -x509 [-outform DER > cert_X.der]
|
||||
openssl req -new -out cert_X.pem -key rsaX_key.pem -subj '/CN=example.com' -x509 [-outform DER > cert_X.der]
|
||||
|
|
|
|||
8
certbot/certbot/tests/testdata/ec_prime256v1_key.pem
vendored
Normal file
8
certbot/certbot/tests/testdata/ec_prime256v1_key.pem
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-----BEGIN EC PARAMETERS-----
|
||||
BggqhkjOPQMBBw==
|
||||
-----END EC PARAMETERS-----
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIDqQPQl69kuh+DrecC8SFPt21f0F/HHDP3T4/Lf0zIVFoAoGCCqGSM49
|
||||
AwEHoUQDQgAEHou50Ee9u+8Vial6VbUHExlzsiCHtORlW0X0pKo5RspIKB0QyKwo
|
||||
dUXvBbv95I9yCO5+MlGkKjwLHtIEze0Hww==
|
||||
-----END EC PRIVATE KEY-----
|
||||
9
certbot/certbot/tests/testdata/ec_secp384r1_key.pem
vendored
Normal file
9
certbot/certbot/tests/testdata/ec_secp384r1_key.pem
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
-----BEGIN EC PARAMETERS-----
|
||||
BgUrgQQAIg==
|
||||
-----END EC PARAMETERS-----
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIGkAgEBBDAgvZGw5C7Mp26N0cXA+vIg5K/J5MJw+MVGYfGF4ZutuCLeYMrWT68R
|
||||
A0h6hJvDtMSgBwYFK4EEACKhZANiAAR1uQYZeU5Kml5o53Q8/PCdwUbqdgCSkV0C
|
||||
J5a6bhDRMp20fdp2T/mbkdxuVEl81lqfKPZhsd4CZsLaVIU3RUoGgIT1R3QKawpJ
|
||||
SuXq37yWFX2hqlgt+lsBufZ8RD5QnZc=
|
||||
-----END EC PRIVATE KEY-----
|
||||
10
certbot/certbot/tests/testdata/ec_secp521r1_key.pem
vendored
Normal file
10
certbot/certbot/tests/testdata/ec_secp521r1_key.pem
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
-----BEGIN EC PARAMETERS-----
|
||||
BgUrgQQAIw==
|
||||
-----END EC PARAMETERS-----
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MIHcAgEBBEIACWWVKm1qAIejZ6qmqk9D69wQW5FAe3Er0IxWAMkonTEhu8EH5Q2i
|
||||
2vT2bESm730zhGTe2Pn11b85H6UI9hxhCHygBwYFK4EEACOhgYkDgYYABAEQi1WF
|
||||
m3suHjPyWACyOJYGUn1Kx6rfBo0PjC7X2TU9jr8umLkIpaaF5UsBuMBmdz1IHL0U
|
||||
k0gQtoOQ0Qu8N74GuAGzGR0S3RYIv6gfYVz3dS1K4n4b307Lx62bnvtlNxcIvt3w
|
||||
hmS5OdvQ1Kdxh6oqbSVhhbQmJcgab78Txx3R2QeCxw==
|
||||
-----END EC PRIVATE KEY-----
|
||||
18
certbot/certbot/tests/testdata/sample-archive-ec/cert1.pem
vendored
Normal file
18
certbot/certbot/tests/testdata/sample-archive-ec/cert1.pem
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC2zCCAcOgAwIBAgIIBvrEnbPRYu8wDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
|
||||
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAxMjZjNGIwHhcNMjAxMDEyMjEwNzQw
|
||||
WhcNMjUxMDEyMjEwNzQwWjAjMSEwHwYDVQQDExhjLmVuY3J5cHRpb24tZXhhbXBs
|
||||
ZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARjMhuW0ENPPC33PjB5XsYU
|
||||
CRw640kPQENIDatcTJaENZIZdqKd6rI6jc+lpbmXot7Zi52clJlSJS+V6oDAt2Lh
|
||||
o4HYMIHVMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
|
||||
BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUj7Kd3ENqxlPf8B2bIGhsjydX
|
||||
mPswHwYDVR0jBBgwFoAUEiGxlkRsi+VvcogH5dVD3h1laAcwMQYIKwYBBQUHAQEE
|
||||
JTAjMCEGCCsGAQUFBzABhhVodHRwOi8vMTI3LjAuMC4xOjQwMDIwIwYDVR0RBBww
|
||||
GoIYYy5lbmNyeXB0aW9uLWV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQCl
|
||||
k0JXsa8y7fg41WWMDhw60bPW77O0FtOmTcnhdI5daYNemQVk+Q5EMaBLQ/oGjgXd
|
||||
9QXFzXH1PL904YEnSLt+iTpXn++7rQSNzQsdYqw0neWk4f5pEBiN+WORpb6mwobV
|
||||
ifMtBOkNEHvrJ2Pkci9U1lLwtKD/DSew6QtJU5DSkmH1XdGuMJiubygEIvELtvgq
|
||||
cP9S368ZvPmPGmKaJQXBiuaR8MTjY/Bkr79aXQMjKbf+mpn7h0POCcePk1DY/rm6
|
||||
Da+X16lf0hHyQhSUa7Vgyim6rK1/hlw+Z00i+sQCKD9Ih7kXuuGqfSDC33cfO8Tj
|
||||
o/MXO8lcxkrem5zU5QWP
|
||||
-----END CERTIFICATE-----
|
||||
20
certbot/certbot/tests/testdata/sample-archive-ec/chain1.pem
vendored
Normal file
20
certbot/certbot/tests/testdata/sample-archive-ec/chain1.pem
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDUDCCAjigAwIBAgIIbi787yVrcMAwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVUGViYmxlIFJvb3QgQ0EgMGM1MjI1MCAXDTIwMTAxMjIwMjI0NloYDzIwNTAx
|
||||
MDEyMjEyMjQ2WjAoMSYwJAYDVQQDEx1QZWJibGUgSW50ZXJtZWRpYXRlIENBIDEy
|
||||
NmM0YjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGeVk1BMJraeqRq
|
||||
mJ2+hgso8VOAv2s2CVxUJjIVcn7f2adE8NyTsSQ1brlsnKCUYUw7yLTQH0izLQRB
|
||||
qKVIDFkUqo5/FuTJ2QlfA2EwBL8J7s/7L7vj3L0DiVpwgxPSyFEwdl/Y5y7ofsX5
|
||||
CIhCFcaMAmTIuKLiSfCJjGwkbEMuolm+lO8Mikxxc/JtDVUC479ugU7PU9O09bMH
|
||||
nm+sD6Bgd+KMoPkCCCoeShJS9X3Ziq9HGc7Z6nhM/zirFARt2XkonEdAZ8br01zY
|
||||
MRiY9txhlWQ7mUkOtzOSoEuYJNoUbvMUf0+tNzto26WRyF7dJmh7lTBsYrvAwUTx
|
||||
PzNyst0CAwEAAaOBgzCBgDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYB
|
||||
BQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBIhsZZE
|
||||
bIvlb3KIB+XVQ94dZWgHMB8GA1UdIwQYMBaAFOaKTaXg37vKgRt7d79YOjAoAtJT
|
||||
MA0GCSqGSIb3DQEBCwUAA4IBAQAU2mZii7PH2pkw2lNM0QqPbcW/UYyvFoUeM8Aq
|
||||
uCtsI2s+oxCJTqzfLsA0N8NY4nHLQ5wAlNJfJekngni8hbmJTKU4JFTMe7kLQO8P
|
||||
fJbk0pTzhhHVQw7CVwB6Pwq3u2m/JV+d6xDIDc+AVkuEl19ZJU0rTWyooClfFLZV
|
||||
EdZmEiUtA3PGlxoYwYhoGHYlhFxsoFONhCsBEdN7k7FKtFGVxN7oc5SKmKp0YZTW
|
||||
fcrEtrdNThATO4ymhCC2zh33NI/MT1O74fpaAc2k6LcTl57MKiLfTYX4LTL6v9JG
|
||||
9tlNqjFVRRmzEbtXTPcCb+w9g1VqoOGok7mGXYLTYtShCuvE
|
||||
-----END CERTIFICATE-----
|
||||
38
certbot/certbot/tests/testdata/sample-archive-ec/fullchain1.pem
vendored
Normal file
38
certbot/certbot/tests/testdata/sample-archive-ec/fullchain1.pem
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC2zCCAcOgAwIBAgIILlmGtZhUFEwwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
|
||||
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAxMjZjNGIwHhcNMjAxMDEyMjA1MDM0
|
||||
WhcNMjUxMDEyMjA1MDM0WjAjMSEwHwYDVQQDExhjLmVuY3J5cHRpb24tZXhhbXBs
|
||||
ZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARHEzR8JPWrEmpmgM+F2bk5
|
||||
9mT0u6CjzmJG0QpbaqprLiG5NGpW84VQ5TFCrmC4KxYfigCfMhfHRNfFYvNUK3V/
|
||||
o4HYMIHVMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
|
||||
BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU1CsVL+bPnzaxxQ5jUENmQJIO
|
||||
lKwwHwYDVR0jBBgwFoAUEiGxlkRsi+VvcogH5dVD3h1laAcwMQYIKwYBBQUHAQEE
|
||||
JTAjMCEGCCsGAQUFBzABhhVodHRwOi8vMTI3LjAuMC4xOjQwMDIwIwYDVR0RBBww
|
||||
GoIYYy5lbmNyeXB0aW9uLWV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBn
|
||||
2D8loC7pfk28JYpFLr5lmFKJWWmtLGlpsWDj61fVjtTfGKLziJz+MM6il4Y3hIz5
|
||||
58qiFK0ue0M63dIBJ33N+XxSEXon4Q0gy/zRWfH9jtPJ3FwfjkU/RT9PAUClYi0G
|
||||
ptNWnTmgQkNzousbcAtRNXuuShH3856vhUnwkX+xM+cbIDi1JVmFjcGrEEQJ0rUF
|
||||
mv2ZTyfbWbUs3v4rReETi2NVzr1Ql6J+ByNcMvHODzFy3t0L6yelAw2ca1I+c9HU
|
||||
+Z0tnp/ykR7eXNuVLivok8UBf5OC413lh8ZO5g+Bgzh/LdtkUuavg1MYtEX0H6mX
|
||||
9U7y3nVI8WEbPGf+HDeu
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDUDCCAjigAwIBAgIIbi787yVrcMAwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVUGViYmxlIFJvb3QgQ0EgMGM1MjI1MCAXDTIwMTAxMjIwMjI0NloYDzIwNTAx
|
||||
MDEyMjEyMjQ2WjAoMSYwJAYDVQQDEx1QZWJibGUgSW50ZXJtZWRpYXRlIENBIDEy
|
||||
NmM0YjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGeVk1BMJraeqRq
|
||||
mJ2+hgso8VOAv2s2CVxUJjIVcn7f2adE8NyTsSQ1brlsnKCUYUw7yLTQH0izLQRB
|
||||
qKVIDFkUqo5/FuTJ2QlfA2EwBL8J7s/7L7vj3L0DiVpwgxPSyFEwdl/Y5y7ofsX5
|
||||
CIhCFcaMAmTIuKLiSfCJjGwkbEMuolm+lO8Mikxxc/JtDVUC479ugU7PU9O09bMH
|
||||
nm+sD6Bgd+KMoPkCCCoeShJS9X3Ziq9HGc7Z6nhM/zirFARt2XkonEdAZ8br01zY
|
||||
MRiY9txhlWQ7mUkOtzOSoEuYJNoUbvMUf0+tNzto26WRyF7dJmh7lTBsYrvAwUTx
|
||||
PzNyst0CAwEAAaOBgzCBgDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYB
|
||||
BQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBIhsZZE
|
||||
bIvlb3KIB+XVQ94dZWgHMB8GA1UdIwQYMBaAFOaKTaXg37vKgRt7d79YOjAoAtJT
|
||||
MA0GCSqGSIb3DQEBCwUAA4IBAQAU2mZii7PH2pkw2lNM0QqPbcW/UYyvFoUeM8Aq
|
||||
uCtsI2s+oxCJTqzfLsA0N8NY4nHLQ5wAlNJfJekngni8hbmJTKU4JFTMe7kLQO8P
|
||||
fJbk0pTzhhHVQw7CVwB6Pwq3u2m/JV+d6xDIDc+AVkuEl19ZJU0rTWyooClfFLZV
|
||||
EdZmEiUtA3PGlxoYwYhoGHYlhFxsoFONhCsBEdN7k7FKtFGVxN7oc5SKmKp0YZTW
|
||||
fcrEtrdNThATO4ymhCC2zh33NI/MT1O74fpaAc2k6LcTl57MKiLfTYX4LTL6v9JG
|
||||
9tlNqjFVRRmzEbtXTPcCb+w9g1VqoOGok7mGXYLTYtShCuvE
|
||||
-----END CERTIFICATE-----
|
||||
5
certbot/certbot/tests/testdata/sample-archive-ec/privkey1.pem
vendored
Normal file
5
certbot/certbot/tests/testdata/sample-archive-ec/privkey1.pem
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgNgefv2dad4U1VYEi
|
||||
0WkdHuqywi5QXAe30OwNTTGjhbihRANCAARHEzR8JPWrEmpmgM+F2bk59mT0u6Cj
|
||||
zmJG0QpbaqprLiG5NGpW84VQ5TFCrmC4KxYfigCfMhfHRNfFYvNUK3V/
|
||||
-----END PRIVATE KEY-----
|
||||
79
certbot/certbot/tests/testdata/sample-renewal-ec.conf
vendored
Normal file
79
certbot/certbot/tests/testdata/sample-renewal-ec.conf
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# add some stuff here
|
||||
# assets/integration_tests
|
||||
|
||||
cert = MAGICDIR/live/sample-renewal-ec/cert.pem
|
||||
privkey = MAGICDIR/live/sample-renewal-ec/privkey.pem
|
||||
chain = MAGICDIR/live/sample-renewal-ec/chain.pem
|
||||
fullchain = MAGICDIR/live/sample-renewal-ec/fullchain.pem
|
||||
renew_before_expiry = 4 years
|
||||
|
||||
# Options and defaults used in the renewal process
|
||||
[renewalparams]
|
||||
no_self_upgrade = False
|
||||
apache_enmod = a2enmod
|
||||
no_verify_ssl = False
|
||||
ifaces = None
|
||||
apache_dismod = a2dismod
|
||||
register_unsafely_without_email = False
|
||||
apache_handle_modules = True
|
||||
uir = None
|
||||
installer = None
|
||||
nginx_ctl = nginx
|
||||
config_dir = MAGICDIR
|
||||
text_mode = False
|
||||
func = <function obtain_cert at 0x7f093a163c08>
|
||||
staging = True
|
||||
prepare = False
|
||||
work_dir = /var/lib/letsencrypt
|
||||
tos = False
|
||||
init = False
|
||||
http01_port = 80
|
||||
duplicate = False
|
||||
noninteractive_mode = True
|
||||
key_path = None
|
||||
nginx = False
|
||||
nginx_server_root = /etc/nginx
|
||||
fullchain_path = /home/ubuntu/letsencrypt/chain.pem
|
||||
email = None
|
||||
csr = None
|
||||
agree_dev_preview = None
|
||||
redirect = None
|
||||
verb = certonly
|
||||
verbose_count = -3
|
||||
config_file = None
|
||||
renew_by_default = False
|
||||
hsts = False
|
||||
apache_handle_sites = True
|
||||
authenticator = standalone
|
||||
domains = isnot.org,
|
||||
key_type = ecdsa
|
||||
elliptic_curve = secp256r1
|
||||
apache_challenge_location = /etc/apache2
|
||||
checkpoints = 1
|
||||
manual_test_mode = False
|
||||
apache = False
|
||||
cert_path = /home/ubuntu/letsencrypt/cert.pem
|
||||
webroot_path = None
|
||||
reinstall = False
|
||||
expand = False
|
||||
strict_permissions = False
|
||||
apache_server_root = /etc/apache2
|
||||
account = None
|
||||
dry_run = False
|
||||
manual_public_ip_logging_ok = False
|
||||
chain_path = /home/ubuntu/letsencrypt/chain.pem
|
||||
break_my_certs = False
|
||||
standalone = True
|
||||
manual = False
|
||||
server = https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
webroot = False
|
||||
os_packages_only = False
|
||||
apache_init_script = None
|
||||
user_agent = None
|
||||
apache_le_vhost_ext = -le-ssl.conf
|
||||
debug = False
|
||||
logs_dir = /var/log/letsencrypt
|
||||
apache_vhost_root = /etc/apache2/sites-available
|
||||
configurator = None
|
||||
must_staple = True
|
||||
[[webroot_map]]
|
||||
|
|
@ -44,6 +44,7 @@ apache_handle_sites = True
|
|||
authenticator = standalone
|
||||
domains = isnot.org,
|
||||
rsa_key_size = 2048
|
||||
elliptic_curve = secp256r1
|
||||
apache_challenge_location = /etc/apache2
|
||||
checkpoints = 1
|
||||
manual_test_mode = False
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ def load_pyopenssl_private_key(*names):
|
|||
return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))
|
||||
|
||||
|
||||
def make_lineage(config_dir, testfile):
|
||||
def make_lineage(config_dir, testfile, ec=False):
|
||||
"""Creates a lineage defined by testfile.
|
||||
|
||||
This creates the archive, live, and renewal directories if
|
||||
|
|
@ -119,7 +119,7 @@ def make_lineage(config_dir, testfile):
|
|||
if not os.path.exists(directory):
|
||||
filesystem.makedirs(directory)
|
||||
|
||||
sample_archive = vector_path('sample-archive')
|
||||
sample_archive = vector_path('sample-archive{}'.format('-ec' if ec else ''))
|
||||
for kind in os.listdir(sample_archive):
|
||||
shutil.copyfile(os.path.join(sample_archive, kind),
|
||||
os.path.join(archive_dir, kind))
|
||||
|
|
|
|||
|
|
@ -80,8 +80,8 @@ its preferences in accordance with its own policy or its administrators'
|
|||
preferences, and use different cryptographic mechanisms or parameters,
|
||||
or a different priority order, than the defaults provided by Certbot.
|
||||
|
||||
If you don't use Certbot to configure your server directly, because the
|
||||
client doesn't integrate with your server software or because you chose
|
||||
If you don't use Certbot to configure your server directly, because the
|
||||
client doesn't integrate with your server software or because you chose
|
||||
not to use this integration, then the cryptographic defaults haven't been
|
||||
modified, and the cryptography chosen by the server will still be whatever
|
||||
the default for your software was. For example, if you obtain a
|
||||
|
|
@ -254,7 +254,7 @@ https://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-secur
|
|||
U.S. Government 18F
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The 18F site (https://18f.gsa.gov/) is using
|
||||
The 18F site (https://18f.gsa.gov/) is using
|
||||
|
||||
::
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
usage:
|
||||
usage:
|
||||
certbot [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ...
|
||||
|
||||
Certbot can obtain and install HTTPS/TLS/SSL certificates. By default,
|
||||
|
|
@ -188,6 +188,12 @@ security:
|
|||
Security parameters & server settings
|
||||
|
||||
--rsa-key-size N Size of the RSA key. (default: 2048)
|
||||
--key-type type The type of algorithm to use for the the private key.
|
||||
Either ``rsa`` or ``ecdsa``. (default: ``rsa``).
|
||||
--elliptic-curve The elliptic curve to use when choosing ``ecdsa`` as the key
|
||||
type. Accepted values are SECG curve names as defined by
|
||||
the cryptography library. ``secp256r1``, ``secp384r1``,
|
||||
``secp521r1``. (default: secp256r1).
|
||||
--must-staple Adds the OCSP Must Staple extension to the
|
||||
certificate. Autoconfigures OCSP Stapling for
|
||||
supported setups (Apache version >= 2.3.3 ). (default:
|
||||
|
|
|
|||
|
|
@ -434,16 +434,23 @@ Please:
|
|||
|
||||
4. Remember to use ``pylint``.
|
||||
|
||||
5. You may consider installing a plugin for `editorconfig`_ in
|
||||
your editor to prevent some linting warnings.
|
||||
|
||||
6. Please avoid `unittest.assertTrue` or `unittest.assertFalse` when
|
||||
possible, and use `assertEqual` or more specific assert. They give
|
||||
better messages when it's failing, and are generally more correct.
|
||||
|
||||
.. _Google Python Style Guide:
|
||||
https://google.github.io/styleguide/pyguide.html
|
||||
.. _Sphinx-style: https://www.sphinx-doc.org/
|
||||
.. _PEP 8 - Style Guide for Python Code:
|
||||
https://www.python.org/dev/peps/pep-0008
|
||||
.. _editorconfig: https://editorconfig.org/
|
||||
|
||||
Use ``certbot.compat.os`` instead of ``os``
|
||||
===========================================
|
||||
|
||||
|
||||
Python's standard library ``os`` module lacks full support for several Windows
|
||||
security features about file permissions (eg. DACLs). However several files
|
||||
handled by Certbot (eg. private keys) need strongly restricted access
|
||||
|
|
|
|||
|
|
@ -319,6 +319,7 @@ This returns information in the following format::
|
|||
Domains: example.com, www.example.com
|
||||
Expiry Date: 2017-02-19 19:53:00+00:00 (VALID: 30 days)
|
||||
Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
|
||||
Key Type: RSA
|
||||
Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem
|
||||
|
||||
``Certificate Name`` shows the name of the certificate. Pass this name
|
||||
|
|
@ -346,7 +347,6 @@ control Certbot's behavior when re-creating
|
|||
a certificate with the same name as an existing certificate.
|
||||
If you don't specify a requested behavior, Certbot may ask you what you intended.
|
||||
|
||||
|
||||
``--force-renewal`` tells Certbot to request a new certificate
|
||||
with the same domains as an existing certificate. Each domain
|
||||
must be explicitly specified via ``-d``. If successful, this certificate
|
||||
|
|
@ -380,7 +380,6 @@ If you prefer, you can specify the domains individually like this:
|
|||
Consider using ``--cert-name`` instead of ``--expand``, as it gives more control
|
||||
over which certificate is modified and it lets you remove domains as well as adding them.
|
||||
|
||||
|
||||
``--allow-subset-of-names`` tells Certbot to continue with certificate generation if
|
||||
only some of the specified domain authorizations can be obtained. This may
|
||||
be useful if some domains specified in a certificate no longer point at this
|
||||
|
|
@ -411,6 +410,68 @@ replace that set entirely::
|
|||
certbot certonly --cert-name example.com -d example.org,www.example.org
|
||||
|
||||
|
||||
Using ECDSA keys
|
||||
----------------
|
||||
|
||||
As of version 1.10, Certbot supports two types of private key algorithms:
|
||||
``rsa`` and ``ecdsa``. The type of key used by Certbot can be controlled
|
||||
through the ``--key-type`` option. You can also use the ``--elliptic-curve``
|
||||
option to control the curve used in ECDSA certificates.
|
||||
|
||||
.. warning:: If you obtain certificates using ECDSA keys, you should be careful
|
||||
not to downgrade your Certbot installation since ECDSA keys are not
|
||||
supported by older versions of Certbot. Downgrades like this are possible if
|
||||
you switch from something like the snaps or certbot-auto to packages
|
||||
provided by your operating system which often lag behind.
|
||||
|
||||
Changing existing certificates from RSA to ECDSA
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Unless you are aware that you need to support very old HTTPS clients that are
|
||||
not supported by most sites, you can safely just transition your site to use
|
||||
ECDSA keys instead of RSA keys. To accomplish this if you have existing
|
||||
certificates managed by Certbot, you may freely change the certificate to a new
|
||||
private key.
|
||||
|
||||
If you want to use ECDSA keys for all certificates in the future, you can
|
||||
simply add the following line to Certbot's :ref:`configuration file <config-file>`
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
key-type = ecdsa
|
||||
|
||||
After this option is set, newly obtained certificates will use ECDSA keys. This
|
||||
includes certificates managed by Certbot that previously used RSA keys.
|
||||
|
||||
If you want to change a single certificate to use ECDSA keys, you'll need to
|
||||
issue a new Certbot command setting ``--key-type ecdsa`` on the command line
|
||||
like
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
certbot renew --key-type ecdsa --cert-name example.com --force-renewal
|
||||
|
||||
Obtaining ECDSA certificates in addition to RSA certificates
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When Certbot configures the certificates it obtains with Apache or Nginx, all
|
||||
HTTPS clients that we try to support can use certificates with ECDSA keys. If,
|
||||
however, you are aware of having a specific need to support very old TLS
|
||||
clients, you may want to obtain both ECDSA and RSA certificates for the same
|
||||
domains. Certbot can only configure Apache or Nginx to use a single
|
||||
certificate, however, you could manually configure your software to use the
|
||||
different certificates depending on your needs.
|
||||
|
||||
When obtaining both ECDSA and RSA certificates for the same domains with
|
||||
Certbot, we recommend using the ``--cert-name`` option to give your
|
||||
certificates names so that you can easily identify them. For instance, you may
|
||||
want to append "ecdsa" to the name of your ECDSA certificate by using a command
|
||||
like
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
certbot certonly --key-type ecdsa --cert-name example.com-ecdsa
|
||||
|
||||
Revoking certificates
|
||||
---------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
# certificate on a system with several certificates should not be placed
|
||||
# here.
|
||||
|
||||
# Use ECC for the private key
|
||||
key-type = ecdsa
|
||||
elliptic-curve = secp384r1
|
||||
|
||||
# Use a 4096 bit RSA key instead of 2048
|
||||
rsa-key-size = 4096
|
||||
|
||||
|
|
|
|||
|
|
@ -510,7 +510,7 @@ class ReportFailedAuthzrsTest(unittest.TestCase):
|
|||
|
||||
auth_handler._report_failed_authzrs([self.authzr1], 'key')
|
||||
call_list = mock_zope().add_message.call_args_list
|
||||
self.assertTrue(len(call_list) == 1)
|
||||
self.assertEqual(len(call_list), 1)
|
||||
self.assertTrue("Domain: example.com\nType: tls\nDetail: detail" in call_list[0][0][0])
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
|
|
@ -518,7 +518,7 @@ class ReportFailedAuthzrsTest(unittest.TestCase):
|
|||
from certbot._internal import auth_handler
|
||||
|
||||
auth_handler._report_failed_authzrs([self.authzr1, self.authzr2], 'key')
|
||||
self.assertTrue(mock_zope().add_message.call_count == 2)
|
||||
self.assertEqual(mock_zope().add_message.call_count, 2)
|
||||
|
||||
|
||||
def gen_auth_resp(chall_list):
|
||||
|
|
|
|||
|
|
@ -359,6 +359,21 @@ class ParseTest(unittest.TestCase):
|
|||
self.assertFalse(cli.option_was_set(
|
||||
'authenticator', cli.flag_default('authenticator')))
|
||||
|
||||
def test_ecdsa_key_option(self):
|
||||
elliptic_curve_option = 'elliptic_curve'
|
||||
elliptic_curve_option_value = cli.flag_default(elliptic_curve_option)
|
||||
self.parse('--elliptic-curve {0}'.format(elliptic_curve_option_value).split())
|
||||
self.assertIs(cli.option_was_set(elliptic_curve_option, elliptic_curve_option_value), True)
|
||||
|
||||
def test_invalid_key_type(self):
|
||||
key_type_option = 'key_type'
|
||||
key_type_value = cli.flag_default(key_type_option)
|
||||
self.parse('--key-type {0}'.format(key_type_value).split())
|
||||
self.assertIs(cli.option_was_set(key_type_option, key_type_value), True)
|
||||
|
||||
with self.assertRaises(SystemExit):
|
||||
self.parse("--key-type foo")
|
||||
|
||||
def test_encode_revocation_reason(self):
|
||||
for reason, code in constants.REVOCATION_REASONS.items():
|
||||
namespace = self.parse(['--reason', reason])
|
||||
|
|
|
|||
|
|
@ -329,7 +329,11 @@ class ClientTest(ClientTestCommon):
|
|||
self._test_obtain_certificate_common(mock.sentinel.key, csr)
|
||||
|
||||
mock_crypto_util.init_save_key.assert_called_once_with(
|
||||
self.config.rsa_key_size, self.config.key_dir)
|
||||
key_size=self.config.rsa_key_size,
|
||||
key_dir=self.config.key_dir,
|
||||
key_type=self.config.key_type,
|
||||
elliptic_curve=None, # elliptic curve is not set
|
||||
)
|
||||
mock_crypto_util.init_save_csr.assert_called_once_with(
|
||||
mock.sentinel.key, self.eg_domains, self.config.csr_dir)
|
||||
mock_crypto_util.cert_and_chain_from_fullchain.assert_called_once_with(
|
||||
|
|
@ -365,7 +369,11 @@ class ClientTest(ClientTestCommon):
|
|||
self.client.config.dry_run = True
|
||||
self._test_obtain_certificate_common(key, csr)
|
||||
|
||||
mock_crypto.make_key.assert_called_once_with(self.config.rsa_key_size)
|
||||
mock_crypto.make_key.assert_called_once_with(
|
||||
bits=self.config.rsa_key_size,
|
||||
elliptic_curve=None, # not making an elliptic private key
|
||||
key_type=self.config.key_type,
|
||||
)
|
||||
mock_acme_crypto.make_csr.assert_called_once_with(
|
||||
mock.sentinel.key_pem, self.eg_domains, self.config.must_staple)
|
||||
mock_crypto.init_save_key.assert_not_called()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import unittest
|
|||
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock
|
||||
import OpenSSL
|
||||
import zope.component
|
||||
|
|
@ -32,6 +32,7 @@ CERT_LEAF = test_util.load_vector('cert_leaf.pem')
|
|||
CERT_ISSUER = test_util.load_vector('cert_intermediate_1.pem')
|
||||
CERT_ALT_ISSUER = test_util.load_vector('cert_intermediate_2.pem')
|
||||
|
||||
|
||||
class InitSaveKeyTest(test_util.TempDirTestCase):
|
||||
"""Tests for certbot.crypto_util.init_save_key."""
|
||||
def setUp(self):
|
||||
|
|
@ -174,11 +175,54 @@ class ImportCSRFileTest(unittest.TestCase):
|
|||
class MakeKeyTest(unittest.TestCase):
|
||||
"""Tests for certbot.crypto_util.make_key."""
|
||||
|
||||
def test_it(self): # pylint: disable=no-self-use
|
||||
def test_rsa(self): # pylint: disable=no-self-use
|
||||
# RSA Key Type Test
|
||||
from certbot.crypto_util import make_key
|
||||
# Do not test larger keys as it takes too long.
|
||||
OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, make_key(1024))
|
||||
|
||||
def test_ec(self): # pylint: disable=no-self-use
|
||||
# ECDSA Key Type Tests
|
||||
from certbot.crypto_util import make_key
|
||||
# Do not test larger keys as it takes too long.
|
||||
|
||||
# Try a good key size for ECDSA
|
||||
OpenSSL.crypto.load_privatekey(
|
||||
OpenSSL.crypto.FILETYPE_PEM, make_key(1024))
|
||||
OpenSSL.crypto.FILETYPE_PEM, make_key(elliptic_curve="secp256r1", key_type='ecdsa'))
|
||||
|
||||
def test_bad_key_sizes(self):
|
||||
from certbot.crypto_util import make_key
|
||||
# Try a bad key size for RSA and ECDSA
|
||||
with self.assertRaises(errors.Error) as e:
|
||||
make_key(bits=512, key_type='rsa')
|
||||
self.assertEqual(
|
||||
"Unsupported RSA key length: 512",
|
||||
str(e.exception),
|
||||
"Unsupported RSA key length: 512"
|
||||
)
|
||||
|
||||
def test_bad_elliptic_curve_name(self):
|
||||
from certbot.crypto_util import make_key
|
||||
with self.assertRaises(errors.Error) as e:
|
||||
make_key(elliptic_curve="nothere", key_type='ecdsa')
|
||||
self.assertEqual(
|
||||
"Unsupported elliptic curve: nothere",
|
||||
str(e.exception),
|
||||
"Unsupported elliptic curve: nothere"
|
||||
)
|
||||
|
||||
def test_bad_key_type(self):
|
||||
from certbot.crypto_util import make_key
|
||||
|
||||
# Try a bad --key-type
|
||||
with self.assertRaises(errors.Error) as e:
|
||||
OpenSSL.crypto.load_privatekey(
|
||||
OpenSSL.crypto.FILETYPE_PEM, make_key(1024, key_type='unf'))
|
||||
self.assertEqual(
|
||||
"Invalid key_type specified: unf. Use [rsa|ecdsa]",
|
||||
str(e.exception),
|
||||
"Invalid key_type specified: unf. Use [rsa|ecdsa]",
|
||||
)
|
||||
|
||||
|
||||
class VerifyCertSetup(unittest.TestCase):
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class GetEmailTest(unittest.TestCase):
|
|||
mock_input.return_value = (display_util.OK, "foo@bar.baz")
|
||||
with mock.patch("certbot.display.ops.util.safe_email") as mock_safe_email:
|
||||
mock_safe_email.return_value = True
|
||||
self.assertTrue(self._call() == "foo@bar.baz")
|
||||
self.assertEqual(self._call(), "foo@bar.baz")
|
||||
|
||||
@test_util.patch_get_utility("certbot.display.ops.z_util")
|
||||
def test_ok_not_safe(self, mock_get_utility):
|
||||
|
|
@ -51,7 +51,7 @@ class GetEmailTest(unittest.TestCase):
|
|||
mock_input.return_value = (display_util.OK, "foo@bar.baz")
|
||||
with mock.patch("certbot.display.ops.util.safe_email") as mock_safe_email:
|
||||
mock_safe_email.side_effect = [False, True]
|
||||
self.assertTrue(self._call() == "foo@bar.baz")
|
||||
self.assertEqual(self._call(), "foo@bar.baz")
|
||||
|
||||
@test_util.patch_get_utility("certbot.display.ops.z_util")
|
||||
def test_invalid_flag(self, mock_get_utility):
|
||||
|
|
|
|||
|
|
@ -49,14 +49,51 @@ RSA2048_KEY_PATH = test_util.vector_path('rsa2048_key.pem')
|
|||
SS_CERT_PATH = test_util.vector_path('cert_2048.pem')
|
||||
|
||||
|
||||
class TestHandleIdenticalCerts(unittest.TestCase):
|
||||
"""Test for certbot._internal.main._handle_identical_cert_request"""
|
||||
def test_handle_identical_cert_request_pending(self):
|
||||
class TestHandleCerts(unittest.TestCase):
|
||||
"""Test for certbot._internal.main._handle_* methods"""
|
||||
@mock.patch("certbot._internal.main._handle_unexpected_key_type_migration")
|
||||
def test_handle_identical_cert_request_pending(self, mock_handle_migration):
|
||||
mock_lineage = mock.Mock()
|
||||
mock_lineage.ensure_deployed.return_value = False
|
||||
# pylint: disable=protected-access
|
||||
ret = main._handle_identical_cert_request(mock.Mock(), mock_lineage)
|
||||
self.assertEqual(ret, ("reinstall", mock_lineage))
|
||||
self.assertTrue(mock_handle_migration.called)
|
||||
|
||||
@mock.patch("certbot._internal.main._handle_unexpected_key_type_migration")
|
||||
def test_handle_subset_cert_request(self, mock_handle_migration):
|
||||
mock_config = mock.Mock()
|
||||
mock_config.expand = True
|
||||
mock_lineage = mock.Mock()
|
||||
mock_lineage.names.return_value = ["dummy1", "dummy2"]
|
||||
ret = main._handle_subset_cert_request(mock_config, ["dummy1"], mock_lineage)
|
||||
self.assertEqual(ret, ("renew", mock_lineage))
|
||||
self.assertTrue(mock_handle_migration.called)
|
||||
|
||||
@mock.patch("certbot._internal.main.cli.set_by_cli")
|
||||
def test_handle_unexpected_key_type_migration(self, mock_set):
|
||||
config = mock.Mock()
|
||||
config.key_type = "rsa"
|
||||
cert = mock.Mock()
|
||||
cert.private_key_type = "ecdsa"
|
||||
|
||||
mock_set.return_value = True
|
||||
main._handle_unexpected_key_type_migration(config, cert)
|
||||
|
||||
mock_set.return_value = False
|
||||
with self.assertRaises(errors.Error) as raised:
|
||||
main._handle_unexpected_key_type_migration(config, cert)
|
||||
self.assertTrue("Please provide both --cert-name and --key-type" in str(raised.exception))
|
||||
|
||||
mock_set.side_effect = lambda var: var != "certname"
|
||||
with self.assertRaises(errors.Error) as raised:
|
||||
main._handle_unexpected_key_type_migration(config, cert)
|
||||
self.assertTrue("Please provide both --cert-name and --key-type" in str(raised.exception))
|
||||
|
||||
mock_set.side_effect = lambda var: var != "key_type"
|
||||
with self.assertRaises(errors.Error) as raised:
|
||||
main._handle_unexpected_key_type_migration(config, cert)
|
||||
self.assertTrue("Please provide both --cert-name and --key-type" in str(raised.exception))
|
||||
|
||||
|
||||
class RunTest(test_util.ConfigTestCase):
|
||||
|
|
@ -163,31 +200,35 @@ class CertonlyTest(unittest.TestCase):
|
|||
@mock.patch('certbot._internal.cert_manager.lineage_for_certname')
|
||||
@mock.patch('certbot._internal.cert_manager.domains_for_certname')
|
||||
@mock.patch('certbot._internal.renewal.renew_cert')
|
||||
@mock.patch('certbot._internal.main._handle_unexpected_key_type_migration')
|
||||
@mock.patch('certbot._internal.main._report_new_cert')
|
||||
def test_find_lineage_for_domains_and_certname(self, mock_report_cert,
|
||||
mock_renew_cert, mock_domains, mock_lineage):
|
||||
mock_handle_type, mock_renew_cert, mock_domains, mock_lineage):
|
||||
domains = ['example.com', 'test.org']
|
||||
mock_domains.return_value = domains
|
||||
mock_lineage.names.return_value = domains
|
||||
self._call(('certonly --webroot -d example.com -d test.org '
|
||||
'--cert-name example.com').split())
|
||||
self.assertTrue(mock_lineage.call_count == 1)
|
||||
self.assertTrue(mock_domains.call_count == 1)
|
||||
self.assertTrue(mock_renew_cert.call_count == 1)
|
||||
self.assertTrue(mock_report_cert.call_count == 1)
|
||||
|
||||
self.assertEqual(mock_lineage.call_count, 1)
|
||||
self.assertEqual(mock_domains.call_count, 1)
|
||||
self.assertEqual(mock_renew_cert.call_count, 1)
|
||||
self.assertEqual(mock_report_cert.call_count, 1)
|
||||
self.assertEqual(mock_handle_type.call_count, 1)
|
||||
|
||||
# user confirms updating lineage with new domains
|
||||
self._call(('certonly --webroot -d example.com -d test.com '
|
||||
'--cert-name example.com').split())
|
||||
self.assertTrue(mock_lineage.call_count == 2)
|
||||
self.assertTrue(mock_domains.call_count == 2)
|
||||
self.assertTrue(mock_renew_cert.call_count == 2)
|
||||
self.assertTrue(mock_report_cert.call_count == 2)
|
||||
self.assertEqual(mock_lineage.call_count, 2)
|
||||
self.assertEqual(mock_domains.call_count, 2)
|
||||
self.assertEqual(mock_renew_cert.call_count, 2)
|
||||
self.assertEqual(mock_report_cert.call_count, 2)
|
||||
self.assertEqual(mock_handle_type.call_count, 2)
|
||||
|
||||
# error in _ask_user_to_confirm_new_names
|
||||
self.mock_get_utility().yesno.return_value = False
|
||||
self.assertRaises(errors.ConfigurationError, self._call,
|
||||
('certonly --webroot -d example.com -d test.com --cert-name example.com').split())
|
||||
'certonly --webroot -d example.com -d test.com --cert-name example.com'.split())
|
||||
|
||||
@mock.patch('certbot._internal.cert_manager.domains_for_certname')
|
||||
@mock.patch('certbot.display.ops.choose_names')
|
||||
|
|
@ -200,14 +241,15 @@ class CertonlyTest(unittest.TestCase):
|
|||
# no lineage with this name but we specified domains so create a new cert
|
||||
self._call(('certonly --webroot -d example.com -d test.com '
|
||||
'--cert-name example.com').split())
|
||||
self.assertTrue(mock_lineage.call_count == 1)
|
||||
self.assertTrue(mock_report_cert.call_count == 1)
|
||||
self.assertEqual(mock_lineage.call_count, 1)
|
||||
self.assertEqual(mock_report_cert.call_count, 1)
|
||||
|
||||
# no lineage with this name and we didn't give domains
|
||||
mock_choose_names.return_value = ["somename"]
|
||||
mock_domains_for_certname.return_value = None
|
||||
self._call(('certonly --webroot --cert-name example.com').split())
|
||||
self.assertTrue(mock_choose_names.called)
|
||||
self.assertIs(mock_choose_names.called, True)
|
||||
|
||||
|
||||
class FindDomainsOrCertnameTest(unittest.TestCase):
|
||||
"""Tests for certbot._internal.main._find_domains_or_certname."""
|
||||
|
|
@ -718,7 +760,7 @@ class MainTest(test_util.ConfigTestCase):
|
|||
# This needed two calls to find_all(), which we're avoiding for now
|
||||
# because of possible side effects:
|
||||
# https://github.com/letsencrypt/letsencrypt/commit/51ed2b681f87b1eb29088dd48718a54f401e4855
|
||||
#with mock.patch('certbot._internal.cli.plugins_testable') as plugins:
|
||||
# with mock.patch('certbot._internal.cli.plugins_testable') as plugins:
|
||||
# plugins.return_value = {"apache": True, "nginx": True}
|
||||
# ret, _, _, _ = self._call(args)
|
||||
# self.assertTrue("Too many flags setting" in ret)
|
||||
|
|
@ -985,6 +1027,7 @@ class MainTest(test_util.ConfigTestCase):
|
|||
mock_lineage.should_autorenew.return_value = due_for_renewal
|
||||
mock_lineage.has_pending_deployment.return_value = False
|
||||
mock_lineage.names.return_value = ['isnot.org']
|
||||
mock_lineage.private_key_type = 'RSA'
|
||||
mock_certr = mock.MagicMock()
|
||||
mock_key = mock.MagicMock(pem='pem_key')
|
||||
mock_client = mock.MagicMock()
|
||||
|
|
|
|||
|
|
@ -233,11 +233,11 @@ class AddrTest(unittest.TestCase):
|
|||
def test_eq(self):
|
||||
self.assertEqual(self.addr1, self.addr2.get_addr_obj(""))
|
||||
self.assertNotEqual(self.addr1, self.addr2)
|
||||
self.assertFalse(self.addr1 == 3333)
|
||||
self.assertNotEqual(self.addr1, 3333)
|
||||
|
||||
self.assertEqual(self.addr4, self.addr4.get_addr_obj(""))
|
||||
self.assertNotEqual(self.addr4, self.addr5)
|
||||
self.assertFalse(self.addr4 == 3333)
|
||||
self.assertNotEqual(self.addr4, 3333)
|
||||
from certbot.plugins.common import Addr
|
||||
self.assertEqual(self.addr4, Addr.fromstring("[fe00:0:0::1]"))
|
||||
self.assertEqual(self.addr4, Addr.fromstring("[fe00:0::0:0:1]"))
|
||||
|
|
|
|||
|
|
@ -74,6 +74,30 @@ class RenewalTest(test_util.ConfigTestCase):
|
|||
|
||||
assert self.config.rsa_key_size == 2048
|
||||
|
||||
def test_reuse_ec_key_renewal_params(self):
|
||||
self.config.elliptic_curve = 'INVALID_CURVE'
|
||||
self.config.reuse_key = True
|
||||
self.config.dry_run = True
|
||||
self.config.key_type = 'ecdsa'
|
||||
config = configuration.NamespaceConfig(self.config)
|
||||
|
||||
rc_path = test_util.make_lineage(
|
||||
self.config.config_dir,
|
||||
'sample-renewal-ec.conf',
|
||||
ec=True,
|
||||
)
|
||||
lineage = storage.RenewableCert(rc_path, config)
|
||||
|
||||
le_client = mock.MagicMock()
|
||||
le_client.obtain_certificate.return_value = (None, None, None, None)
|
||||
|
||||
from certbot._internal import renewal
|
||||
|
||||
with mock.patch('certbot._internal.renewal.hooks.renew_hook'):
|
||||
renewal.renew_cert(self.config, None, le_client, lineage)
|
||||
|
||||
assert self.config.elliptic_curve == 'secp256r1'
|
||||
|
||||
|
||||
class RestoreRequiredConfigElementsTest(test_util.ConfigTestCase):
|
||||
"""Tests for certbot._internal.renewal.restore_required_config_elements."""
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ class LockDirUntilExit(test_util.TempDirTestCase):
|
|||
from certbot import util
|
||||
# Despite lock_dir_until_exit has been called twice to subdir, its lock should have been
|
||||
# added only once. So we expect to have two lock references: for self.tempdir and subdir
|
||||
self.assertTrue(len(util._LOCKS) == 2) # pylint: disable=protected-access
|
||||
self.assertEqual(len(util._LOCKS), 2) # pylint: disable=protected-access
|
||||
registered_func() # Exception should not be raised
|
||||
# Logically, logger.debug, that would be invoked in case of unlock failure,
|
||||
# should never been called.
|
||||
|
|
|
|||
Loading…
Reference in a new issue