From a588141e06d5322a3bc1a14a7e0bf7f495382338 Mon Sep 17 00:00:00 2001 From: Lubos Mjachky Date: Tue, 13 Feb 2024 21:34:55 +0100 Subject: [PATCH] Remove the optional "kid" parameter This removes the key identifier stored in the JWK's header in the manifest v2 schema 1 signatures' payload. The "kid" parameter is optional in JWK. This parameter might be also considered irrelevant because the key pair for ECDSA is generated each time the conversion from schema 2 to schema 1 happens. So, clients cannot verify the origin of the signature with the fingerprint/kid because the public key is created on the fly and then immediately trashed. Ref: https://www.rfc-editor.org/rfc/rfc7517#section-4.5 Ref: https://docker-docs.uclv.cu/registry/spec/manifest-v2-1/ closes #1485 (cherry picked from commit 59e06e591bd3e621401d83f417fd3fa2ecadbf0a) --- CHANGES/1485.removal | 3 ++ pulp_container/app/schema_convert.py | 52 ---------------------------- requirements.txt | 1 - 3 files changed, 3 insertions(+), 53 deletions(-) create mode 100644 CHANGES/1485.removal diff --git a/CHANGES/1485.removal b/CHANGES/1485.removal new file mode 100644 index 000000000..d0ee06c73 --- /dev/null +++ b/CHANGES/1485.removal @@ -0,0 +1,3 @@ +Removed the optional "kid" parameter stored inside the signatures' payload generated during +docker manifest v2 schema 1 conversion. This change also removes the ``ecdsa`` dependency, +which is vulnerable to Minevra timing attacks. diff --git a/pulp_container/app/schema_convert.py b/pulp_container/app/schema_convert.py index 581f5e1c2..d34a7beb2 100644 --- a/pulp_container/app/schema_convert.py +++ b/pulp_container/app/schema_convert.py @@ -1,9 +1,5 @@ -import base64 -import binascii import datetime -import ecdsa import hashlib -import itertools import json import logging @@ -129,7 +125,6 @@ def convert(self): ) key = jwk.ECKey().load_key(ecc.P256) - key.kid = getKeyId(key) manifest_data = _jsonDumps(manifest) signed_manifest_data = sign(manifest_data, key) return manifest_data, signed_manifest_data @@ -250,53 +245,6 @@ def sign(data, key): return data_with_signature -def getKeyId(key): - """ - DER-encode the key and represent it in the format XXXX:YYYY:... - """ - derRepr = toDer(key) - shaRepr = hashlib.sha256(derRepr).digest()[:30] - b32Repr = base64.b32encode(shaRepr).decode() - return ":".join(byN(b32Repr, 4)) - - -def toDer(key): - """Return the DER-encoded representation of the key""" - point = ( - b"\x00\x04" + number2string(key.x, key.curve.bytes) + number2string(key.y, key.curve.bytes) - ) - der = ecdsa.der - curveEncodedOid = der.encode_oid(1, 2, 840, 10045, 3, 1, 7) - return der.encode_sequence( - der.encode_sequence(ecdsa.keys.encoded_oid_ecPublicKey, curveEncodedOid), - der.encode_bitstring(point), - ) - - -def byN(strobj, N): - """ - Yield consecutive substrings of length N from string strobj - """ - it = iter(strobj) - while True: - substr = "".join(itertools.islice(it, N)) - if not substr: - return - yield substr - - -def number2string(num, order): - """ - Hex-encode the number and return a zero-padded (to the left) to a total - length of 2*order - """ - # convert to hex - nhex = "%x" % num - # Zero-pad to the left so the length of the resulting unhexified string is order - nhex = nhex.rjust(2 * order, "0") - return binascii.unhexlify(nhex) - - def compute_digest(manifest_data): """ Compute the digest from the passed manifest data. diff --git a/requirements.txt b/requirements.txt index a6299d942..df3609c04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -ecdsa>=0.14,<=0.18.0 jsonschema>=4.4,<4.18 pulpcore>=3.25.0,<3.40 pyjwkest>=1.4,<=1.4.2