mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 02:54:34 +02:00
Add naive JWK Thumbprint implementation
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
"""JSON Web Key."""
|
"""JSON Web Key."""
|
||||||
import abc
|
import abc
|
||||||
import binascii
|
import binascii
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import cryptography.exceptions
|
import cryptography.exceptions
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
from cryptography.hazmat.primitives import serialization
|
from cryptography.hazmat.primitives import serialization
|
||||||
from cryptography.hazmat.primitives.asymmetric import ec
|
from cryptography.hazmat.primitives.asymmetric import ec
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
@@ -27,6 +29,32 @@ class JWK(json_util.TypedJSONObjectWithFields):
|
|||||||
cryptography_key_types = ()
|
cryptography_key_types = ()
|
||||||
"""Subclasses should override."""
|
"""Subclasses should override."""
|
||||||
|
|
||||||
|
required = NotImplemented
|
||||||
|
"""Required members of public key's representation as defined by JWK/JWA."""
|
||||||
|
|
||||||
|
_thumbprint_json_dumps_params = {
|
||||||
|
# "no whitespace or line breaks before or after any syntactic
|
||||||
|
# elements"
|
||||||
|
'indent': 0,
|
||||||
|
'separators': (',', ':'),
|
||||||
|
# "members ordered lexicographically by the Unicode [UNICODE]
|
||||||
|
# code points of the member names"
|
||||||
|
'sort_keys': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
def thumbprint(self, hash_function=hashes.SHA256):
|
||||||
|
"""Compute JWK Thumbprint.
|
||||||
|
|
||||||
|
https://tools.ietf.org/html/rfc7638
|
||||||
|
|
||||||
|
"""
|
||||||
|
digest = hashes.Hash(hash_function(), backend=default_backend())
|
||||||
|
digest.update(json.dumps(
|
||||||
|
dict((k, v) for k, v in six.iteritems(self.to_json())
|
||||||
|
if k in self.required),
|
||||||
|
**self._thumbprint_json_dumps_params).encode())
|
||||||
|
return digest.finalize()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def public_key(self): # pragma: no cover
|
def public_key(self): # pragma: no cover
|
||||||
"""Generate JWK with public key.
|
"""Generate JWK with public key.
|
||||||
@@ -105,6 +133,7 @@ class JWKES(JWK): # pragma: no cover
|
|||||||
typ = 'ES'
|
typ = 'ES'
|
||||||
cryptography_key_types = (
|
cryptography_key_types = (
|
||||||
ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey)
|
ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey)
|
||||||
|
required = ('crv', JWK.type_field_name, 'x', 'y')
|
||||||
|
|
||||||
def fields_to_partial_json(self):
|
def fields_to_partial_json(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@@ -122,6 +151,7 @@ class JWKOct(JWK):
|
|||||||
"""Symmetric JWK."""
|
"""Symmetric JWK."""
|
||||||
typ = 'oct'
|
typ = 'oct'
|
||||||
__slots__ = ('key',)
|
__slots__ = ('key',)
|
||||||
|
required = ('k', JWK.type_field_name)
|
||||||
|
|
||||||
def fields_to_partial_json(self):
|
def fields_to_partial_json(self):
|
||||||
# TODO: An "alg" member SHOULD also be present to identify the
|
# TODO: An "alg" member SHOULD also be present to identify the
|
||||||
@@ -150,6 +180,7 @@ class JWKRSA(JWK):
|
|||||||
typ = 'RSA'
|
typ = 'RSA'
|
||||||
cryptography_key_types = (rsa.RSAPublicKey, rsa.RSAPrivateKey)
|
cryptography_key_types = (rsa.RSAPublicKey, rsa.RSAPrivateKey)
|
||||||
__slots__ = ('key',)
|
__slots__ = ('key',)
|
||||||
|
required = ('e', JWK.type_field_name, 'n')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
if 'key' in kwargs and not isinstance(
|
if 'key' in kwargs and not isinstance(
|
||||||
|
|||||||
@@ -25,9 +25,24 @@ class JWKTest(unittest.TestCase):
|
|||||||
self.assertRaises(errors.Error, JWKRSA.load, DSA_PEM)
|
self.assertRaises(errors.Error, JWKRSA.load, DSA_PEM)
|
||||||
|
|
||||||
|
|
||||||
class JWKOctTest(unittest.TestCase):
|
class JWKTestBaseMixin(object):
|
||||||
|
"""Mixin test for JWK subclass tests."""
|
||||||
|
|
||||||
|
thumbprint = NotImplemented
|
||||||
|
|
||||||
|
def test_thumbprint_private(self):
|
||||||
|
self.assertEqual(self.thumbprint, self.jwk.thumbprint())
|
||||||
|
|
||||||
|
def test_thumbprint_public(self):
|
||||||
|
self.assertEqual(self.thumbprint, self.jwk.public_key().thumbprint())
|
||||||
|
|
||||||
|
|
||||||
|
class JWKOctTest(unittest.TestCase, JWKTestBaseMixin):
|
||||||
"""Tests for acme.jose.jwk.JWKOct."""
|
"""Tests for acme.jose.jwk.JWKOct."""
|
||||||
|
|
||||||
|
thumbprint = (b"=,\xdd;I\x1a+i\x02x\x8a\x12?06IM\xc2\x80"
|
||||||
|
b"\xe4\xc3\x1a\xfc\x89\xf3)'\xce\xccm\xfd5")
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
from acme.jose.jwk import JWKOct
|
from acme.jose.jwk import JWKOct
|
||||||
self.jwk = JWKOct(key=b'foo')
|
self.jwk = JWKOct(key=b'foo')
|
||||||
@@ -52,10 +67,13 @@ class JWKOctTest(unittest.TestCase):
|
|||||||
self.assertTrue(self.jwk.public_key() is self.jwk)
|
self.assertTrue(self.jwk.public_key() is self.jwk)
|
||||||
|
|
||||||
|
|
||||||
class JWKRSATest(unittest.TestCase):
|
class JWKRSATest(unittest.TestCase, JWKTestBaseMixin):
|
||||||
"""Tests for acme.jose.jwk.JWKRSA."""
|
"""Tests for acme.jose.jwk.JWKRSA."""
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
|
||||||
|
thumbprint = (b'\x08\xfa1\x87\x1d\x9b6H/*\x1eW\xc2\xe3\xf6P'
|
||||||
|
b'\xefs\x0cKB\x87\xcf\x85yO\x045\x0e\x91\x80\x0b')
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
from acme.jose.jwk import JWKRSA
|
from acme.jose.jwk import JWKRSA
|
||||||
self.jwk256 = JWKRSA(key=RSA256_KEY.public_key())
|
self.jwk256 = JWKRSA(key=RSA256_KEY.public_key())
|
||||||
@@ -87,6 +105,7 @@ class JWKRSATest(unittest.TestCase):
|
|||||||
'dq': 'bHh2u7etM8LKKCF2pY2UdQ',
|
'dq': 'bHh2u7etM8LKKCF2pY2UdQ',
|
||||||
'qi': 'oi45cEkbVoJjAbnQpFY87Q',
|
'qi': 'oi45cEkbVoJjAbnQpFY87Q',
|
||||||
})
|
})
|
||||||
|
self.jwk = self.private
|
||||||
|
|
||||||
def test_init_auto_comparable(self):
|
def test_init_auto_comparable(self):
|
||||||
self.assertTrue(isinstance(
|
self.assertTrue(isinstance(
|
||||||
|
|||||||
Reference in New Issue
Block a user