def generate_jwt(claims, priv_key=None, algorithm='PS512', lifetime=None, expires=None, not_before=None, jti_size=16, other_headers=None): """ Generate a JSON Web Token. :param claims: The claims you want included in the signature. :type claims: dict :param priv_key: The private key to be used to sign the token. Note: if you pass ``None`` then the token will be returned with an empty cryptographic signature and :obj:`algorithm` will be forced to the value ``none``. :type priv_key: `jwcrypto.jwk.JWK <https://jwcrypto.readthedocs.io/en/latest/jwk.html>`_ :param algorithm: The algorithm to use for generating the signature. ``RS256``, ``RS384``, ``RS512``, ``PS256``, ``PS384``, ``PS512``, ``ES256``, ``ES384``, ``ES512``, ``HS256``, ``HS384``, ``HS512`` and ``none`` are supported. :type algorithm: str :param lifetime: How long the token is valid for. :type lifetime: datetime.timedelta :param expires: When the token expires (if :obj:`lifetime` isn't specified) :type expires: datetime.datetime :param not_before: When the token is valid from. Defaults to current time (if ``None`` is passed). :type not_before: datetime.datetime :param jti_size: Size in bytes of the unique token ID to put into the token (can be used to detect replay attacks). Defaults to 16 (128 bits). Specify 0 or ``None`` to omit the JTI from the token. :type jti_size: int :param other_headers: Any headers other than "typ" and "alg" may be specified, they will be included in the header. :type other_headers: dict :rtype: unicode :returns: The JSON Web Token. Note this includes a header, the claims and a cryptographic signature. The following extra claims are added, per the `JWT spec <http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html>`_: - **exp** (*IntDate*) -- The UTC expiry date and time of the token, in number of seconds from 1970-01-01T0:0:0Z UTC. - **iat** (*IntDate*) -- The UTC date and time at which the token was generated. - **nbf** (*IntDate*) -- The UTC valid-from date and time of the token. - **jti** (*str*) -- A unique identifier for the token. :raises: ValueError: If other_headers contains either the "typ" or "alg" header """ header = { 'typ': 'JWT', 'alg': algorithm if priv_key else 'none' } if other_headers is not None: redefined_keys = set(header.keys()) & set(other_headers.keys()) if redefined_keys: raise ValueError('other_headers re-specified the headers: {}'.format(', '.join(redefined_keys))) header.update(other_headers) claims = dict(claims) now = datetime.utcnow() if jti_size: claims['jti'] = base64url_encode(urandom(jti_size)) claims['nbf'] = timegm((not_before or now).utctimetuple()) claims['iat'] = timegm(now.utctimetuple()) if lifetime: claims['exp'] = timegm((now + lifetime).utctimetuple()) elif expires: claims['exp'] = timegm(expires.utctimetuple()) if header['alg'] == 'none': signature = '' else: token = JWS(json_encode(claims)) token.add_signature(priv_key, protected=header) signature = json_decode(token.serialize())['signature'] return u'%s.%s.%s' % ( base64url_encode(json_encode(header)), base64url_encode(json_encode(claims)), signature )
def __init__(self, alg, key, header, payload, algs=None): """Core JWS token handling. :param alg: The algorithm used to produce the signature. See RFC 7518 :param key: A (:class:`jwcrypto.jwk.JWK`) key of appropriate type for the "alg" provided in the 'protected' json string. :param header: A JSON string representing the protected header. :param payload(bytes): An arbitrary value :param algs: An optional list of allowed algorithms :raises ValueError: if the key is not a :class:`JWK` object :raises InvalidJWAAlgorithm: if the algorithm is not valid, is unknown or otherwise not yet implemented. """ self.alg = alg self.engine = self._jwa(alg, algs) if not isinstance(key, JWK): raise ValueError('key is not a JWK object') self.key = key if header is not None: self.protected = base64url_encode(header.encode('utf-8')) else: self.protected = '' self.payload = base64url_encode(payload)
def wrap_fake(self, key, bitsize, cek, headers, point_x, point_y, aes_key): self._check_key(key) dk_size = self.keysize if self.keysize is None: if cek is not None: raise InvalidJWEOperation('ECDH-ES cannot use an existing CEK') alg = headers['enc'] dk_size = bitsize else: alg = headers['alg'] epk = JWK.generate(kty=key.key_type, crv=key.key_curve) dk = self._derive(epk.get_op_key('unwrapKey'), key.get_op_key('wrapKey'), alg, dk_size, headers) if self.keysize is None: ret = {'cek': aes_key} else: aeskw = self.aeskwmap[self.keysize]() kek = JWK(kty="oct", use="enc", k=base64url_encode(dk)) ret = aeskw.wrap(kek, bitsize, cek, headers) ret['header'] = { 'epk': { "crv": "P-256", "kty": "EC", "x": base64url_encode(int.to_bytes(point_x, 32, "big")), "y": base64url_encode(int.to_bytes(point_y, 32, "big")) } } return ret
def _decrypt(self, key, ppe): jh = self._get_jose_header(ppe.get('header', None)) # TODO: allow caller to specify list of headers it understands self._check_crit(jh.get('crit', dict())) alg = self._jwa(jh.get('alg', None)) enc = self._jwa(jh.get('enc', None)) aad = base64url_encode(self.objects.get('protected', '')) if 'aad' in self.objects: aad += '.' + base64url_encode(self.objects['aad']) cek = alg.unwrap(key, ppe.get('encrypted_key', b'')) data = enc.decrypt(cek, aad.encode('utf-8'), self.objects['iv'], self.objects['ciphertext'], self.objects['tag']) self.decryptlog.append('Success') self.cek = cek compress = jh.get('zip', None) if compress == 'DEF': self.plaintext = zlib.decompress(data, -zlib.MAX_WBITS) elif compress is None: self.plaintext = data else: raise ValueError('Unknown compression')
def create_JWK(): """Create a private key and return it formatted as JWK """ # Generate the private key using Ethereum methods acc = Account.create( extra_entropy= "Alastria is the first Public-Permissioned Blockchain Network") # Get the public key publicKey = PublicKey.from_private(acc._key_obj) # The public key is 64 bytes composed of the x and y curve coordinates # x and y are each 32 bytes long # We convert x and y to hex, so the dictionary can be converted to JSON x = publicKey[:32] y = publicKey[32:] # Create the Json Web Key (JWK) representation, as specified by W3C DID Document format key_JWK = JWK(kty="EC", crv="secp256k1", d=base64url_encode(acc.privateKey), x=base64url_encode(x), y=base64url_encode(y)) return key_JWK
def addPublicKey(self, kid: str, key_type: str, publicKey: bytes): # TODO: Use the public key thumbprint for kis (RFC 7638) # The public key is 64 bytes composed of the x and y curve coordinates # x and y are each 32 bytes long # We convert x and y to hex, so the dictionary can be converted to JSON x = publicKey[:32] y = publicKey[32:] # Create the Json Web Key (JWK) representation, as specified by W3C DID Document format publicKey_JWK = { "id": self.doc["id"] + "#" + kid, "type": key_type, "controller": self.doc["id"], "publicKeyJwk": { "kid": kid, "kty": "EC", "crv": "secp256k1", "x": base64url_encode(x), "y": base64url_encode(y) } } self.doc["verificationMethod"].append(publicKey_JWK) self.setUpdated()
def __init__(self, alg, key, header, payload, algs=None): """Core JWS token handling. :param alg: The algorithm used to produce the signature. See RFC 7518 :param key: A (:class:`jwcrypto.jwk.JWK`) key of appropriate type for the "alg" provided in the 'protected' json string. :param header: A JSON string representing the protected header. :param payload(bytes): An arbitrary value :param algs: An optional list of allowed algorithms :raises ValueError: if the key is not a :class:`JWK` object :raises InvalidJWAAlgorithm: if the algorithm is not valid, is unknown or otherwise not yet implemented. """ self.alg = alg self.engine = self._jwa(alg, algs) if not isinstance(key, JWK): raise ValueError('key is not a JWK object') self.key = key if header is not None: if isinstance(header, dict): header = json_encode(header) self.protected = base64url_encode(header.encode('utf-8')) else: self.protected = '' self.payload = base64url_encode(payload)
def is_correct(g, d): k = jwk.JWK(kty='EC', crv='P-256', d=base64url_encode(d.to_bytes(2, 'big')), x=base64url_encode(g.x.to_bytes(32, 'big')), y=base64url_encode(g.y.to_bytes(32, 'big'))) probe = jwe.JWE(payload, recipient=k, protected=header).serialize(True) response = requests.post('http://chal.cybersecurityrumble.de:1234/submit', probe.encode()) return response.content == b'{"status":"success"}\n'
def test_jws_loopback(self): sign = jws.JWS(payload='message') sign.add_signature(jwk.JWK(kty='oct', k=base64url_encode(b'A' * 16)), alg="HS512") o = sign.serialize() check = jws.JWS() check.deserialize(o, jwk.JWK(kty='oct', k=base64url_encode(b'A' * 16)), alg="HS512") self.assertTrue(check.objects['valid'])
def _import_pyca_pri_okp(self, key, **params): params.update(kty='OKP', crv=self._okp_curve_from_pyca_key(key), d=base64url_encode( key.private_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.Raw, serialization.NoEncryption())), x=base64url_encode(key.public_key().public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw))) self.import_key(**params)
def serialize(self, compact=False): """Serializes the object into a JWS token. :param compact(boolean): if True generates the compact representation, otherwise generates a standard JSON format. :raises InvalidJWSOperation: if the object cannot serialized with the compact representation and `compat` is True. :raises InvalidJWSSignature: if no signature has been added to the object, or no valid signature can be found. """ if compact: if 'signatures' in self.objects: raise InvalidJWSOperation("Can't use compact encoding with " "multiple signatures") if 'signature' not in self.objects: raise InvalidJWSSignature("No available signature") if not self.objects.get('valid', False): raise InvalidJWSSignature("No valid signature found") if 'protected' in self.objects: protected = base64url_encode(self.objects['protected']) else: protected = '' return '.'.join([protected, base64url_encode(self.objects['payload']), base64url_encode(self.objects['signature'])]) else: obj = self.objects if 'signature' in obj: if not obj.get('valid', False): raise InvalidJWSSignature("No valid signature found") sig = {'payload': base64url_encode(obj['payload']), 'signature': base64url_encode(obj['signature'])} if 'protected' in obj: sig['protected'] = base64url_encode(obj['protected']) if 'header' in obj: sig['header'] = obj['header'] elif 'signatures' in obj: sig = {'payload': base64url_encode(obj['payload']), 'signatures': list()} for o in obj['signatures']: if not o.get('valid', False): continue s = {'signature': base64url_encode(o['signature'])} if 'protected' in o: s['protected'] = base64url_encode(o['protected']) if 'header' in o: s['header'] = o['header'] sig['signatures'].append(s) if len(sig['signatures']) == 0: raise InvalidJWSSignature("No valid signature found") else: raise InvalidJWSSignature("No available signature") return json_encode(sig)
def wrap(self, key, keylen, cek, headers): rk = self._get_key(key, 'encrypt') if not cek: cek = os.urandom(keylen) iv = os.urandom(96 // 8) cipher = Cipher(algorithms.AES(rk), modes.GCM(iv), backend=self.backend) encryptor = cipher.encryptor() ek = encryptor.update(cek) + encryptor.finalize() tag = encryptor.tag return {'cek': cek, 'ek': ek, 'header': {'iv': base64url_encode(iv), 'tag': base64url_encode(tag)}}
def wrap(self, key, bitsize, cek, headers): rk = self._get_key(key, 'encrypt') if not cek: cek = _randombits(bitsize) iv = _randombits(96) cipher = Cipher(algorithms.AES(rk), modes.GCM(iv), backend=self.backend) encryptor = cipher.encryptor() ek = encryptor.update(cek) + encryptor.finalize() tag = encryptor.tag return {'cek': cek, 'ek': ek, 'header': {'iv': base64url_encode(iv), 'tag': base64url_encode(tag)}}
def verify_proof_chain(did, did_document, proof_chain): #--------------Verify sha-256 in the last proof---------- document_sha256 = hashlib.sha256() document_sha256.update(json.dumps(did_document).encode('utf-8')) document_sha256_b64 = base64url_encode(document_sha256.digest()) last_proof = jws.JWS() last_proof.deserialize(proof_chain[-1]) payload = json.loads(last_proof.objects['payload'].decode()) if (document_sha256_b64 != payload['sha-256']): raise Exception("The sha-256 included in the proof is not valid") return -1 #--------------Verify the chain of trust--------------- _did = did signer_jwk = did_to_jwk( did) #----The fist proof must be verified using the DID for proof in proof_chain: claimed_proof = jws.JWS() claimed_proof.deserialize(proof) payload = json.loads(claimed_proof.objects['payload'].decode()) _id = payload['id'] if (_id != _did): #----All proofs must include DID in their id field raise Exception("A proof contains an invalid id") return -1 claimed_proof.verify(signer_jwk) if ('controller' in payload): signer_jwk = jwk.JWK(**payload[ 'controller']) #----The next proof must be verified with this return True
def from_pub_bytes(pub_key_data): ''' Generate a compliance public key (for verification) from 32 bytes of Ed25519 key. ''' key = jwk.JWK(kty='OKP', crv='Ed25519', x=base64url_encode(pub_key_data)) return ComplianceKey(key)
def topic(self): """ Generate token """ token = jwt.generate_jwt(payload, None, 'none', timedelta(seconds=60)) header, claims, _ = token.split('.') parsed_header = json_decode(base64url_decode(header)) del parsed_header['alg'] return u"%s.%s." % (base64url_encode(json_encode(parsed_header)), claims)
def _import_pyca_pub_okp(self, key, **params): params.update(kty='OKP', crv=params['crv'], x=base64url_encode( key.public_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.Raw))) self.import_key(**params)
def _get_key(self, alg, key, p2s, p2c): if isinstance(key, bytes): plain = key else: plain = key.encode('utf8') salt = bytes(self.name.encode('utf8')) + b'\x00' + p2s if self.hashsize == 256: hashalg = hashes.SHA256() elif self.hashsize == 384: hashalg = hashes.SHA384() elif self.hashsize == 512: hashalg = hashes.SHA512() else: raise ValueError('Unknown Hash Size') kdf = PBKDF2HMAC(algorithm=hashalg, length=_inbytes(self.keysize), salt=salt, iterations=p2c, backend=self.backend) rk = kdf.derive(plain) if _bitsize(rk) != self.keysize: raise InvalidJWEKeyLength(self.keysize, len(rk)) return JWK(kty="oct", use="enc", k=base64url_encode(rk))
def wrap(self, key, bitsize, cek, headers): self._check_key(key) dk_size = self.keysize if self.keysize is None: if cek is not None: raise InvalidJWEOperation('ECDH-ES cannot use an existing CEK') alg = headers['enc'] dk_size = bitsize else: alg = headers['alg'] epk = JWK.generate(kty=key.key_type, crv=key.key_curve) dk = self._derive(epk.get_op_key('unwrapKey'), key.get_op_key('wrapKey'), alg, dk_size, headers) if self.keysize is None: ret = {'cek': dk} else: aeskw = self.aeskwmap[self.keysize]() kek = JWK(kty="oct", use="enc", k=base64url_encode(dk)) ret = aeskw.wrap(kek, bitsize, cek, headers) ret['header'] = {'epk': json_decode(epk.export_public())} return ret
def wrap(self, key, keylen, cek, headers): self._check_key(key) if self.keydatalen is None: if cek is not None: raise InvalidJWEOperation('ECDH-ES cannot use an existing CEK') keydatalen = keylen * 8 alg = headers['enc'] else: keydatalen = self.keydatalen alg = headers['alg'] epk = JWK.generate(kty=key.key_type, crv=key.key_curve) dk = self._derive(epk.get_op_key('unwrapKey'), key.get_op_key('wrapKey'), alg, keydatalen, headers) if self.keydatalen is None: ret = {'cek': dk} else: aeskw = _AesKw(keydatalen) kek = JWK(kty="oct", use="enc", k=base64url_encode(dk)) ret = aeskw.wrap(kek, keydatalen // 8, cek, headers) ret['header'] = {'epk': json_decode(epk.export_public())} return ret
def _add_signature( self, prop: str, key: JWK, alg: Optional[AlgorithmName], header: Optional[JsonObject], prepare_payload_header: _PreparePayloadHeader, install_payload_header: _InstallPayloadHeader) -> None: if self._payload is None: raise InvalidJWSObject('Missing Payload') # Check the header round-trips through JSON h = json_decode(json_encode(header or {})) self._check_extensions(h.get(_EXTENSIONS, [])) a = self._get_alg(alg, header, ValueError) # Prepare payload for signature algorithm h.pop(_VALUE, None) payload = copy(self._payload) payload[prop] = prepare_payload_header(h) canonical = _dumpb(payload) # Calculate signature # # JWSCore would encode payload as base64 and prepend a dot, # but Cleartext JWS uses canonicalized JSON as Signing Input, # so we just use Core for its algorithm engine selection logic. c = JWSCore(a, key, header=None, payload='', algs=self.allowed_algs) sig = c.engine.sign(key, canonical) # Put signature in place h[_VALUE] = base64url_encode(sig) install_payload_header(h) self._valid = True
def _save_session(self, session_id, user_id, data, legacy=False): raw_data = json.dumps(vars(data)) protected_header = { 'alg': 'dir', 'enc': 'A256GCM', 'kid': '1,1', } if legacy: plaintext = base64url_encode(raw_data) else: plaintext = raw_data jwe_token = jwe.JWE( plaintext=plaintext, protected=protected_header, recipient=self.key ) session_model = EQSession( session_id, user_id, jwe_token.serialize(compact=True) ) data_access.put(session_model)
def sign(self): """Generates a signature""" sigin = ('.'.join([self.protected, self.payload])).encode('utf-8') signature = self.engine.sign(self.key, sigin) return {'protected': self.protected, 'payload': self.payload, 'signature': base64url_encode(signature)}
def topic(self, topic): """ Verify the token """ clock_load(orig_datetime.utcfromtimestamp(0)) r = jwt.verify_jwt(topic, JWK(kty='oct', k=base64url_encode('secret')), ['HS256']) clock_reset() return r
def sign(self): """Generates a signature""" payload = self._payload() sigin = b'.'.join([self.protected.encode('utf-8'), payload]) signature = self.engine.sign(self.key, sigin) return {'protected': self.protected, 'payload': payload, 'signature': base64url_encode(signature)}
def topic(self): """ Generate token """ token = jwt.generate_jwt(payload, None, 'none', timedelta(seconds=60)) header, claims, _ = token.split('.') parsed_header = json_decode(base64url_decode(header)) del parsed_header['alg'] return u"%s.%s." % (base64url_encode( json_encode(parsed_header)), claims)
def _get_key() -> str: # 32 bit key size fallback = b"1234567890123456" # need to STRONGLY encourage setting ARC_APP_SECRET in the docs secret = os.environ.get("ARC_APP_SECRET", fallback) return jwk.JWK(k=base64url_encode(secret), kty="oct")
def encrypt(self, payload, header, key): jwetoken = jwe.JWE(payload.encode('utf-8'), header) params = dict() params['kty'] = 'oct' params['k'] = base64url_encode(key) jwetoken.add_recipient(jwk.JWK(**params)) print("JWE encryption successful") return jwetoken.serialize(compact=True)
def jwk_key(self): if self.algorithm == AbstractApplication.RS256_ALGORITHM: if not oauth2_settings.OIDC_RSA_PRIVATE_KEY: raise ImproperlyConfigured("You must set OIDC_RSA_PRIVATE_KEY to use RSA algorithm") return jwk.JWK.from_pem(oauth2_settings.OIDC_RSA_PRIVATE_KEY.encode("utf8")) elif self.algorithm == AbstractApplication.HS256_ALGORITHM: return jwk.JWK(kty="oct", k=base64url_encode(self.client_secret)) raise ImproperlyConfigured("This application does not support signed tokens")
def _payload(self): if self.header.get('b64', True): return base64url_encode(self.payload).encode('utf-8') else: if isinstance(self.payload, bytes): return self.payload else: return self.payload.encode('utf-8')
def _generate_oct(self, params): size = 128 if 'size' in params: size = params['size'] del params['size'] key = os.urandom(size // 8) params['kty'] = 'oct' params['k'] = base64url_encode(key) self.import_key(**params)
def wrap(self, key, keylen, cek, headers): p2s = os.urandom(16) p2c = 8192 kek = self._get_key(headers['alg'], key, p2s, p2c) aeskw = _AesKw(self.keysize * 8) ret = aeskw.wrap(kek, keylen, cek, headers) ret['header'] = {'p2s': base64url_encode(p2s), 'p2c': p2c} return ret
def generate(header, payload, priv_pem): priv_pem = json_decode(priv_pem.replace('\n', '\\n')) if priv_pem.startswith("-----BEGIN"): priv_key = JWK.from_pem(to_bytes_2and3(priv_pem)) else: priv_key = JWK(kty='oct', k=base64url_encode(priv_pem)) sig = JWS(payload) sig.add_signature(priv_key, protected=header) sys.stdout.write(sig.serialize(compact=True))
def _encrypt(self, alg, enc, jh): aad = base64url_encode(self.objects.get('protected', '')) if 'aad' in self.objects: aad += '.' + base64url_encode(self.objects['aad']) aad = aad.encode('utf-8') compress = jh.get('zip', None) if compress == 'DEF': data = zlib.compress(self.plaintext)[2:-4] elif compress is None: data = self.plaintext else: raise ValueError('Unknown compression') iv, ciphertext, tag = enc.encrypt(self.cek, aad, data) self.objects['iv'] = iv self.objects['ciphertext'] = ciphertext self.objects['tag'] = tag
def decrypt(self, jwe_payload, key): jwetoken = jwe.JWE() jwetoken.deserialize(raw_jwe=jwe_payload) params = dict() params['kty'] = 'oct' params['k'] = base64url_encode(key) print("JWE decryption successful.") jwetoken.decrypt(jwk.JWK(**params)) return jwetoken.payload
def wrap(self, key, bitsize, cek, headers): p2s = _randombits(128) p2c = 8192 kek = self._get_key(headers['alg'], key, p2s, p2c) aeskw = self.aeskwmap[self.keysize]() ret = aeskw.wrap(kek, bitsize, cek, headers) ret['header'] = {'p2s': base64url_encode(p2s), 'p2c': p2c} return ret
def generatePrivateKey(self, hashedsecretkey=None): return jwkcrypto.JWK( kty= 'oct', # MANDATORY KeyType Octet sequence (used to represent symmetric keys) https://tools.ietf.org/html/rfc7518#section-6.1 size= 512, # MANDATORY key size will be automatically match with alg used in config k=base64url_encode( hashedsecretkey) # OPTIONNAL secret key hashed and encoded )
def verify(sjws, pub_pem): sjws = json_decode(sjws) pub_pem = json_decode(pub_pem.replace('\n', '\\n')) if pub_pem.startswith("-----BEGIN"): pub_key = JWK.from_pem(to_bytes_2and3(pub_pem)) else: pub_key = JWK(kty='oct', k=base64url_encode(pub_pem)) sig = JWS() sig.deserialize(sjws, pub_key) sys.stdout.write(base64url_decode(json_decode(sig.serialize())['payload']))
def did_to_jwk(did: str) -> jwk.JWK: if (did.startswith("did:self:") ): #Used only for the first signature of the proof chain public_key = did.split(":")[2] if (did.startswith("did:key:z6MK") ): #Ed25519 public key encoded using base58 public_key_58 = did.split(":")[2][4:] public_key = base64url_encode(base58.b58decode(public_key_58)) key_dict = {'kty': 'OKP', 'crv': 'Ed25519', 'x': public_key} return jwk.JWK(**key_dict)
def thumbprint(self, hashalg=hashes.SHA256()): """Returns the key thumbprint as specified by RFC 7638. :param hashalg: A hash function (defaults to SHA256) """ t = {'kty': self._params['kty']} for name, val in iteritems(JWKValuesRegistry[t['kty']]): if val[2] == 'Required': t[name] = self._key[name] digest = hashes.Hash(hashalg, backend=default_backend()) digest.update(bytes(json_encode(t).encode('utf8'))) return base64url_encode(digest.finalize())
def unwrap(self, key, keylen, ek, headers): if 'epk' not in headers: raise InvalidJWEData('Invalid Header, missing "epk" parameter') self._check_key(key) if self.keydatalen is None: keydatalen = keylen * 8 alg = headers['enc'] else: keydatalen = self.keydatalen alg = headers['alg'] epk = JWK(**headers['epk']) dk = self._derive(key.get_op_key('unwrapKey'), epk.get_op_key('wrapKey'), alg, keydatalen, headers) if self.keydatalen is None: return dk else: aeskw = _AesKw(keydatalen) kek = JWK(kty="oct", use="enc", k=base64url_encode(dk)) cek = aeskw.unwrap(kek, keydatalen // 8, ek, headers) return cek
def _get_key(self, alg, key, p2s, p2c): if isinstance(key, bytes): plain = key else: plain = key.encode('utf8') salt = bytes(self.name.encode('utf8')) + b'\x00' + p2s if self.hashsize == 256: hashalg = hashes.SHA256() elif self.hashsize == 384: hashalg = hashes.SHA384() elif self.hashsize == 512: hashalg = hashes.SHA512() else: raise InvalidJWEData('Unknown Hash Size') kdf = PBKDF2HMAC(algorithm=hashalg, length=self.keysize, salt=salt, iterations=p2c, backend=self.backend) rk = kdf.derive(plain) if len(rk) != self.keysize: raise InvalidJWEKeyLength(self.keysize * 8, len(rk) * 8) return JWK(kty="oct", use="enc", k=base64url_encode(rk))
""" test using PEM as key - we shouldn't validate a HMAC token instead """ # pylint: disable=wrong-import-order from test.common import payload, pub_pem, pub_key from test import python_jwt as jwt from datetime import timedelta from pyvows import Vows, expect from jwcrypto.jwk import JWK from jwcrypto.common import base64url_encode pem_key = JWK(kty='oct', k=base64url_encode(pub_pem)) @Vows.batch class PEMAsHMACKey(Vows.Context): """ setup tests """ def topic(self): """ Generate token """ return jwt.generate_jwt(payload, pem_key, 'HS256', timedelta(seconds=60)) class VerifyTokenUsingPublicPEMNoAllowedAlgsSpecified(Vows.Context): """ Verify token, allowed algorithms not specified """ @Vows.capture_error def topic(self, topic): """ Verify the token """ return jwt.verify_jwt(topic, pem_key) def token_should_not_verify(self, r): """ Should not verify """ expect(r).to_be_an_error() expect(str(r)).to_equal('algorithm not allowed: HS256') class VerifyTokenUsingPublicPEMHS256AlgAllowed(Vows.Context):
"iv": JWE_IV_5_9_2, "ciphertext": JWE_Ciphertext_5_9_4, "tag": JWE_Authentication_Tag_5_9_4} JWE_flattened_5_9_5 = { "protected": JWE_Protected_Header_5_9_4, "encrypted_key": JWE_Encrypted_Key_5_9_3, "iv": JWE_IV_5_9_2, "ciphertext": JWE_Ciphertext_5_9_4, "tag": JWE_Authentication_Tag_5_9_4} # 5.10 AAD_5_10_1 = base64url_encode(json_encode( ["vcard", [["version", {}, "text", "4.0"], ["fn", {}, "text", "Meriadoc Brandybuck"], ["n", {}, "text", ["Brandybuck", "Meriadoc", "Mr.", ""]], ["bday", {}, "text", "TA 2982"], ["gender", {}, "text", "M"]]])) JWE_IV_5_10_2 = "veCx9ece2orS7c_N" JWE_Encrypted_Key_5_10_3 = "4YiiQ_ZzH76TaIkJmYfRFgOV9MIpnx4X" JWE_Protected_Header_5_10_4 = \ "eyJhbGciOiJBMTI4S1ciLCJraWQiOiI4MWIyMDk2NS04MzMyLTQzZDktYTQ2OC" + \ "04MjE2MGFkOTFhYzgiLCJlbmMiOiJBMTI4R0NNIn0" JWE_Ciphertext_5_10_4 = \ "Z_3cbr0k3bVM6N3oSNmHz7Lyf3iPppGf3Pj17wNZqteJ0Ui8p74SchQP8xygM1" + \ "oFRWCNzeIa6s6BcEtp8qEFiqTUEyiNkOWDNoF14T_4NFqF-p2Mx8zkbKxI7oPK" + \
def test_aes_256(self): enc = jwe.JWE(plaintext='plain') key256 = jwk.JWK(kty='oct', k=base64url_encode(b'C' * (256 // 8))) enc.add_recipient(key256, '{"alg":"A256KW","enc":"A256CBC-HS512"}') enc.add_recipient(key256, '{"alg":"A256KW","enc":"A256GCM"}')
def test_aes_192(self): enc = jwe.JWE(plaintext='plain') key192 = jwk.JWK(kty='oct', k=base64url_encode(b'B' * (192 // 8))) enc.add_recipient(key192, '{"alg":"A192KW","enc":"A192CBC-HS384"}') enc.add_recipient(key192, '{"alg":"A192KW","enc":"A192GCM"}')
def test_aes_128(self): enc = jwe.JWE(plaintext='plain') key128 = jwk.JWK(kty='oct', k=base64url_encode(b'A' * (128 // 8))) enc.add_recipient(key128, '{"alg":"A128KW","enc":"A128CBC-HS256"}') enc.add_recipient(key128, '{"alg":"A128KW","enc":"A128GCM"}')
def test_jwe_no_enc_in_jose_headers(self): enc = jwe.JWE(plaintext='plain') self.assertRaises(jwe.InvalidJWEData, enc.add_recipient, jwk.JWK(kty='oct', k=base64url_encode(b'A' * 16)), '{"alg":"A128KW"}')
def topic(self, topic): """ Verify the token with some public key and none alg allowed """ return jwt.verify_jwt(topic, JWK(kty='oct', k=base64url_encode('anysecrethere')), ['none'])
def _encode_int(self, i): intg = hex(i).rstrip("L").lstrip("0x") return base64url_encode(unhexlify((len(intg) % 2) * '0' + intg))
def serialize(self, compact=False): """Serializes the object into a JWE token. :param compact(boolean): if True generates the compact representation, otherwise generates a standard JSON format. :raises InvalidJWEOperation: if the object cannot serialized with the compact representation and `compat` is True. :raises InvalidJWEOperation: if no recipients have been added to the object. """ if 'ciphertext' not in self.objects: raise InvalidJWEOperation("No available ciphertext") if compact: for invalid in 'aad', 'unprotected': if invalid in self.objects: raise InvalidJWEOperation("Can't use compact encoding") if 'recipients' in self.objects: if len(self.objects['recipients']) != 1: raise InvalidJWEOperation("Invalid number of recipients") rec = self.objects['recipients'][0] else: rec = self.objects return '.'.join([base64url_encode(self.objects['protected']), base64url_encode(rec.get('encrypted_key', '')), base64url_encode(self.objects['iv']), base64url_encode(self.objects['ciphertext']), base64url_encode(self.objects['tag'])]) else: obj = self.objects enc = {'ciphertext': base64url_encode(obj['ciphertext']), 'iv': base64url_encode(obj['iv']), 'tag': base64url_encode(self.objects['tag'])} if 'protected' in obj: enc['protected'] = base64url_encode(obj['protected']) if 'unprotected' in obj: enc['unprotected'] = json_decode(obj['unprotected']) if 'aad' in obj: enc['aad'] = base64url_encode(obj['aad']) if 'recipients' in obj: enc['recipients'] = list() for rec in obj['recipients']: e = dict() if 'encrypted_key' in rec: e['encrypted_key'] = \ base64url_encode(rec['encrypted_key']) if 'header' in rec: e['header'] = json_decode(rec['header']) enc['recipients'].append(e) else: if 'encrypted_key' in obj: enc['encrypted_key'] = \ base64url_encode(obj['encrypted_key']) if 'header' in obj: enc['header'] = json_decode(obj['header']) return json_encode(enc)
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 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. """ if self.plaintext is None: 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) rec = dict() if header: rec['header'] = header self.cek, ek = alg.wrap(key, enc.key_size, self.cek) if ek: rec['encrypted_key'] = ek if 'ciphertext' not in self.objects: aad = base64url_encode(self.objects.get('protected', '')) if 'aad' in self.objects: aad += '.' + base64url_encode(self.objects['aad']) aad = aad.encode('utf-8') compress = jh.get('zip', None) if compress == 'DEF': data = zlib.compress(self.plaintext)[2:-4] elif compress is None: data = self.plaintext else: raise ValueError('Unknown compression') iv, ciphertext, tag = enc.encrypt(self.cek, aad, data) self.objects['iv'] = iv self.objects['ciphertext'] = ciphertext self.objects['tag'] = tag if 'recipients' in self.objects: self.objects['recipients'].append(rec) elif 'encrypted_key' in self.objects or 'header' in self.objects: self.objects['recipients'] = list() n = dict() if 'encrypted_key' in self.objects: n['encrypted_key'] = self.objects['encrypted_key'] del self.objects['encrypted_key'] if 'header' in self.objects: n['header'] = self.objects['header'] del self.objects['header'] self.objects['recipients'].append(n) self.objects['recipients'].append(rec) else: self.objects.update(rec)
def test_jwe_no_protected_header(self): enc = jwe.JWE(plaintext='plain') enc.add_recipient(jwk.JWK(kty='oct', k=base64url_encode(b'A' * 16)), '{"alg":"A128KW","enc":"A128GCM"}')
def topic(self, topic): """ Verify the token with some public key """ return jwt.verify_jwt(topic, JWK(kty='oct', k=base64url_encode('anysecrethere')))
def serialize(self, compact=False): """Serializes the object into a JWE token. :param compact(boolean): if True generates the compact representation, otherwise generates a standard JSON format. :raises InvalidJWEOperation: if the object cannot serialized with the compact representation and `compact` is True. :raises InvalidJWEOperation: if no recipients have been added to the object. """ if 'ciphertext' not in self.objects: raise InvalidJWEOperation("No available ciphertext") if compact: for invalid in 'aad', 'unprotected': if invalid in self.objects: raise InvalidJWEOperation("Can't use compact encoding") if 'recipients' in self.objects: if len(self.objects['recipients']) != 1: raise InvalidJWEOperation("Invalid number of recipients") rec = self.objects['recipients'][0] else: rec = self.objects if 'header' in rec: # The AESGCMKW algorithm generates data (iv, tag) we put in the # per-recipient unpotected header by default. Move it to the # protected header and re-encrypt the payload, as the protected # header is used as additional authenticated data. h = json_decode(rec['header']) ph = json_decode(self.objects['protected']) nph = self._merge_headers(h, ph) self.objects['protected'] = json_encode(nph) jh = self._get_jose_header() alg, enc = self._get_alg_enc_from_headers(jh) self._encrypt(alg, enc, jh) del rec['header'] return '.'.join([base64url_encode(self.objects['protected']), base64url_encode(rec.get('encrypted_key', '')), base64url_encode(self.objects['iv']), base64url_encode(self.objects['ciphertext']), base64url_encode(self.objects['tag'])]) else: obj = self.objects enc = {'ciphertext': base64url_encode(obj['ciphertext']), 'iv': base64url_encode(obj['iv']), 'tag': base64url_encode(self.objects['tag'])} if 'protected' in obj: enc['protected'] = base64url_encode(obj['protected']) if 'unprotected' in obj: enc['unprotected'] = json_decode(obj['unprotected']) if 'aad' in obj: enc['aad'] = base64url_encode(obj['aad']) if 'recipients' in obj: enc['recipients'] = list() for rec in obj['recipients']: e = dict() if 'encrypted_key' in rec: e['encrypted_key'] = \ base64url_encode(rec['encrypted_key']) if 'header' in rec: e['header'] = json_decode(rec['header']) enc['recipients'].append(e) else: if 'encrypted_key' in obj: enc['encrypted_key'] = \ base64url_encode(obj['encrypted_key']) if 'header' in obj: enc['header'] = json_decode(obj['header']) return json_encode(enc)