More coverage for crypto_util

This commit is contained in:
Jakub Warmuz
2014-12-10 17:07:12 +01:00
parent 2322266b98
commit a0a81bf533
7 changed files with 178 additions and 33 deletions
+35 -33
View File
@@ -1,6 +1,5 @@
"""Let's Encrypt client crypto utility functions"""
import binascii
import hashlib
import logging
import time
@@ -15,8 +14,6 @@ from letsencrypt.client import CONFIG
from letsencrypt.client import le_util
# TODO: All of these functions need unit tests
def b64_cert_to_pem(b64_der_cert):
return M2Crypto.X509.load_cert_der_string(
le_util.jose_b64decode(b64_der_cert)).as_pem()
@@ -76,27 +73,32 @@ def leading_zeros(arg):
return arg
def sha256(arg):
return hashlib.sha256(arg).hexdigest()
# based on M2Crypto unit test written by Toby Allsopp
def make_key(bits=CONFIG.RSA_KEY_SIZE):
"""Generate PEM encoded RSA key.
:param int bits: Number of bits.
:returns: new RSA key in PEM form with specified number of bits
:rtype: str
"""
Returns new RSA key in PEM form with specified bits
"""
# Python Crypto module doesn't produce any stdout
key = Crypto.PublicKey.RSA.generate(bits)
# rsa = M2Crypto.RSA.gen_key(bits, 65537)
# key_pem = rsa.as_pem(cipher=None)
# rsa = None # should not be freed here
return key.exportKey(format='PEM')
# Python Crypto module doesn't produce any stdout
return Crypto.PublicKey.RSA.generate(bits).exportKey(format='PEM')
def make_csr(key_str, domains):
"""
Returns new CSR in PEM and DER form using key_file containing all domains
"""Generate a CSR.
:param str key_str: RSA key.
:param list domains: Domains included in the certificate.
:returns: new CSR in PEM and DER form containing all domains
:rtype: tuple
"""
assert domains, "Must provide one or more hostnames for the CSR."
rsa_key = M2Crypto.RSA.load_key_string(key_str)
@@ -115,7 +117,7 @@ def make_csr(key_str, domains):
extstack = M2Crypto.X509.X509_Extension_Stack()
ext = M2Crypto.X509.new_extension(
'subjectAltName', ", ".join(["DNS:%s" % d for d in domains]))
'subjectAltName', ", ".join("DNS:%s" % d for d in domains))
extstack.push(ext)
csr.add_extensions(extstack)
@@ -210,7 +212,7 @@ def valid_csr(csr):
Check if `csr` is a valid CSR for the given domains.
:param str csr: CSR file contents
:param str csr: CSR in PEM.
:returns: Validity of CSR.
:rtype: bool
@@ -229,7 +231,7 @@ def csr_matches_names(csr, domains):
M2Crypto currently does not expose the OpenSSL interface to
also check the SAN extension. This is insufficient for full testing
:param str csr: CSR file contents
:param str csr: CSR in DER.
:param list domains: Domains the CSR should contain.
@@ -244,6 +246,21 @@ def csr_matches_names(csr, domains):
return False
def csr_matches_pubkey(csr, privkey):
"""Does private key correspond to the subject public key in the CSR?
:param str csr: CSR in PEM.
:param str privkey: Private key file contents
:returns: Correspondence of private key to CSR subject public key.
:rtype: bool
"""
csr_obj = M2Crypto.X509.load_request_string(csr)
privkey_obj = M2Crypto.RSA.load_key_string(privkey)
return csr_obj.get_pubkey().get_rsa().pub() == privkey_obj.pub()
def valid_privkey(privkey):
"""Is valid RSA private key?
@@ -257,18 +274,3 @@ def valid_privkey(privkey):
return bool(M2Crypto.RSA.load_key_string(privkey).check_key())
except M2Crypto.RSA.RSAError:
return False
def csr_matches_pubkey(csr, privkey):
"""Does private key correspond to the subject public key in the CSR?
:param str csr: CSR file contents
:param str privkey: Private key file contents
:returns: Correspondence of private key to CSR subject public key.
:rtype: bool
"""
csr_obj = M2Crypto.X509.load_request_string(csr)
privkey_obj = M2Crypto.RSA.load_key_string(privkey)
return csr_obj.get_pubkey().get_rsa().pub() == privkey_obj.pub()
@@ -1,5 +1,7 @@
"""Tests for letsencrypt.client.crypto_util."""
import os
import pkg_resources
import tempfile
import unittest
@@ -40,5 +42,117 @@ class CreateSigTest(unittest.TestCase):
del self.signature['nonce']
self.assertEqual(signature, self.signature)
class MakeCSRTest(unittest.TestCase):
"""Tests for letsencrypt.client.crypto_util.make_csr."""
def setUp(self):
self.key = pkg_resources.resource_string(
__name__, 'testdata/rsa256_key.pem')
def test_single_domain(self):
from letsencrypt.client.crypto_util import make_csr
pem, der = make_csr(self.key, ['example.com'])
self.assertEqual(pem, pkg_resources.resource_string(
__name__, 'testdata/csr.pem'))
self.assertEqual(der, pkg_resources.resource_string(
__name__, 'testdata/csr.der'))
def test_san(self):
from letsencrypt.client.crypto_util import make_csr
pem, der = make_csr(self.key, ['example.com', 'www.example.com'])
self.assertEqual(pem, pkg_resources.resource_string(
__name__, 'testdata/csr-san.pem'))
self.assertEqual(der, pkg_resources.resource_string(
__name__, 'testdata/csr-san.der'))
class ValidCSRTest(unittest.TestCase):
"""Tests for letsencrypt.client.crypto_util.valid_csr."""
def _call(self, csr):
from letsencrypt.client.crypto_util import valid_csr
return valid_csr(csr)
def _call_testdata(self, name):
return self._call(pkg_resources.resource_string(
__name__, os.path.join('testdata', name)))
def test_valid_pem_true(self):
self.assertTrue(self._call_testdata('csr.pem'))
def test_valid_pem_san_true(self):
self.assertTrue(self._call_testdata('csr-san.pem'))
def test_valid_der_false(self):
self.assertFalse(self._call_testdata('csr.der'))
def test_valid_der_san_false(self):
self.assertFalse(self._call_testdata('csr-san.der'))
def test_empty_false(self):
self.assertFalse(self._call(''))
def test_rubbis_false(self):
self.assertFalse(self._call('foo bar'))
class CSRMatchesNamesTest(unittest.TestCase):
"""Tests for letsencrypt.client.crypto_util.csr_matches_names."""
def _call(self, csr, domains):
from letsencrypt.client.crypto_util import csr_matches_names
return csr_matches_names(csr, domains)
def _call_testdata(self, name, domains):
return self._call(pkg_resources.resource_string(
__name__, os.path.join('testdata', name)), domains)
def test_it(self):
self.assertTrue(self._call_testdata('csr.der', ['example.com']))
self.assertFalse(self._call_testdata('csr.der', ['www.example.com']))
self.assertFalse(self._call_testdata('csr.der', ['example']))
def test_san(self):
self.assertTrue(self._call_testdata('csr-san.der', ['example.com']))
self.assertTrue(self._call_testdata('csr-san.der', ['www.example.com']))
self.assertFalse(self._call_testdata('csr-san.der', ['example']))
class CSRMatchesPubkeyTest(unittest.TestCase):
"""Tests for letsencrypt.client.crypto_util.csr_matches_pubkey."""
def _call_testdata(self, name, privkey):
from letsencrypt.client.crypto_util import csr_matches_pubkey
return csr_matches_pubkey(pkg_resources.resource_string(
__name__, os.path.join('testdata', name)), privkey)
def test_valid_true(self):
key = pkg_resources.resource_string(__name__, 'testdata/rsa256_key.pem')
self.assertTrue(self._call_testdata('csr.pem', key))
def test_invalid_false(self):
key = pkg_resources.resource_string(__name__, 'testdata/rsa512_key.pem')
self.assertFalse(self._call_testdata('csr.pem', key))
class ValidPrivkeyTest(unittest.TestCase):
"""Tests fro letsencrypt.client.crypto_util.valid_privkey."""
def _call(self, privkey):
from letsencrypt.client.crypto_util import valid_privkey
return valid_privkey(privkey)
def test_valid_true(self):
self.assertTrue(self._call(pkg_resources.resource_string(
__name__, 'testdata/rsa256_key.pem')))
def test_empty_false(self):
self.assertFalse(self._call(''))
def test_rubbish_false(self):
self.assertFalse(self._call('foo bar'))
if __name__ == '__main__':
unittest.main()
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBbjCCARgCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw
EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy
c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG
9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f
p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoDowOAYJKoZIhvcN
AQkOMSswKTAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29t
MA0GCSqGSIb3DQEBCwUAA0EAZGBM8J1rRs7onFgtc76mOeoT1c3v0ZsEmxQfb2Wy
tmReY6X1N4cs38D9VSow+VMRu2LWkKvzS7RUFSaTaeQz1A==
-----END CERTIFICATE REQUEST-----
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBXTCCAQcCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw
EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy
c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG
9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f
p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoCkwJwYJKoZIhvcN
AQkOMRowGDAWBgNVHREEDzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAANB
AHJH/O6BtC9aGzEVCMGOZ7z9iIRHWSzr9x/bOzn7hLwsbXPAgO1QxEwL+X+4g20G
n9XBE1N9W6HCIEut2d8wACg=
-----END CERTIFICATE REQUEST-----
+9
View File
@@ -0,0 +1,9 @@
-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBAPS2EXFRNza/qpXnnBHF/CcFQ543htV+7nLAmrLrmTNHtPXJmLlM
8SJDIzv/ceAFXL110VzxFfi81lpH5E5c0TMCAwEAAQJBALmppYQ/JVARjWBcsEm/
1/bXBJ127YLv4gQIY5baL4r6IdEE33OXMTTmD9wf+ajuq1eaH0htHkwhOvREu0sz
bskCIQD/Cg+xhEVLcwK3pFp3afPIhj1IPFiL3Uy/nqyMZ6O/RQIhAPWiDBofp7Cp
J4dGZs+hkRySq/IOeeRJlNK1Pq64nToXAiBZ7+te1100YSd5KT051SRB94zO13EG
SZESFduVW8rz3QIgK+tLiqg6TYYRQUi/PUTAM4GuKNuZw828RGiPyqHLywUCIQCd
pkZrNphL/y0D7HSbPIfZzD90M2V8tUjlK0BTqk1bHA==
-----END RSA PRIVATE KEY-----