def _validate_json_jws(self, payload_segment, payload, header_obj, key): protected_segment = header_obj.get('protected') if not protected_segment: raise DecodeError('Missing "protected" value') signature_segment = header_obj.get('signature') if not signature_segment: raise DecodeError('Missing "signature" value') protected_segment = to_bytes(protected_segment) protected = _extract_header(protected_segment) header = header_obj.get('header') if header and not isinstance(header, dict): raise DecodeError('Invalid "header" value') jws_header = JWSHeader(protected, header) self._validate_header(jws_header) algorithm, key = prepare_algorithm_key(self._algorithms, jws_header, payload, key) signing_input = b'.'.join([protected_segment, payload_segment]) signature = _extract_signature(to_bytes(signature_segment)) if algorithm.verify(signing_input, key, signature): return jws_header, True return jws_header, False
def decode_payload(bytes_payload): try: payload = json.loads(to_unicode(bytes_payload)) except ValueError: raise DecodeError('Invalid payload value') if not isinstance(payload, dict): raise DecodeError('Invalid payload type') return payload
def _ensure_dict(s): if not isinstance(s, dict): try: s = json.loads(to_unicode(s)) except (ValueError, TypeError): raise DecodeError('Invalid JWS') if not isinstance(s, dict): raise DecodeError('Invalid JWS') return s
def ensure_dict(s, structure_name): if not isinstance(s, dict): try: s = json_loads(to_unicode(s)) except (ValueError, TypeError): raise DecodeError('Invalid {}'.format(structure_name)) if not isinstance(s, dict): raise DecodeError('Invalid {}'.format(structure_name)) return s
def deserialize_compact(self, s, key, decode=None): """Exact JWS Compact Serialization, and validate with the given key. If key is not provided, the returned dict will contain the signature, and signing input values. Via `Section 7.1`_. :param s: text of JWS Compact Serialization :param key: key used to verify the signature :param decode: a function to decode payload data :return: JWSObject :raise: BadSignatureError .. _`Section 7.1`: https://tools.ietf.org/html/rfc7515#section-7.1 """ try: s = to_bytes(s) signing_input, signature_segment = s.rsplit(b'.', 1) protected_segment, payload_segment = signing_input.split(b'.', 1) except ValueError: raise DecodeError('Not enough segments') protected = _extract_header(protected_segment) jws_header = JWSHeader(protected, None) payload = _extract_payload(payload_segment) if decode: payload = decode(payload) signature = _extract_signature(signature_segment) rv = JWSObject(jws_header, payload, 'compact') algorithm, key = self._prepare_algorithm_key(jws_header, payload, key) if algorithm.verify(signing_input, signature, key): return rv raise BadSignatureError(rv)
def deserialize_compact(self, s, key, decode=None): """Exact JWS Compact Serialization, and validate with the given key. :param s: text of JWS Compact Serialization :param key: key used to verify the signature :param decode: a function to decode plaintext data :return: dict """ try: s = to_bytes(s) protected_s, ek_s, iv_s, ciphertext_s, tag_s = s.rsplit(b'.') except ValueError: raise DecodeError('Not enough segments') protected = extract_header(protected_s, DecodeError) ek = extract_segment(ek_s, DecodeError, 'encryption key') iv = extract_segment(iv_s, DecodeError, 'initialization vector') ciphertext = extract_segment(ciphertext_s, DecodeError, 'ciphertext') tag = extract_segment(tag_s, DecodeError, 'authentication tag') self._pre_validate_header(protected) algorithm, enc_alg, key = self._prepare_alg_enc_key(protected, key, private=True) self._post_validate_header(protected, algorithm) cek = algorithm.unwrap(ek, protected, key) aad = to_bytes(protected_s, 'ascii') msg = enc_alg.decrypt(ciphertext, aad, iv, tag, cek) payload = self._zip_decompress(msg, protected) if decode: payload = decode(payload) return {'header': protected, 'payload': payload}
def decode(self, s, key, claims_cls=None, claims_options=None, claims_params=None): """Decode the JWS with the given key. This is similar with :meth:`verify`, except that it will raise BadSignatureError when signature doesn't match. :param s: text of JWT :param key: key used to verify the signature :param claims_cls: class to be used for JWT claims :param claims_options: `options` parameters for claims_cls :param claims_params: `params` parameters for claims_cls :return: claims_cls instance :raise: BadSignatureError """ if claims_cls is None: claims_cls = JWTClaims key_func = create_key_func(key) s = to_bytes(s) dot_count = s.count(b'.') if dot_count == 2: data = self._jws.deserialize_compact(s, key_func, decode_payload) elif dot_count == 4: data = self._jwe.deserialize_compact(s, key_func, decode_payload) else: raise DecodeError('Invalid input segments length') return claims_cls( data['payload'], data['header'], options=claims_options, params=claims_params, )
def deserialize_json(self, obj, key, decode=None): """Exact JWS JSON Serialization, and validate with the given key. If key is not provided, it will return a dict without signature verification. Header will still be validated. Via `Section 7.2`_. :param obj: text of JWS JSON Serialization :param key: key used to verify the signature :param decode: a function to decode payload data :return: JWSObject :raise: BadSignatureError .. _`Section 7.2`: https://tools.ietf.org/html/rfc7515#section-7.2 """ obj = ensure_dict(obj, 'JWS') payload_segment = obj.get('payload') if not payload_segment: raise DecodeError('Missing "payload" value') payload_segment = to_bytes(payload_segment) payload = _extract_payload(payload_segment) if decode: payload = decode(payload) if 'signatures' not in obj: # flattened JSON JWS jws_header, valid = self._validate_json_jws( payload_segment, payload, obj, key) rv = JWSObject(jws_header, payload, 'flat') if valid: return rv raise BadSignatureError(rv) headers = [] is_valid = True for header_obj in obj['signatures']: jws_header, valid = self._validate_json_jws( payload_segment, payload, header_obj, key) headers.append(jws_header) if not valid: is_valid = False rv = JWSObject(headers, payload, 'json') if is_valid: return rv raise BadSignatureError(rv)
def deserialize_compact(self, s, key, decode=None, sender_key=None): """Extract JWE Compact Serialization. :param s: JWE Compact Serialization as bytes :param key: Private key used to decrypt payload (optionally can be a tuple of kid and essentially key) :param decode: Function to decode payload data :param sender_key: Sender's public key in case JWEAlgorithmWithTagAwareKeyAgreement is used :return: dict with `header` and `payload` keys where `header` value is a dict containing protected header fields """ try: s = to_bytes(s) protected_s, ek_s, iv_s, ciphertext_s, tag_s = s.rsplit(b'.') except ValueError: raise DecodeError('Not enough segments') protected = extract_header(protected_s, DecodeError) ek = extract_segment(ek_s, DecodeError, 'encryption key') iv = extract_segment(iv_s, DecodeError, 'initialization vector') ciphertext = extract_segment(ciphertext_s, DecodeError, 'ciphertext') tag = extract_segment(tag_s, DecodeError, 'authentication tag') alg = self.get_header_alg(protected) enc = self.get_header_enc(protected) zip_alg = self.get_header_zip(protected) self._validate_sender_key(sender_key, alg) self._validate_private_headers(protected, alg) if isinstance(key, tuple) and len(key) == 2: # Ignore separately provided kid, extract essentially key only key = key[1] key = prepare_key(alg, protected, key) if sender_key is not None: sender_key = alg.prepare_key(sender_key) if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement): # For a JWE algorithm with tag-aware key agreement: if alg.key_size is not None: # In case key agreement with key wrapping mode is used: # Provide authentication tag to .unwrap method cek = alg.unwrap(enc, ek, protected, key, sender_key, tag) else: # Otherwise, don't provide authentication tag to .unwrap method cek = alg.unwrap(enc, ek, protected, key, sender_key) else: # For any other JWE algorithm: # Don't provide authentication tag to .unwrap method cek = alg.unwrap(enc, ek, protected, key) aad = to_bytes(protected_s, 'ascii') msg = enc.decrypt(ciphertext, aad, iv, tag, cek) if zip_alg: payload = zip_alg.decompress(to_bytes(msg)) else: payload = msg if decode: payload = decode(payload) return {'header': protected, 'payload': payload}