def build(self, json_web_key=None, ensure_ascii=True): """ Builds a DockerSchema1Manifest object, with optional signature. """ payload = OrderedDict(self._base_payload) payload.update({ DOCKER_SCHEMA1_HISTORY_KEY: self._history, DOCKER_SCHEMA1_FS_LAYERS_KEY: self._fs_layer_digests, }) payload_str = json.dumps(payload, indent=3, ensure_ascii=ensure_ascii) if json_web_key is None: return DockerSchema1Manifest( Bytes.for_string_or_unicode(payload_str)) payload_str = Bytes.for_string_or_unicode(payload_str).as_encoded_str() split_point = payload_str.rfind(b"\n}") protected_payload = { "formatTail": base64url_encode(payload_str[split_point:]).decode("ascii"), "formatLength": split_point, "time": datetime.utcnow().strftime(_ISO_DATETIME_FORMAT_ZULU), } protected = base64url_encode( json.dumps(protected_payload, ensure_ascii=ensure_ascii).encode("utf-8")) logger.debug("Generated protected block: %s", protected) bytes_to_sign = b"%s.%s" % (protected, base64url_encode(payload_str)) signer = SIGNER_ALGS[_JWS_SIGNING_ALGORITHM] signature = base64url_encode( signer.sign(bytes_to_sign, json_web_key.get_key())) logger.debug("Generated signature: %s", signature) public_members = set(json_web_key.public_members) public_key = { comp: value for comp, value in list(json_web_key.to_dict().items()) if comp in public_members } signature_block = { DOCKER_SCHEMA1_HEADER_KEY: { "jwk": public_key, "alg": _JWS_SIGNING_ALGORITHM }, DOCKER_SCHEMA1_SIGNATURE_KEY: signature.decode("ascii"), DOCKER_SCHEMA1_PROTECTED_KEY: protected.decode("ascii"), } logger.debug("Encoded signature block: %s", json.dumps(signature_block)) payload.update({DOCKER_SCHEMA1_SIGNATURES_KEY: [signature_block]}) json_str = json.dumps(payload, indent=3, ensure_ascii=ensure_ascii) return DockerSchema1Manifest(Bytes.for_string_or_unicode(json_str))
def encode(self, payload, signing_key): if not isinstance(payload, Mapping): raise TypeError('Expecting a mapping object, as only ' 'JSON objects can be used as payloads.') token_segments = [] signing_key = load_signing_key(signing_key, self.crypto_backend) # prepare header header = {'typ': self.token_type, 'alg': self.signing_algorithm} token_segments.append(base64url_encode(json_encode(header))) # prepare payload token_segments.append(base64url_encode(json_encode(payload))) # prepare signature signing_input = b'.'.join(token_segments) signer = self._get_signer(signing_key) signer.update(signing_input) signature = signer.finalize() raw_signature = der_to_raw_signature(signature, signing_key.curve) token_segments.append(base64url_encode(raw_signature)) # combine the header, payload, and signature into a token and return it token = b'.'.join(token_segments) return token
def _validate(self): """ Reference: https://docs.docker.com/registry/spec/manifest-v2-1/#signed-manifests """ if not self._signatures: return payload_str = self._payload for signature in self._signatures: protected = signature[DOCKER_SCHEMA1_PROTECTED_KEY] sig = signature[DOCKER_SCHEMA1_SIGNATURE_KEY] jwk = JsonWebKey.import_key( signature[DOCKER_SCHEMA1_HEADER_KEY]["jwk"]) jws = JsonWebSignature( algorithms=[signature[DOCKER_SCHEMA1_HEADER_KEY]["alg"]]) obj_to_verify = { DOCKER_SCHEMA1_PROTECTED_KEY: protected, DOCKER_SCHEMA1_SIGNATURE_KEY: sig, DOCKER_SCHEMA1_HEADER_KEY: { "alg": signature[DOCKER_SCHEMA1_HEADER_KEY]["alg"] }, "payload": base64url_encode(payload_str), } try: data = jws.deserialize_json(obj_to_verify, jwk.get_public_key()) except (BadSignatureError, UnsupportedAlgorithmError): raise InvalidSchema1Signature() if not data: raise InvalidSchema1Signature()
def encode(self, payload, signing_key=None): if not isinstance(payload, Mapping): raise TypeError('Expecting a mapping object, as only ' 'JSON objects can be used as payloads.') if not signing_key: # create unsecured token header = {'typ': self.token_type, 'alg': 'none'} return self._create_signing_input(payload, header) + b'.' signing_key = load_signing_key( BitcoinPrivateKey(signing_key).to_pem(), self.crypto_backend) # prepare header header = {'typ': self.token_type, 'alg': self.signing_algorithm} # get token signing_input signing_input = self._create_signing_input(payload, header) # prepare signature signer = self._get_signer(signing_key) signer.update(signing_input) signature = signer.finalize() raw_signature = der_to_raw_signature(signature, signing_key.curve) # combine the header, payload, and signature into a token and return it return signing_input + b'.' + base64url_encode(raw_signature)
def decode(cls, token): header, payload, raw_signature, signing_input = cls._unpack(token) token = { "header": header, "payload": payload, "signature": base64url_encode(raw_signature) } return token
def test_invalid_jku_in_token_header(self): uaa_config = uaa_configs.VALID['uaa'] header = get_unverified_header(jwt_tokens.CORRECT_END_USER_TOKEN) header['jku'] = 'http://ana.ondemandh.com\\\\\\\\\\\\\\\\@' + uaa_config['uaadomain'] token_parts = jwt_tokens.CORRECT_END_USER_TOKEN.split(".") token_parts[0] = base64url_encode(json.dumps(header).encode('utf-8')).decode() token = '.'.join(token_parts) with self.assertRaises(RuntimeError) as e: xssec.create_security_context(token, uaa_config) self.assertEqual("JKU of token is not trusted", str(e.exception),)
def forged_user_header(jwt_generator): """Return JWT token with a forged UID claim""" token = jwt_generator(uid='bozydar') # Decode token: header_bytes, payload_bytes, signature_bytes = [ base64url_decode(_.encode('ascii')) for _ in token.split(".")] payload_dict = json.loads(payload_bytes.decode('ascii')) # Rewrite uid and invert token decode procedure. payload_dict['uid'] = 'fafok' payload_bytes = json.dumps(payload_dict).encode('utf-8') forged_token = '.'.join( base64url_encode(_).decode('ascii') for _ in ( header_bytes, payload_bytes, signature_bytes) ) header = {'Authorization': 'token={}'.format(forged_token)} return header
def calculate_at_hash(access_token, hash_alg): """Helper method for calculating an access token hash, as described in http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the access_token value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the access_token value with SHA-256, then take the left-most 128 bits and base64url encode them. The at_hash value is a case sensitive string. Args: access_token (str): An access token string. hash_alg (callable): A callable returning a hash object, e.g. hashlib.sha256 """ hash_digest = hash_alg(access_token.encode('utf-8')).digest() cut_at = int(len(hash_digest) / 2) truncated = hash_digest[:cut_at] from jwt.utils import base64url_encode at_hash = base64url_encode(truncated) return at_hash.decode('utf-8')
def forged_user_header(jwt_generator): """Return JWT token with a forged UID claim""" token = jwt_generator(uid='bozydar') # Decode token: header_bytes, payload_bytes, signature_bytes = [ base64url_decode(_.encode('ascii')) for _ in token.split(".") ] payload_dict = json.loads(payload_bytes.decode('ascii')) # Rewrite uid and invert token decode procedure. payload_dict['uid'] = 'fafok' payload_bytes = json.dumps(payload_dict).encode('utf-8') forged_token = '.'.join( base64url_encode(_).decode('ascii') for _ in (header_bytes, payload_bytes, signature_bytes)) header = {'Authorization': 'token={}'.format(forged_token)} return header
def _validate(self): if not self._signatures: return payload_str = self._payload for signature in self._signatures: bytes_to_verify = "{0}.{1}".format(signature["protected"], base64url_encode(payload_str)) signer = SIGNER_ALGS[signature["header"]["alg"]] key = keyrep(signature["header"]["jwk"]) gk = key.get_key() sig = base64url_decode(signature["signature"].encode("utf-8")) try: verified = signer.verify(bytes_to_verify, sig, gk) except BadSignature: raise InvalidSchema1Signature() if not verified: raise InvalidSchema1Signature()
def _validate(self): if not self._signatures: return payload_str = self._payload for signature in self._signatures: bytes_to_verify = '{0}.{1}'.format(signature['protected'], base64url_encode(payload_str)) signer = SIGNER_ALGS[signature['header']['alg']] key = keyrep(signature['header']['jwk']) gk = key.get_key() sig = base64url_decode(signature['signature'].encode('utf-8')) try: verified = signer.verify(bytes_to_verify, sig, gk) except BadSignature: raise InvalidSchema1Signature() if not verified: raise InvalidSchema1Signature()
def test_login_fails_bad_signature(self, dex): # This token is not expired, but has a bad signature. id_token = self._get_id_token(dex, '*****@*****.**', 'password') header_bytes, payload_bytes, signature_bytes = [ base64url_decode(_.encode('ascii')) for _ in id_token.split(".") ] payload_dict = json.loads(payload_bytes.decode('ascii')) # Change `email` and invert token decode procedure. forged_payload_dict = payload_dict.copy() forged_payload_dict['email'] = '*****@*****.**' forged_payload_bytes = json.dumps(forged_payload_dict).encode('utf-8') forged_token = '.'.join( base64url_encode(_).decode('ascii') for _ in (header_bytes, forged_payload_bytes, signature_bytes)) r = requests.post(Url('/auth/login'), json={'token': forged_token}) assert r.status_code == 401 assert 'bad token' in r.json()['description']
def build(self, json_web_key=None, ensure_ascii=True): """ Builds a DockerSchema1Manifest object, with optional signature. NOTE: For backward compatibility, "JWS JSON Serialization" is used instead of "JWS Compact Serialization", since the latter **requires** that the "alg" headers be carried in the **protected** headers, which was never done before migrating to authlib (One shouldn't be using schema1 anyways) References: - https://tools.ietf.org/html/rfc7515#section-10.7 - https://docs.docker.com/registry/spec/manifest-v2-1/#signed-manifests """ payload = OrderedDict(self._base_payload) payload.update({ DOCKER_SCHEMA1_HISTORY_KEY: self._history, DOCKER_SCHEMA1_FS_LAYERS_KEY: self._fs_layer_digests, }) payload_str = json.dumps(payload, indent=3, ensure_ascii=ensure_ascii) if json_web_key is None: return DockerSchema1Manifest( Bytes.for_string_or_unicode(payload_str)) payload_str = Bytes.for_string_or_unicode(payload_str).as_encoded_str() split_point = payload_str.rfind(b"\n}") protected_payload = { DOCKER_SCHEMA1_FORMAT_TAIL_KEY: base64url_encode(payload_str[split_point:]).decode("ascii"), DOCKER_SCHEMA1_FORMAT_LENGTH_KEY: split_point, "time": datetime.utcnow().strftime(_ISO_DATETIME_FORMAT_ZULU), } # Flattened JSON serialization header jws = JsonWebSignature(algorithms=[_JWS_SIGNING_ALGORITHM]) headers = { "protected": protected_payload, "header": { "alg": _JWS_SIGNING_ALGORITHM }, } signed = jws.serialize_json(headers, payload_str, json_web_key.get_private_key()) protected = signed["protected"] signature = signed["signature"] logger.debug("Generated signature: %s", signature) logger.debug("Generated protected block: %s", protected) public_members = set(json_web_key.REQUIRED_JSON_FIELDS + json_web_key.ALLOWED_PARAMS) public_key = { comp: value for comp, value in list(json_web_key.as_dict().items()) if comp in public_members } public_key["kty"] = json_web_key.kty # Signed Docker schema 1 manifests require the kid to be in a specific format # https://docs.docker.com/registry/spec/auth/jwt/ pub_key = json_web_key.get_public_key() # Take the DER encoded public key which the JWT token was signed against key_der = pub_key.public_bytes( encoding=Encoding.DER, format=PublicFormat.SubjectPublicKeyInfo) # Create a SHA256 hash out of it and truncate to 240bits hash256 = sha256() hash256.update(key_der) digest = hash256.digest() digest_first_240_bits = digest[:30] # Split the result into 12 base32 encoded groups with : as delimiter base32 = base64.b32encode(digest_first_240_bits).decode("ascii") kid = "" i = 0 for i in range(0, int(len(base32) / 4) - 1): start = i * 4 end = start + 4 kid += base32[start:end] + ":" kid += base32[(i + 1) * 4:] # Add the last group without the delimiter public_key["kid"] = kid signature_block = { DOCKER_SCHEMA1_HEADER_KEY: { "jwk": public_key, "alg": _JWS_SIGNING_ALGORITHM }, DOCKER_SCHEMA1_SIGNATURE_KEY: signature, DOCKER_SCHEMA1_PROTECTED_KEY: protected, } logger.debug("Encoded signature block: %s", json.dumps(signature_block)) payload.update({DOCKER_SCHEMA1_SIGNATURES_KEY: [signature_block]}) json_str = json.dumps(payload, indent=3, ensure_ascii=ensure_ascii) return DockerSchema1Manifest(Bytes.for_string_or_unicode(json_str))
def build(self, json_web_key=None, ensure_ascii=True): """ Builds a DockerSchema1Manifest object, with optional signature. NOTE: For backward compatibility, "JWS JSON Serialization" is used instead of "JWS Compact Serialization", since the latter **requires** that the "alg" headers be carried in the **protected** headers, which was never done before migrating to authlib (One shouldn't be using schema1 anyways) References: - https://tools.ietf.org/html/rfc7515#section-10.7 - https://docs.docker.com/registry/spec/manifest-v2-1/#signed-manifests """ payload = OrderedDict(self._base_payload) payload.update({ DOCKER_SCHEMA1_HISTORY_KEY: self._history, DOCKER_SCHEMA1_FS_LAYERS_KEY: self._fs_layer_digests, }) payload_str = json.dumps(payload, indent=3, ensure_ascii=ensure_ascii) if json_web_key is None: return DockerSchema1Manifest( Bytes.for_string_or_unicode(payload_str)) payload_str = Bytes.for_string_or_unicode(payload_str).as_encoded_str() split_point = payload_str.rfind(b"\n}") protected_payload = { DOCKER_SCHEMA1_FORMAT_TAIL_KEY: base64url_encode(payload_str[split_point:]).decode("ascii"), DOCKER_SCHEMA1_FORMAT_LENGTH_KEY: split_point, "time": datetime.utcnow().strftime(_ISO_DATETIME_FORMAT_ZULU), } # Flattened JSON serialization header jws = JsonWebSignature(algorithms=[_JWS_SIGNING_ALGORITHM]) headers = { "protected": protected_payload, "header": { "alg": _JWS_SIGNING_ALGORITHM }, } signed = jws.serialize_json(headers, payload_str, json_web_key.get_private_key()) protected = signed["protected"] signature = signed["signature"] logger.debug("Generated signature: %s", signature) logger.debug("Generated protected block: %s", protected) public_members = set(json_web_key.REQUIRED_JSON_FIELDS + json_web_key.ALLOWED_PARAMS) public_key = { comp: value for comp, value in list(json_web_key.as_dict().items()) if comp in public_members } public_key["kty"] = json_web_key.kty signature_block = { DOCKER_SCHEMA1_HEADER_KEY: { "jwk": public_key, "alg": _JWS_SIGNING_ALGORITHM }, DOCKER_SCHEMA1_SIGNATURE_KEY: signature, DOCKER_SCHEMA1_PROTECTED_KEY: protected, } logger.debug("Encoded signature block: %s", json.dumps(signature_block)) payload.update({DOCKER_SCHEMA1_SIGNATURES_KEY: [signature_block]}) json_str = json.dumps(payload, indent=3, ensure_ascii=ensure_ascii) return DockerSchema1Manifest(Bytes.for_string_or_unicode(json_str))
def decode(self, token): token_parts = self._unpack_token(token) header, payload, raw_signature, signing_input = token_parts token = {"header": header, "payload": payload, "signature": base64url_encode(raw_signature)} return token
def generate_token(self) -> bytes: return base64url_encode(bytes(signer.sign(self.id), "utf-8"))
def _create_signing_input(payload, header): return b'.'.join([ base64url_encode(json_encode(header)), base64url_encode(json_encode(payload)) ])