def test_4_8_signing(self): plaintext = base64url_decode(Payload_plaintext_b64_4) S = jws.JWS(payload=plaintext) # 4_8_2 protected = \ base64url_decode(JWS_Protected_Header_4_8_2).decode('utf-8') header = json_encode(JWS_Unprotected_Header_4_8_2) pri_key = jwk.JWK(**RSA_Private_Key_3_4) S.add_signature(pri_key, None, protected, header) # 4_8_3 header = json_encode(JWS_Unprotected_Header_4_8_3) pri_key = jwk.JWK(**EC_Private_Key_3_2) S.add_signature(pri_key, None, None, header) # 4_8_4 protected = \ base64url_decode(JWS_Protected_Header_4_8_4).decode('utf-8') sym_key = jwk.JWK(**Symmetric_Key_MAC_3_5) S.add_signature(sym_key, None, protected) sig = S.serialize() # Can't compare signature with reference because ECDSA uses # random nonces every time a signature is generated. rsa_key = jwk.JWK(**RSA_Public_Key_3_3) ec_key = jwk.JWK(**EC_Public_Key_3_1) S.deserialize(sig, rsa_key) S.deserialize(sig, ec_key) S.deserialize(sig, sym_key) # Just deserialize each example form S.deserialize(json_encode(JWS_general_4_8_5), rsa_key) S.deserialize(json_encode(JWS_general_4_8_5), ec_key) S.deserialize(json_encode(JWS_general_4_8_5), sym_key)
def derive_key(privkey, point_x, point_y, alg, bitsize, headers): # OtherInfo is defined in NIST SP 56A 5.8.1.2.1 # AlgorithmID otherinfo = struct.pack('>I', len(alg)) otherinfo += bytes(alg.encode('utf8')) # PartyUInfo apu = base64url_decode(headers['apu']) if 'apu' in headers else b'' otherinfo += struct.pack('>I', len(apu)) otherinfo += apu # PartyVInfo apv = base64url_decode(headers['apv']) if 'apv' in headers else b'' otherinfo += struct.pack('>I', len(apv)) otherinfo += apv # SuppPubInfo otherinfo += struct.pack('>I', bitsize) # no SuppPrivInfo # Shared Key generation x, y = point_x, point_y P = vulnecc.AffinePoint(vulnecc.curveP256_vuln, x, y) s = privkey shared = s * P shared_key = int.to_bytes(shared.x, 32, "big") ckdf = ConcatKDFHash(algorithm=hashes.SHA256(), length=bitsize // 8, otherinfo=otherinfo, backend=default_backend()) return ckdf.derive(shared_key)
def _derive(self, privkey, pubkey, alg, bitsize, headers): # OtherInfo is defined in NIST SP 56A 5.8.1.2.1 # AlgorithmID otherinfo = struct.pack('>I', len(alg)) otherinfo += bytes(alg.encode('utf8')) # PartyUInfo apu = base64url_decode(headers['apu']) if 'apu' in headers else b'' otherinfo += struct.pack('>I', len(apu)) otherinfo += apu # PartyVInfo apv = base64url_decode(headers['apv']) if 'apv' in headers else b'' otherinfo += struct.pack('>I', len(apv)) otherinfo += apv # SuppPubInfo otherinfo += struct.pack('>I', bitsize) # no SuppPrivInfo # Shared Key generation if isinstance(privkey, ec.EllipticCurvePrivateKey): shared_key = privkey.exchange(ec.ECDH(), pubkey) else: # X25519/X448 shared_key = privkey.exchange(pubkey) ckdf = ConcatKDFHash(algorithm=hashes.SHA256(), length=_inbytes(bitsize), otherinfo=otherinfo, backend=self.backend) return ckdf.derive(shared_key)
def readable_jwt(txt): """ Convert a JWT token in base64url into a readable dictionary object with decoded payload and header for printing and logging """ lst = txt.split('.') readable = { 'protected': json_decode(base64url_decode(lst[0])), 'payload': json_decode(base64url_decode(lst[1])), 'signature': lst[2] } # create readable timestamps for exp/iat claims payload = readable["payload"] if "iat" in payload: t = payload["iat"] if isinstance(t, int): t = datetime.fromtimestamp(t).isoformat(' ') payload["iat"] = t if "exp" in payload: t = payload["exp"] if isinstance(t, int): t = datetime.fromtimestamp(t).isoformat(' ') payload["exp"] = t print(json.dumps(readable, indent=4, sort_keys=False)) return readable
def _derive(self, privkey, pubkey, alg, keydatalen, headers): # OtherInfo is defined in NIST SP 56A 5.8.1.2.1 # AlgorithmID otherinfo = struct.pack('>I', len(alg)) otherinfo += bytes(alg.encode('utf8')) # PartyUInfo apu = base64url_decode(headers['apu']) if 'apu' in headers else b'' otherinfo += struct.pack('>I', len(apu)) otherinfo += apu # PartyVInfo apv = base64url_decode(headers['apv']) if 'apv' in headers else b'' otherinfo += struct.pack('>I', len(apv)) otherinfo += apv # SuppPubInfo otherinfo += struct.pack('>I', keydatalen) # no SuppPrivInfo shared_key = privkey.exchange(ec.ECDH(), pubkey) ckdf = ConcatKDFHash(algorithm=hashes.SHA256(), length=keydatalen // 8, otherinfo=otherinfo, backend=self.backend) return ckdf.derive(shared_key)
def verify_jws(self, raw_jws: str, did_document: DidDocument or None): c = raw_jws.split('.') data = { 'protected': json.loads(base64url_decode(str(c[0])).decode('utf-8')), 'payload': json.loads(base64url_decode(str(c[1])).decode('utf-8')), 'signature': base64url_decode(str(c[2])) } key_id = data['protected']['kid'] issuer = data['payload']['iss'] if did_document is None: _cache_pub_key_of_issuer = self.document.get_public_key( key_id=key_id) if not _cache_pub_key_of_issuer: self.document = self.__get_document(issuer) pub_key_of_issuer = self.document.get_public_key(key_id=key_id) if not pub_key_of_issuer: raise DidNotFoundException( error_message=f"Not Found KeyID in did document {issuer}") user_pub_key_hex = pub_key_of_issuer.get('publicKeyHex') if not user_pub_key_hex: return None user_pub_key = jwk.JWK.from_pem( VerifyingKey.from_string(bytes.fromhex(user_pub_key_hex), curve=SECP256k1).to_pem()) return VerifiableSignedJWT.verify(token=raw_jws, key=user_pub_key)
def _deserialize_signature(self, s): o = dict() o['signature'] = base64url_decode(str(s['signature'])) if 'protected' in s: p = base64url_decode(str(s['protected'])) o['protected'] = p.decode('utf-8') if 'header' in s: o['header'] = s['header'] return o
def deserialize(self, raw_jws, key=None, alg=None): """Deserialize a JWS token. NOTE: Destroys any current status and tries to import the raw JWS provided. :param raw_jws: a 'raw' JWS token (JSON Encoded or Compact notation) string. :param key: A (:class:`jwcrypto.jwk.JWK`) verification key (optional). If a key is provided a verification step will be attempted after the object is successfully deserialized. :param alg: The signing algorithm (optional). usually the algorithm is known as it is provided with the JOSE Headers of the token. :raises InvalidJWSObject: if the raw object is an invaid JWS token. :raises InvalidJWSSignature: if the verification fails. """ self.objects = dict() o = dict() try: try: djws = json_decode(raw_jws) if 'signatures' in djws: o['signatures'] = list() for s in djws['signatures']: os = self._deserialize_signature(s) o['signatures'].append(os) self._deserialize_b64(o, os.get('protected')) else: o = self._deserialize_signature(djws) self._deserialize_b64(o, o.get('protected')) if 'payload' in djws: if o.get('b64', True): o['payload'] = base64url_decode(str(djws['payload'])) else: o['payload'] = djws['payload'] except ValueError: c = raw_jws.split('.') if len(c) != 3: raise InvalidJWSObject('Unrecognized representation') p = base64url_decode(str(c[0])) if len(p) > 0: o['protected'] = p.decode('utf-8') self._deserialize_b64(o, o['protected']) o['payload'] = base64url_decode(str(c[1])) o['signature'] = base64url_decode(str(c[2])) self.objects = o except Exception as e: # pylint: disable=broad-except raise InvalidJWSObject('Invalid format', repr(e)) if key: self.verify(key, alg)
def test_4_1_signing(self): plaintext = base64url_decode(Payload_plaintext_b64_4) protected = \ base64url_decode(JWS_Protected_Header_4_1_2).decode('utf-8') pub_key = jwk.JWK(**RSA_Public_Key_3_3) pri_key = jwk.JWK(**RSA_Private_Key_3_4) S = jws.JWS(payload=plaintext) S.add_signature(pri_key, None, protected) self.assertEqual(JWS_compact_4_1_3, S.serialize(compact=True)) S.deserialize(json_encode(JWS_general_4_1_3), pub_key) S.deserialize(json_encode(JWS_flattened_4_1_3), pub_key)
def test_4_6_signing(self): plaintext = base64url_decode(Payload_plaintext_b64_4) protected = \ base64url_decode(JWS_Protected_Header_4_6_2).decode('utf-8') header = json_encode(JWS_Unprotected_Header_4_6_2) key = jwk.JWK(**Symmetric_Key_MAC_3_5) S = jws.JWS(payload=plaintext) S.add_signature(key, None, protected, header) sig = S.serialize() S.deserialize(sig, key) self.assertEqual(json_decode(sig), JWS_flattened_4_6_3) # Just deserialize each example form S.deserialize(json_encode(JWS_general_4_6_3), key) S.deserialize(json_encode(JWS_flattened_4_6_3), key)
def test_5_10_encryption(self): plaintext = Payload_plaintext_5 protected = base64url_decode(JWE_Protected_Header_5_10_4) aad = base64url_decode(AAD_5_10_1) aes_key = jwk.JWK(**AES_key_5_8_1) E = jwe.JWE(plaintext, protected, aad=aad) E.add_recipient(aes_key) e = E.serialize() E.deserialize(e, aes_key) self.assertEqual(E.payload, plaintext) E.deserialize(json_encode(JWE_general_5_10_5), aes_key) self.assertEqual(E.payload, plaintext) E.deserialize(json_encode(JWE_flattened_5_10_5), aes_key) self.assertEqual(E.payload, plaintext)
def deserialize(self, raw_jws, key=None, alg=None): """Deserialize a JWS token. NOTE: Destroys any current status and tries to import the raw JWS provided. :param raw_jws: a 'raw' JWS token (JSON Encoded or Compact notation) string. :param key: A (:class:`jwcrypto.jwk.JWK`) verification key (optional). If a key is provided a verification step will be attempted after the object is successfully deserialized. :param alg: The signing algorithm (optional). usually the algorithm is known as it is provided with the JOSE Headers of the token. :raises InvalidJWSObject: if the raw object is an invaid JWS token. :raises InvalidJWSSignature: if the verification fails. """ self.objects = dict() o = dict() try: try: djws = json_decode(raw_jws) o['payload'] = base64url_decode(str(djws['payload'])) if 'signatures' in djws: o['signatures'] = list() for s in djws['signatures']: os = dict() os['signature'] = base64url_decode(str(s['signature'])) if 'protected' in s: p = base64url_decode(str(s['protected'])) os['protected'] = p.decode('utf-8') if 'header' in s: os['header'] = s['header'] o['signatures'].append(os) else: o['signature'] = base64url_decode(str(djws['signature'])) if 'protected' in djws: p = base64url_decode(str(djws['protected'])) o['protected'] = p.decode('utf-8') if 'header' in djws: o['header'] = djws['header'] except ValueError: c = raw_jws.split('.') if len(c) != 3: raise InvalidJWSObject('Unrecognized representation') p = base64url_decode(str(c[0])) if len(p) > 0: o['protected'] = p.decode('utf-8') o['payload'] = base64url_decode(str(c[1])) o['signature'] = base64url_decode(str(c[2])) self.objects = o except Exception as e: # pylint: disable=broad-except raise InvalidJWSObject('Invalid format', repr(e)) if key: self.verify(key, alg)
def _get_public_key(self, arg=None): if self._params['kty'] == 'oct': return self._key['k'] elif self._params['kty'] == 'RSA': return self._rsa_pub(self._key).public_key(default_backend()) elif self._params['kty'] == 'EC': try: return self._ec_pub(self._key, arg).public_key(default_backend()) except ValueError: # invalid point from jwcrypto.jwa import _decode_int return _decode_int(base64url_decode(self._key['x'])), _decode_int(base64url_decode(self._key['y'])) elif self._params['kty'] == 'OKP': return self._okp_pub(self._key) else: raise NotImplementedError
def get_key(self, key, op): if key.key_type != 'oct': raise InvalidJWEKeyType('oct', key.key_type) rk = base64url_decode(key.get_op_key(op)) if len(rk) != self.keysize: raise InvalidJWEKeyLength(self.keysize * 8, len(rk) * 8) return rk
def _okp_pri(self, k): try: privkey = _OKP_CURVES_TABLE[k['crv']].privkey except KeyError: raise InvalidJWKValue('Unknown curve "%s"' % k['crv']) return privkey.from_private_bytes(base64url_decode(k['d']))
def verify(self, key, payload, signature): vkey = base64url_decode(key.get_op_key('verify')) h = self._hmac_setup(vkey, payload) try: h.verify(signature) except InvalidSignature as e: raise InvalidJWSSignature(exception=e)
def _load(self): logger.debug('finding eq_session_id in database', eq_session_id=self.eq_session_id) self._eq_session = data_access.get_by_key(EQSession, self.eq_session_id) if self._eq_session: self.user_id = self._eq_session.user_id if self._eq_session.session_data: encrypted_session_data = self._eq_session.session_data session_data = StorageEncryption(self.user_id, self.user_ik, self.pepper)\ .decrypt_data(encrypted_session_data) # for backwards compatibility # session data used to be base64 encoded before encryption try: session_data = base64url_decode( session_data.decode()).decode() except ValueError: pass self.session_data = json.loads( session_data, object_hook=lambda d: SessionData(**d)) logger.debug( 'found matching eq_session for eq_session_id in database', session_id=self._eq_session.eq_session_id, user_id=self._eq_session.user_id) else: logger.debug('eq_session_id not found in database', eq_session_id=self.eq_session_id) return self._eq_session
def process_jwt(jwt): """ Process a JSON Web Token without verifying it. Call this before :func:`verify_jwt` if you need access to the header or claims in the token before verifying it. For example, the claims might identify the issuer such that you can retrieve the appropriate public key. :param jwt: The JSON Web Token to verify. :type jwt: str or unicode :rtype: tuple :returns: ``(header, claims)`` """ header, claims, _ = jwt.split('.') parsed_header = json_decode(base64url_decode(header)) parsed_claims = json_decode(base64url_decode(claims)) return parsed_header, parsed_claims
def _load(self) -> None: logger.debug("finding eq_session_id in database", eq_session_id=self.eq_session_id) self._eq_session: Optional[EQSession] = current_app.eq["storage"].get( EQSession, self.eq_session_id) # type: ignore if self._eq_session and self._eq_session.session_data: self.user_id = self._eq_session.user_id encrypted_session_data = self._eq_session.session_data session_data_as_bytes = StorageEncryption( self.user_id, self.user_ik, self.pepper).decrypt_data(encrypted_session_data) session_data_as_str = session_data_as_bytes.decode() # for backwards compatibility # session data used to be base64 encoded before encryption try: session_data_as_str = base64url_decode( session_data_as_str).decode() except ValueError: pass self.session_data = json_loads( session_data_as_str, object_hook=lambda d: SessionData(**d)) logger.debug( "found matching eq_session for eq_session_id in database", session_id=self._eq_session.eq_session_id, user_id=self._eq_session.user_id, ) else: logger.debug("eq_session_id not found in database", eq_session_id=self.eq_session_id)
def jti_size_should_be_as_expected(self, claims): """ Check jti size """ if jti_size and not callable( privk ): # don't assume format of externally-generated JTIs expect(len(base64url_decode( claims['jti']))).to_equal(jti_size)
def _get_key(self, alg, key, p2s, p2c): if not isinstance(key, JWK): # backwards compatiblity for old interface if isinstance(key, bytes): plain = key else: plain = key.encode('utf8') else: plain = base64url_decode(key.get_op_key()) 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 test_4_3_signing(self): plaintext = base64url_decode(Payload_plaintext_b64_4) protected = \ base64url_decode(JWS_Protected_Header_4_3_2).decode('utf-8') pub_key = jwk.JWK(**EC_Public_Key_3_1) pri_key = jwk.JWK(**EC_Private_Key_3_2) S = jws.JWS(payload=plaintext) S.add_signature(pri_key, None, protected) # Can't compare signature with reference because ECDSA uses # random nonces every time a signature is generated. sig = S.serialize() S.deserialize(sig, pub_key) # Just deserialize each example form S.deserialize(JWS_compact_4_3_3, pub_key) S.deserialize(json_encode(JWS_general_4_3_3), pub_key) S.deserialize(json_encode(JWS_flattened_4_3_3), pub_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 _verify(self, prop: str, key: JWK, alg: Optional[AlgorithmName], header: JsonObject, signer: Optional[JsonObject], patch_header: _PatchHeader) -> None: a = self._get_alg(alg, signer or header, InvalidJWSSignature) # Prepare payload for verification algorithm payload = copy(self._payload) h = copy(header) s = copy(signer) signature = base64url_decode((s or h).pop(_VALUE)) exclude = h.pop(_EXCLUDES, []) for x in exclude: payload.pop(x, None) h.update(patch_header(s)) payload[prop] = h canonical = _dumpb(payload) # Verify signature if key is None: key = JWK(**((s or h).get(_PUBLICKEY, None))) c = JWSCore(a, key, header=None, payload='', algs=self._allowed_algs) c.engine.verify(key, canonical, signature)
def unwrap(self, key, keylen, ek, headers): self._check_key(key) if ek != b'': raise InvalidJWEData('Invalid Encryption Key.') cek = base64url_decode(key.get_op_key('decrypt')) if len(cek) != keylen: raise InvalidJWEKeyLength(keylen, len(cek)) return cek
def unwrap(self, key, bitsize, ek, headers): rk = self._get_key(key, 'decrypt') if 'iv' not in headers: raise ValueError('Invalid Header, missing "iv" parameter') iv = base64url_decode(headers['iv']) if 'tag' not in headers: raise ValueError('Invalid Header, missing "tag" parameter') tag = base64url_decode(headers['tag']) cipher = Cipher(algorithms.AES(rk), modes.GCM(iv, tag), backend=self.backend) decryptor = cipher.decryptor() cek = decryptor.update(ek) + decryptor.finalize() if _bitsize(cek) != bitsize: raise InvalidJWEKeyLength(bitsize, _bitsize(cek)) return cek
def wrap(self, key, bitsize, cek, headers): self._check_key(key) if cek: return (cek, None) k = base64url_decode(key.get_op_key('encrypt')) if _bitsize(k) != bitsize: raise InvalidCEKeyLength(bitsize, _bitsize(k)) return {'cek': k}
def _get_base64_encoded_data(self, data): """ Legacy data was stored in a dict, base64-encoded, and not compressed: { 'data': '<base 64 encoded and encrypted data' } """ data = json.loads(data).get('data') decrypted_data = self.encrypter.decrypt_data(data) return base64url_decode(decrypted_data.decode()).decode()
def unwrap(self, key, keylen, ek, headers): rk = self._get_key(key, 'decrypt') if 'iv' not in headers: raise InvalidJWEData('Invalid Header, missing "iv" parameter') iv = base64url_decode(headers['iv']) if 'tag' not in headers: raise InvalidJWEData('Invalid Header, missing "tag" parameter') tag = base64url_decode(headers['tag']) cipher = Cipher(algorithms.AES(rk), modes.GCM(iv, tag), backend=self.backend) decryptor = cipher.decryptor() cek = decryptor.update(ek) + decryptor.finalize() if len(cek) != keylen: raise InvalidJWEKeyLength(keylen, len(cek)) return cek
def wrap(self, key, keylen, cek): self.check_key(key) if cek: return (cek, None) k = base64url_decode(key.get_op_key('encrypt')) if len(k) != keylen: raise InvalidCEKeyLength(keylen, len(k)) return (k, '')
def unwrap(self, key, bitsize, ek, headers): self._check_key(key) if ek != b'': raise ValueError('Invalid Encryption Key.') cek = base64url_decode(key.get_op_key('decrypt')) if _bitsize(cek) != bitsize: raise InvalidJWEKeyLength(bitsize, _bitsize(cek)) return cek
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 _okp_pub(self): crv = self._params.get('crv', None) try: pubkey = _OKP_CURVES_TABLE[crv].pubkey except KeyError: raise InvalidJWKValue('Unknown curve "%s"' % crv) x = base64url_decode(self._params['x']) return pubkey.from_public_bytes(x)
def _okp_pri(self): crv = self._params.get('crv', None) try: privkey = _OKP_CURVES_TABLE[crv].privkey except KeyError: raise InvalidJWKValue('Unknown curve "%s"' % crv) d = base64url_decode(self._params['d']) return privkey.from_private_bytes(d)
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)) if len(rk) != self.keysize: raise InvalidJWEKeyLength(self.keysize * 8, len(rk) * 8) return rk
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)) if _bitsize(rk) != self.keysize: raise InvalidJWEKeyLength(self.keysize, _bitsize(rk)) return rk
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 unwrap(self, key, keylen, ek, headers): if 'p2s' not in headers: raise InvalidJWEData('Invalid Header, missing "p2s" parameter') if 'p2c' not in headers: raise InvalidJWEData('Invalid Header, missing "p2c" parameter') p2s = base64url_decode(headers['p2s']) p2c = headers['p2c'] kek = self._get_key(headers['alg'], key, p2s, p2c) aeskw = _AesKw(self.keysize * 8) return aeskw.unwrap(kek, keylen, ek, headers)
def f(sjwt, iat_skew=timedelta()): """ verify token using node-jsjws """ r = spawn( "fixtures.verify({now}, {sjwt}, {iat_skew}, {key}, {alg})".format( now=timegm(datetime.utcnow().utctimetuple()), sjwt=json_encode(sjwt), iat_skew=iat_skew.total_seconds(), key=json_encode(base64url_decode(json_decode(key.export())['k']) if key.is_symmetric else key.export_to_pem()), alg=json_encode(alg)), True) return tuple(r)
def f(claims, alg, lifetime=None, expires=None, not_before=None): """ generate token using node-jsjws """ now = datetime.utcnow() return spawn( "fixtures.generate({now}, {header}, {claims}, {expires}, {not_before}, {key})".format( now=timegm(now.utctimetuple()), header=json_encode({'alg': alg}), claims=json_encode(claims), expires=timegm(((now + lifetime) if lifetime else expires).utctimetuple()), not_before=timegm((not_before or now).utctimetuple()), key=json_encode(base64url_decode(json_decode(key.export())['k']) if key.is_symmetric else key.export_to_pem(True, None))), False)
def test_5_6_encryption(self): plaintext = Payload_plaintext_5 protected = base64url_decode(JWE_Protected_Header_5_6_3) aes_key = jwk.JWK(**AES_key_5_6_1) E = jwe.JWE(plaintext, protected) E.add_recipient(aes_key) _ = E.serialize(compact=True) e = E.serialize() E.deserialize(e, aes_key) self.assertEqual(E.payload, plaintext) E.deserialize(JWE_compact_5_6_4, aes_key) self.assertEqual(E.payload, plaintext) E.deserialize(json_encode(JWE_general_5_6_4), aes_key) self.assertEqual(E.payload, plaintext)
def test_5_2_encryption(self): plaintext = Payload_plaintext_5 protected = base64url_decode(JWE_Protected_Header_5_2_4) rsa_key = jwk.JWK(**RSA_key_5_2_1) E = jwe.JWE(plaintext, protected) E.add_recipient(rsa_key) e = E.serialize() E.deserialize(e, rsa_key) self.assertEqual(E.payload, plaintext) E.deserialize(JWE_compact_5_2_5, rsa_key) self.assertEqual(E.payload, plaintext) E.deserialize(json_encode(JWE_general_5_2_5), rsa_key) self.assertEqual(E.payload, plaintext) E.deserialize(json_encode(JWE_flattened_5_2_5), rsa_key) self.assertEqual(E.payload, plaintext)
def check_sign(self, test): s = jws.JWSCore(test['alg'], jwk.JWK(**test['key']), test['protected'], test['payload'], test.get('allowed_algs', None)) sig = s.sign() decsig = base64url_decode(sig['signature']) s.verify(decsig) # ECDSA signatures are always different every time # they are generated unlike RSA or symmetric ones if test['key']['kty'] != 'EC': self.assertEqual(decsig, test['signature']) else: # Check we can verify the test signature independently # this is so taht we can test the ECDSA agaist a known # good signature s.verify(test['signature'])
def deserialize(self, raw_jwe, key=None): """Deserialize a JWE token. NOTE: Destroys any current status and tries to import the raw JWE provided. :param raw_jwe: a 'raw' JWE token (JSON Encoded or Compact notation) string. :param key: A (:class:`jwcrypto.jwk.JWK`) decryption key (optional). If a key is provided a idecryption step will be attempted after the object is successfully deserialized. :raises InvalidJWEData: if the raw object is an invaid JWE token. :raises InvalidJWEOperation: if the decryption fails. """ self.objects = dict() self.plaintext = None self.cek = None o = dict() try: try: djwe = json_decode(raw_jwe) o['iv'] = base64url_decode(str(djwe['iv'])) o['ciphertext'] = base64url_decode(str(djwe['ciphertext'])) o['tag'] = base64url_decode(str(djwe['tag'])) if 'protected' in djwe: p = base64url_decode(str(djwe['protected'])) o['protected'] = p.decode('utf-8') if 'unprotected' in djwe: o['unprotected'] = json_encode(djwe['unprotected']) if 'aad' in djwe: o['aad'] = base64url_decode(str(djwe['aad'])) if 'recipients' in djwe: o['recipients'] = list() for rec in djwe['recipients']: e = dict() if 'encrypted_key' in rec: e['encrypted_key'] = \ base64url_decode(str(rec['encrypted_key'])) if 'header' in rec: e['header'] = json_encode(rec['header']) o['recipients'].append(e) else: if 'encrypted_key' in djwe: o['encrypted_key'] = \ base64url_decode(str(djwe['encrypted_key'])) if 'header' in djwe: o['header'] = json_encode(djwe['header']) except ValueError: c = raw_jwe.split('.') if len(c) != 5: raise InvalidJWEData() p = base64url_decode(str(c[0])) o['protected'] = p.decode('utf-8') ekey = base64url_decode(str(c[1])) if ekey != '': o['encrypted_key'] = base64url_decode(str(c[1])) o['iv'] = base64url_decode(str(c[2])) o['ciphertext'] = base64url_decode(str(c[3])) o['tag'] = base64url_decode(str(c[4])) self.objects = o except Exception as e: # pylint: disable=broad-except raise InvalidJWEData('Invalid format', repr(e)) if key: self.decrypt(key)
def unwrap(self, key, ek): self.check_key(key) if ek != b'': raise InvalidJWEData('Invalid Encryption Key.') return base64url_decode(key.get_op_key('decrypt'))
def jti_size_should_be_as_expected(self, claims): """ Check jti size """ if jti_size and not callable(privk): # don't assume format of externally-generated JTIs expect(len(base64url_decode(claims['jti']))).to_equal(jti_size)
def verify_jwt(jwt, pub_key=None, allowed_algs=None, iat_skew=timedelta(), checks_optional=False, ignore_not_implemented=False): """ Verify a JSON Web Token. :param jwt: The JSON Web Token to verify. :type jwt: str or unicode :param pub_key: The public key to be used to verify the token. Note: if you pass ``None`` and **allowed_algs** contains ``none`` then the token's signature will not be verified. :type pub_key: `jwcrypto.jwk.JWK <https://jwcrypto.readthedocs.io/en/latest/jwk.html>`_ :param allowed_algs: Algorithms expected to be used to sign the token. The ``in`` operator is used to test membership. :type allowed_algs: list or NoneType (meaning an empty list) :param iat_skew: The amount of leeway to allow between the issuer's clock and the verifier's clock when verifiying that the token was generated in the past. Defaults to no leeway. :type iat_skew: datetime.timedelta :param checks_optional: If ``False``, then the token must contain the **typ** header property and the **iat**, **nbf** and **exp** claim properties. :type checks_optional: bool :param ignore_not_implemented: If ``False``, then the token must *not* contain the **jku**, **jwk**, **x5u**, **x5c** or **x5t** header properties. :type ignore_not_implemented: bool :rtype: tuple :returns: ``(header, claims)`` if the token was verified successfully. The token must pass the following tests: - Its header must contain a property **alg** with a value in **allowed_algs**. - Its signature must verify using **pub_key** (unless its algorithm is ``none`` and ``none`` is in **allowed_algs**). - If the corresponding property is present or **checks_optional** is ``False``: - Its header must contain a property **typ** with the value ``JWT``. - Its claims must contain a property **iat** which represents a date in the past (taking into account :obj:`iat_skew`). - Its claims must contain a property **nbf** which represents a date in the past. - Its claims must contain a property **exp** which represents a date in the future. :raises: If the token failed to verify. """ if allowed_algs is None: allowed_algs = [] if not isinstance(allowed_algs, list): # jwcrypto only supports list of allowed algorithms raise _JWTError('allowed_algs must be a list') header, claims, _ = jwt.split('.') parsed_header = json_decode(base64url_decode(header)) alg = parsed_header.get('alg') if alg is None: raise _JWTError('alg header not present') if alg not in allowed_algs: raise _JWTError('algorithm not allowed: ' + alg) if not ignore_not_implemented: for k in parsed_header: if k not in JWSHeaderRegistry: raise _JWTError('unknown header: ' + k) if not JWSHeaderRegistry[k].supported: raise _JWTError('header not implemented: ' + k) if pub_key: token = JWS() token.allowed_algs = allowed_algs token.deserialize(jwt, pub_key) elif 'none' not in allowed_algs: raise _JWTError('no key but none alg not allowed') parsed_claims = json_decode(base64url_decode(claims)) utcnow = datetime.utcnow() now = timegm(utcnow.utctimetuple()) typ = parsed_header.get('typ') if typ is None: if not checks_optional: raise _JWTError('typ header not present') elif typ != 'JWT': raise _JWTError('typ header is not JWT') iat = parsed_claims.get('iat') if iat is None: if not checks_optional: raise _JWTError('iat claim not present') elif iat > timegm((utcnow + iat_skew).utctimetuple()): raise _JWTError('issued in the future') nbf = parsed_claims.get('nbf') if nbf is None: if not checks_optional: raise _JWTError('nbf claim not present') elif nbf > now: raise _JWTError('not yet valid') exp = parsed_claims.get('exp') if exp is None: if not checks_optional: raise _JWTError('exp claim not present') elif exp <= now: raise _JWTError('expired') return parsed_header, parsed_claims
def _decode_int(self, n): return int(hexlify(base64url_decode(n)), 16)
def sign(self, key, payload): skey = base64url_decode(key.get_op_key('sign')) h = self._hmac_setup(skey, payload) return h.finalize()
def add_signature(self, key, alg=None, protected=None, header=None): """Adds a new signature to the object. :param key: A (:class:`jwcrypto.jwk.JWK`) key of appropriate for the "alg" provided. :param alg: An optional algorithm name. If already provided as an element of the protected or unprotected header it can be safely omitted. :param potected: The Protected Header (optional) :param header: The Unprotected Header (optional) :raises InvalidJWSObject: if no payload has been set on the object. :raises ValueError: if the key is not a :class:`JWK` object. :raises ValueError: if the algorithm is missing or is not provided by one of the headers. :raises InvalidJWAAlgorithm: if the algorithm is not valid, is unknown or otherwise not yet implemented. """ if not self.objects.get('payload', None): raise InvalidJWSObject('Missing Payload') p = dict() if protected: p = json_decode(protected) # TODO: allow caller to specify list of headers it understands if 'crit' in p: self._check_crit(p['crit']) if header: h = json_decode(header) p = self._merge_headers(p, h) if 'alg' in p: if alg is None: alg = p['alg'] elif alg != p['alg']: raise ValueError('"alg" value mismatch, specified "alg" ' 'does not match JOSE header value') if alg is None: raise ValueError('"alg" not specified') S = JWSCore(alg, key, protected, self.objects['payload']) sig = S.sign() o = dict() o['signature'] = base64url_decode(sig['signature']) if protected: o['protected'] = protected if header: o['header'] = h o['valid'] = True if 'signatures' in self.objects: self.objects['signatures'].append(o) elif 'signature' in self.objects: self.objects['signatures'] = list() n = dict() n['signature'] = self.objects['signature'] del self.objects['signature'] if 'protected' in self.objects: n['protected'] = self.objects['protected'] del self.objects['protected'] if 'header' in self.objects: n['header'] = self.objects['header'] del self.objects['header'] if 'valid' in self.objects: n['valid'] = self.objects['valid'] del self.objects['valid'] self.objects['signatures'].append(n) self.objects['signatures'].append(o) else: self.objects.update(o)
A4_example = {'key': A4_key, 'alg': 'ES512', 'protected': bytes(bytearray(A4_protected)).decode('utf-8'), 'payload': bytes(bytearray(A4_payload)), 'signature': bytes(bytearray(A4_signature))} # RFC 7515 - A.4 A5_protected = 'eyJhbGciOiJub25lIn0' A5_payload = A2_payload A5_key = \ {"kty": "oct", "k": ""} A5_signature = b'' A5_example = {'key': A5_key, 'alg': 'none', 'protected': base64url_decode(A5_protected).decode('utf-8'), 'payload': bytes(bytearray(A5_payload)), 'signature': A5_signature} A6_serialized = \ '{' + \ '"payload":' + \ '"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF' + \ 'tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",' + \ '"signatures":[' + \ '{"protected":"eyJhbGciOiJSUzI1NiJ9",' + \ '"header":' + \ '{"kid":"2010-12-29"},' + \ '"signature":' + \ '"cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZ' + \ 'mh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjb' + \