summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimo Sorce <simo@redhat.com>2015-03-04 21:25:09 -0500
committerSimo Sorce <simo@redhat.com>2015-03-06 01:34:43 -0500
commit9a36f12f15552467ccdaa855aa036f73a7305396 (patch)
tree58f64495dd07bde7c861852e0a873c8c423d378d
parenteb1fb55ac331a2f7d73acd7f3034617cdcdff41e (diff)
downloadjwcrypto-9a36f12f15552467ccdaa855aa036f73a7305396.tar.gz
jwcrypto-9a36f12f15552467ccdaa855aa036f73a7305396.tar.xz
jwcrypto-9a36f12f15552467ccdaa855aa036f73a7305396.zip
Add JWK implementation
Implements: draft-ietf-jose-json-web-key-41 plus Tests Signed-off-by: Simo Sorce <simo@redhat.com>
-rw-r--r--jwcrypto/jwk.py254
-rw-r--r--jwcrypto/tests.py178
2 files changed, 432 insertions, 0 deletions
diff --git a/jwcrypto/jwk.py b/jwcrypto/jwk.py
new file mode 100644
index 0000000..25744ef
--- /dev/null
+++ b/jwcrypto/jwk.py
@@ -0,0 +1,254 @@
+# Copyright (C) 2015 JWCrypto Project Contributors - see LICENSE file
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric import rsa
+from cryptography.hazmat.primitives.asymmetric import ec
+from jwcrypto.common import base64url_decode
+import json
+
+# draft-ietf-jose-json-web-algorithms-24 - 7.4
+JWKTypesRegistry = {'EC': 'Elliptic Curve',
+ 'RSA': 'RSA',
+ 'oct': 'Octet sequence'}
+
+# draft-ietf-jose-json-web-algorithms-24 - 7.5
+# It is part of the JWK Parameters Registry, but we want a more
+# specific map for internal usage
+JWKValuesRegistry = {'EC': {'crv': ('Curve', 'Public'),
+ 'x': ('X Coordinate', 'Public'),
+ 'y': ('Y Coordinate', 'Public'),
+ 'd': ('ECC Private Key', 'Private')},
+ 'RSA': {'n': ('Modulus', 'Public'),
+ 'e': ('Exponent', 'Public'),
+ 'd': ('Private Exponent', 'Private'),
+ 'p': ('First Prime Factor', 'Private'),
+ 'q': ('Second Prime Factor', 'Private'),
+ 'dp': ('First Factor CRT Exponent', 'Private'),
+ 'dq': ('Second Factor CRT Exponent', 'Private'),
+ 'qi': ('First CRT Coefficient', 'Private')},
+ 'oct': {'k': ('Key Value', 'Private')}}
+
+JWKParamsRegistry = {'kty': ('Key Type', 'Public', ),
+ 'use': ('Public Key Use', 'Public'),
+ 'key_ops': ('Key Operations', 'Public'),
+ 'alg': ('Algorithm', 'Public'),
+ 'kid': ('Key ID', 'Public'),
+ 'x5u': ('X.509 URL', 'Public'),
+ 'x5c': ('X.509 Certificate Chain', 'Public'),
+ 'x5t': ('X.509 Certificate SHA-1 Thumbprint', 'Public'),
+ 'x5t#S256': ('X.509 Certificate SHA-256 Thumbprint',
+ 'Public')}
+
+# draft-ietf-jose-json-web-algorithms-24 - 7.6
+JWKEllipticCurveRegistry = {'P-256': 'P-256 curve',
+ 'P-384': 'P-384 curve',
+ 'P-521': 'P-521 curve'}
+
+# draft-ietf-jose-json-web-key-41 - 8.2
+JWKUseRegistry = {'sig': 'Digital Signature or MAC',
+ 'enc': 'Encryption'}
+
+# draft-ietf-jose-json-web-key-41 - 8.2
+JWKOperationsRegistry = {'sign': 'Compute digital Signature or MAC',
+ 'verify': 'Verify digital signature or MAC',
+ 'encrypt': 'Encrypt content',
+ 'decrypt': 'Decrypt content and validate'
+ ' decryption, if applicable',
+ 'wrapKey': 'Encrypt key',
+ 'unwrapKey': 'Decrypt key and validate'
+ ' decryption, if applicable',
+ 'deriveKey': 'Derive key',
+ 'deriveBits': 'Derive bits not to be used as a key'}
+
+
+class InvalidJWKType(Exception):
+
+ def __init__(self, value=None):
+ super(InvalidJWKType, self).__init__()
+ self.value = value
+
+ def __str__(self):
+ return 'Unknown type "%s", valid types are: %s' % (
+ self.value, JWKTypesRegistry.keys())
+
+
+class InvalidJWKUsage(Exception):
+
+ def __init__(self, use, value):
+ super(InvalidJWKUsage, self).__init__()
+ self.value = value
+ self.use = use
+
+ def __str__(self):
+ if self.use in JWKUseRegistry.keys():
+ usage = JWKUseRegistry[self.use]
+ else:
+ usage = 'Unknown(%s)' % self.use
+ if self.value in JWKUseRegistry.keys():
+ valid = JWKUseRegistry[self.value]
+ else:
+ valid = 'Unknown(%s)' % self.value
+ return 'Invalid usage requested: "%s". Valid for: "%s"' % (usage,
+ valid)
+
+
+class InvalidJWKOperation(Exception):
+
+ def __init__(self, operation, values):
+ super(InvalidJWKOperation, self).__init__()
+ self.op = operation
+ self.values = values
+
+ def __str__(self):
+ if self.op in JWKOperationsRegistry.keys():
+ op = JWKOperationsRegistry[self.op]
+ else:
+ op = 'Unknown(%s)' % self.op
+ valid = list()
+ for v in self.values:
+ if v in JWKOperationsRegistry.keys():
+ valid.append(JWKOperationsRegistry[v])
+ else:
+ valid.append('Unknown(%s)' % v)
+ return 'Invalid operation requested: "%s". Valid for: "%s"' % (op,
+ valid)
+
+
+class InvalidJWKValue(Exception):
+ pass
+
+
+class JWK(object):
+
+ def __init__(self, **kwargs):
+
+ names = kwargs.keys()
+
+ self._params = dict()
+ for name in JWKParamsRegistry.keys():
+ if name in kwargs:
+ self._params[name] = kwargs[name]
+ while name in names:
+ names.remove(name)
+
+ kty = self._params.get('kty', None)
+ if kty not in JWKTypesRegistry:
+ raise InvalidJWKType(kty)
+
+ self._key = dict()
+ for name in JWKValuesRegistry[kty].keys():
+ if name in kwargs:
+ self._key[name] = kwargs[name]
+ while name in names:
+ names.remove(name)
+
+ if len(names) != 0:
+ raise InvalidJWKValue('Unknown key parameters: %s' % names)
+
+ if len(self._key) == 0:
+ raise InvalidJWKValue('No Key Values found')
+
+ def export(self):
+ d = dict()
+ d.update(self._params)
+ d.update(self._key)
+ return json.dumps(d)
+
+ @property
+ def key_id(self):
+ return self._params.get('kid', None)
+
+ def get_curve(self, arg):
+ k = self._key
+ if self._params['kty'] != 'EC':
+ raise InvalidJWKType('Not an EC key')
+ if arg and k['crv'] != arg:
+ raise InvalidJWKValue('Curve requested is "%s", but '
+ 'key curve is "%s"' % (arg, k['crv']))
+ if k['crv'] == 'P-256':
+ return ec.SECP256R1()
+ elif k['crv'] == 'P-384':
+ return ec.SECP384R1()
+ elif k['crv'] == 'P-521':
+ return ec.SECP521R1()
+ else:
+ raise InvalidJWKValue('Unknown Elliptic Curve Type')
+
+ def _check_constraints(self, usage, operation):
+ use = self._params.get('use', None)
+ if use and use != usage:
+ raise InvalidJWKUsage(usage, use)
+ ops = self._params.get('key_ops', None)
+ if ops:
+ if not isinstance(ops, list):
+ ops = [ops]
+ if operation not in ops:
+ raise InvalidJWKOperation(operation, ops)
+ # TODO: check alg ?
+
+ def _decode_int(self, n):
+ return int(base64url_decode(n).encode('hex'), 16)
+
+ def sign_key(self, arg=None):
+ self._check_constraints('sig', 'sign')
+ if self._params['kty'] == 'oct':
+ return self._key['k']
+ elif self._params['kty'] == 'RSA':
+ k = self._key
+ pub = rsa.RSAPublicNumbers(self._decode_int(k['e']),
+ self._decode_int(k['n']))
+ pri = rsa.RSAPrivateNumbers(self._decode_int(k['p']),
+ self._decode_int(k['q']),
+ self._decode_int(k['d']),
+ self._decode_int(k['dp']),
+ self._decode_int(k['dq']),
+ self._decode_int(k['qi']), pub)
+ return pri.private_key(default_backend())
+ elif self._params['kty'] == 'EC':
+ k = self._key
+ pub = ec.EllipticCurvePublicNumbers(self._decode_int(k['x']),
+ self._decode_int(k['y']),
+ self.get_curve(arg))
+ pri = ec.EllipticCurvePrivateNumbers(self._decode_int(k['d']),
+ pub)
+ return pri.private_key(default_backend())
+ else:
+ raise NotImplementedError
+
+ def verify_key(self, arg=None):
+ self._check_constraints('sig', 'verify')
+ if self._params['kty'] == 'oct':
+ return self._key['k']
+ elif self._params['kty'] == 'RSA':
+ k = self._key
+ pub = rsa.RSAPublicNumbers(self._decode_int(k['e']),
+ self._decode_int(k['n']))
+ return pub.public_key(default_backend())
+ elif self._params['kty'] == 'EC':
+ k = self._key
+ pub = ec.EllipticCurvePublicNumbers(self._decode_int(k['x']),
+ self._decode_int(k['y']),
+ self.get_curve(arg))
+ return pub.public_key(default_backend())
+ else:
+ raise NotImplementedError
+
+
+class JWKSet(set):
+
+ def add(self, elem):
+ if not isinstance(elem, JWK):
+ raise TypeError('Only JWK objects are valid elements')
+ set.add(self, elem)
+
+ def export(self):
+ keys = list()
+ for jwk in self:
+ keys.append(json.loads(jwk.export()))
+ return json.dumps({'keys': keys})
+
+ def get_key(self, kid):
+ for jwk in self:
+ if jwk.key_id == kid:
+ return jwk
+ return None
diff --git a/jwcrypto/tests.py b/jwcrypto/tests.py
new file mode 100644
index 0000000..8e6c14c
--- /dev/null
+++ b/jwcrypto/tests.py
@@ -0,0 +1,178 @@
+# Copyright (C) 2015 JWCrypto Project Contributors - see LICENSE file
+
+from jwcrypto import jwk
+import json
+import unittest
+
+# draft-ietf-jose-json-web-key-41 - A.1
+PublicKeys = {"keys": [
+ {"kty": "EC",
+ "crv": "P-256",
+ "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
+ "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
+ "use": "enc",
+ "kid": "1"},
+ {"kty": "RSA",
+ "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbf"
+ "AAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknj"
+ "hMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65"
+ "YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQ"
+ "vRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lF"
+ "d2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzK"
+ "nqDKgw",
+ "e": "AQAB",
+ "alg": "RS256",
+ "kid": "2011-04-29"}]}
+
+# draft-ietf-jose-json-web-key-41 - A.2
+PrivateKeys = {"keys": [
+ {"kty": "EC",
+ "crv": "P-256",
+ "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
+ "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
+ "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE",
+ "use": "enc",
+ "kid": "1"},
+ {"kty": "RSA",
+ "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbb"
+ "fAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3ok"
+ "njhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v"
+ "-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu"
+ "6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0"
+ "fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8a"
+ "wapJzKnqDKgw",
+ "e": "AQAB",
+ "d": "X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7d"
+ "x5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_"
+ "YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywb"
+ "ReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5Zi"
+ "G7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z"
+ "4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc"
+ "0X4jfcKoAC8Q",
+ "p": "83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVn"
+ "wD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XO"
+ "uVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O"
+ "0nVbfs",
+ "q": "3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumq"
+ "jVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VV"
+ "S78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb"
+ "6yelxk",
+ "dp": "G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimY"
+ "wxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA"
+ "77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8Y"
+ "eiKkTiBj0",
+ "dq": "s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUv"
+ "MfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqU"
+ "fLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txX"
+ "w494Q_cgk",
+ "qi": "GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgU"
+ "IZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_"
+ "mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia"
+ "6zTKhAVRU",
+ "alg": "RS256",
+ "kid": "2011-04-29"}]}
+
+# draft-ietf-jose-json-web-key-41 - A.3
+SymmetricKeys = {"keys": [
+ {"kty": "oct",
+ "alg": "A128KW",
+ "k": "GawgguFyGrWKav7AX4VKUg"},
+ {"kty": "oct",
+ "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH7"
+ "5aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow",
+ "kid": "HMAC key used in JWS A.1 example"}]}
+
+# draft-ietf-jose-json-web-key-41 - B
+Useofx5c = {"kty": "RSA",
+ "use": "sig",
+ "kid": "1b94c",
+ "n": "vrjOfz9Ccdgx5nQudyhdoR17V-IubWMeOZCwX_jj0hgAsz2J_pqYW08PLbK"
+ "_PdiVGKPrqzmDIsLI7sA25VEnHU1uCLNwBuUiCO11_-7dYbsr4iJmG0Qu2j"
+ "8DsVyT1azpJC_NG84Ty5KKthuCaPod7iI7w0LK9orSMhBEwwZDCxTWq4aYW"
+ "Achc8t-emd9qOvWtVMDC2BXksRngh6X5bUYLy6AyHKvj-nUy1wgzjYQDwHM"
+ "TplCoLtU-o-8SNnZ1tmRoGE9uJkBLdh5gFENabWnU5m1ZqZPdwS-qo-meMv"
+ "VfJb6jJVWRpl2SUtCnYG2C32qvbWbjZ_jBPD5eunqsIo1vQ",
+ "e": "AQAB",
+ "x5c": ["MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJ"
+ "BgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRww"
+ "GgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5Ccmlh"
+ "biBDYW1wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVa"
+ "MGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVu"
+ "dmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQD"
+ "Ew5CcmlhbiBDYW1wYmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC"
+ "AQoCggEBAL64zn8/QnHYMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9"
+ "if6amFtPDy2yvz3YlRij66s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u"
+ "3WG7K+IiZhtELto/A7Fck9Ws6SQvzRvOE8uSirYbgmj6He4iO8NCyvaK"
+ "0jIQRMMGQwsU1quGmFgHIXPLfnpnfajr1rVTAwtgV5LEZ4Iel+W1GC8u"
+ "gMhyr4/p1MtcIM42EA8BzE6ZQqC7VPqPvEjZ2dbZkaBhPbiZAS3YeYBR"
+ "DWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVkaZdklLQp2Btgt9qr21m42f"
+ "4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAh8zGlfSl"
+ "cI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL+9gGlqCz5iWLOgWs"
+ "nrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1zFo+Owb1zxtp3"
+ "PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL2Bo3UPGrps"
+ "HzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo4tpzd5r"
+ "FXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTqgawR"
+ "+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA=="
+ ]}
+
+# draft-ietf-jose-json-web-key-41 - C.1
+RSAPrivateKey = {"kty": "RSA",
+ "kid": "juliet@capulet.lit",
+ "use": "enc",
+ "n": "t6Q8PWSi1dkJj9hTP8hNYFlvadM7DflW9mWepOJhJ66w7nyoK1gPNq"
+ "FMSQRyO125Gp-TEkodhWr0iujjHVx7BcV0llS4w5ACGgPrcAd6ZcSR"
+ "0-Iqom-QFcNP8Sjg086MwoqQU_LYywlAGZ21WSdS_PERyGFiNnj3QQ"
+ "lO8Yns5jCtLCRwLHL0Pb1fEv45AuRIuUfVcPySBWYnDyGxvjYGDSM-"
+ "AqWS9zIQ2ZilgT-GqUmipg0XOC0Cc20rgLe2ymLHjpHciCKVAbY5-L"
+ "32-lSeZO-Os6U15_aXrk9Gw8cPUaX1_I8sLGuSiVdt3C_Fn2PZ3Z8i"
+ "744FPFGGcG1qs2Wz-Q",
+ "e": "AQAB",
+ "d": "GRtbIQmhOZtyszfgKdg4u_N-R_mZGU_9k7JQ_jn1DnfTuMdSNprTea"
+ "STyWfSNkuaAwnOEbIQVy1IQbWVV25NY3ybc_IhUJtfri7bAXYEReWa"
+ "Cl3hdlPKXy9UvqPYGR0kIXTQRqns-dVJ7jahlI7LyckrpTmrM8dWBo"
+ "4_PMaenNnPiQgO0xnuToxutRZJfJvG4Ox4ka3GORQd9CsCZ2vsUDms"
+ "XOfUENOyMqADC6p1M3h33tsurY15k9qMSpG9OX_IJAXmxzAh_tWiZO"
+ "wk2K4yxH9tS3Lq1yX8C1EWmeRDkK2ahecG85-oLKQt5VEpWHKmjOi_"
+ "gJSdSgqcN96X52esAQ",
+ "p": "2rnSOV4hKSN8sS4CgcQHFbs08XboFDqKum3sc4h3GRxrTmQdl1ZK9u"
+ "w-PIHfQP0FkxXVrx-WE-ZEbrqivH_2iCLUS7wAl6XvARt1KkIaUxPP"
+ "SYB9yk31s0Q8UK96E3_OrADAYtAJs-M3JxCLfNgqh56HDnETTQhH3r"
+ "CT5T3yJws",
+ "q": "1u_RiFDP7LBYh3N4GXLT9OpSKYP0uQZyiaZwBtOCBNJgQxaj10RWjs"
+ "Zu0c6Iedis4S7B_coSKB0Kj9PaPaBzg-IySRvvcQuPamQu66riMhjV"
+ "tG6TlV8CLCYKrYl52ziqK0E_ym2QnkwsUX7eYTB7LbAHRK9GqocDE5"
+ "B0f808I4s",
+ "dp": "KkMTWqBUefVwZ2_Dbj1pPQqyHSHjj90L5x_MOzqYAJMcLMZtbUtwK"
+ "qvVDq3tbEo3ZIcohbDtt6SbfmWzggabpQxNxuBpoOOf_a_HgMXK_l"
+ "hqigI4y_kqS1wY52IwjUn5rgRrJ-yYo1h41KR-vz2pYhEAeYrhttW"
+ "txVqLCRViD6c",
+ "dq": "AvfS0-gRxvn0bwJoMSnFxYcK1WnuEjQFluMGfwGitQBWtfZ1Er7t1"
+ "xDkbN9GQTB9yqpDoYaN06H7CFtrkxhJIBQaj6nkF5KKS3TQtQ5qCz"
+ "kOkmxIe3KRbBymXxkb5qwUpX5ELD5xFc6FeiafWYY63TmmEAu_lRF"
+ "COJ3xDea-ots",
+ "qi": "lSQi-w9CpyUReMErP1RsBLk7wNtOvs5EQpPqmuMvqW57NBUczScEo"
+ "PwmUqqabu9V0-Py4dQ57_bapoKRu1R90bvuFnU63SHWEFglZQvJDM"
+ "eAvmj4sm-Fp0oYu_neotgQ0hzbI5gry7ajdYy9-2lNx_76aBZoOUu"
+ "9HCJ-UsfSOI8"}
+
+
+class TestJWK(unittest.TestCase):
+ def test_create(self):
+ keylist = PublicKeys['keys']
+ for key in keylist:
+ _ = jwk.JWK(**key) # pylint: disable=star-args
+
+ keylist = PrivateKeys['keys']
+ for key in keylist:
+ _ = jwk.JWK(**key) # pylint: disable=star-args
+
+ keylist = SymmetricKeys['keys']
+ for key in keylist:
+ jwkey = jwk.JWK(**key) # pylint: disable=star-args
+ _ = jwkey.sign_key()
+ _ = jwkey.verify_key()
+ e = jwkey.export()
+ self.assertEqual(json.loads(e), key)
+
+ _ = jwk.JWK(**Useofx5c) # pylint: disable=star-args
+ _ = jwk.JWK(**RSAPrivateKey) # pylint: disable=star-args