Example #1
0
File: schema1.py Project: syed/quay
    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))
Example #2
0
    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))