Commit 906e9a25 authored by Simo Sorce's avatar Simo Sorce

The Pbes2 algorithm accepts only bytes or string

The Pbes2 algorithm is supposed to get a low entropy password so we break
the interface so that JWK keys are not accepted at all.

This is in order to avoid potential confusion in users where a low entropy
password is coerced into an "oct" key and then potentially used as a high
entropy symmetric key with other algorithms yielding very poor security.

By making password mutually eclusive with keys we try to avoid this mistakes
by users.
Signed-off-by: default avatarSimo Sorce <simo@redhat.com>
Closes #40
parent 562cdbbf
......@@ -156,6 +156,8 @@ class _RSA(_RawKeyMgmt):
self.padfn = padfn
def _check_key(self, key):
if not isinstance(key, JWK):
raise ValueError('key is not a JWK object')
if key.key_type != 'RSA':
raise InvalidJWEKeyType('RSA', key.key_type)
......@@ -215,6 +217,8 @@ class _AesKw(_RawKeyMgmt):
self.keysize = keysize // 8
def _get_key(self, key, op):
if not isinstance(key, JWK):
raise ValueError('key is not a JWK object')
if key.key_type != 'oct':
raise InvalidJWEKeyType('oct', key.key_type)
rk = base64url_decode(key.get_op_key(op))
......@@ -309,6 +313,8 @@ class _AesGcmKw(_RawKeyMgmt):
self.keysize = keysize // 8
def _get_key(self, key, op):
if not isinstance(key, JWK):
raise ValueError('key is not a JWK object')
if key.key_type != 'oct':
raise InvalidJWEKeyType('oct', key.key_type)
rk = base64url_decode(key.get_op_key(op))
......@@ -390,9 +396,10 @@ class _Pbes2HsAesKw(_RawKeyMgmt):
self.keysize = keysize // 8
def _get_key(self, alg, key, p2s, p2c):
if key.key_type != 'oct':
raise InvalidJWEKeyType('oct', key.key_type)
plain = base64url_decode(key.get_op_key('encrypt'))
if isinstance(key, bytes):
plain = key
else:
plain = key.encode('utf8')
salt = bytes(self.name.encode('utf8')) + b'\x00' + p2s
if self.hashsize == 256:
......@@ -468,6 +475,8 @@ class _Direct(_RawKeyMgmt):
return 'dir'
def _check_key(self, key):
if not isinstance(key, JWK):
raise ValueError('key is not a JWK object')
if key.key_type != 'oct':
raise InvalidJWEKeyType('oct', key.key_type)
......@@ -501,6 +510,8 @@ class _EcdhEs(_RawKeyMgmt):
self.keydatalen = keydatalen
def _check_key(self, key):
if not isinstance(key, JWK):
raise ValueError('key is not a JWK object')
if key.key_type != 'EC':
raise InvalidJWEKeyType('EC', key.key_type)
......@@ -943,12 +954,11 @@ class JWE(object):
def add_recipient(self, key, header=None):
"""Encrypt the plaintext with the given key.
:param key: A JWK key of appropriate type for the 'alg' provided
in the JOSE Headers.
:param key: A JWK key or password of appropriate type for the 'alg'
provided in the JOSE Headers.
:param header: A JSON string representing the per-recipient header.
:raises ValueError: if the plaintext is missing or not of type bytes.
:raises ValueError: if the key is not a JWK object.
:raises ValueError: if the compression type is unknown.
:raises InvalidJWAAlgorithm: if the 'alg' provided in the JOSE
headers is missing or unknown, or otherwise not implemented.
......@@ -957,8 +967,6 @@ class JWE(object):
raise ValueError('Missing plaintext')
if not isinstance(self.plaintext, bytes):
raise ValueError("Plaintext must be 'bytes'")
if not isinstance(key, JWK):
raise ValueError('key is not a JWK object')
jh = self._get_jose_header(header)
alg, enc = self._get_alg_enc_from_headers(jh)
......@@ -1115,14 +1123,14 @@ class JWE(object):
"""Decrypt a JWE token.
:param key: The (:class:`jwcrypto.jwk.JWK`) decryption key.
:param key: A (:class:`jwcrypto.jwk.JWK`) decryption key or a password
string (optional).
:raises InvalidJWEOperation: if the key is not a JWK object.
:raises InvalidJWEData: if the ciphertext can't be decrypted or
the object is otherwise malformed.
"""
if not isinstance(key, JWK):
raise ValueError('key is not a JWK object')
if 'ciphertext' not in self.objects:
raise InvalidJWEOperation("No available ciphertext")
self.decryptlog = list()
......@@ -1151,7 +1159,8 @@ class JWE(object):
:param raw_jwe: a 'raw' JWE token (JSON Encoded or Compact
notation) string.
:param key: A (:class:`jwcrypto.jwk.JWK`) decryption key (optional).
:param key: A (:class:`jwcrypto.jwk.JWK`) decryption key or a password
string (optional).
If a key is provided a decryption step will be attempted after
the object is successfully deserialized.
......
......@@ -1140,17 +1140,17 @@ class Cookbook08JWETests(unittest.TestCase):
def test_5_3_encryption(self):
plaintext = Payload_plaintext_5_3_1
password = jwk.JWK(kty="oct", use="enc",
k=base64url_encode(Password_5_3_1.decode('utf8')))
password = Password_5_3_1
unicodepwd = Password_5_3_1.decode('utf8')
e = jwe.JWE(plaintext, json_encode(JWE_Protected_Header_no_p2x))
e.add_recipient(password)
e.serialize(compact=True)
enc = e.serialize()
e.deserialize(enc, password)
e.deserialize(enc, unicodepwd)
self.assertEqual(e.payload, plaintext)
e.deserialize(JWE_compact_5_3_5, password)
self.assertEqual(e.payload, plaintext)
e.deserialize(json_encode(JWE_general_5_3_5), password)
e.deserialize(json_encode(JWE_general_5_3_5), unicodepwd)
self.assertEqual(e.payload, plaintext)
e.deserialize(json_encode(JWE_flattened_5_3_5), password)
self.assertEqual(e.payload, plaintext)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment