Dead Simple Signing Envelopes (DSSE)[ECDSA Home][Home]
The Metablock format in in-toto and TUF need canonicalisation, and but DSSE has no need for this and can thus apply itself to byte streams [here]. It also simplifies the formating of the signaturem and supports an envelope supports multiple signature for the same signed data. DSSE also supports a PAE (Pre-Auth-Encoding) and which provides for a safe way to serialization and deserialization data that is untrusted.
|
TUF/In-Toto Metablocks
The Update Framework (TUF) provides a framework to secure the software within a system, while in-toto ("as-a-whole") can be used to protect the whole of software support chain. TUF thus delivers updates to a system while in-toto manages a complete software infrastructure. For this, it uses the Metablock format, and where we do not sign the byte values for the signature but sign the JSON data. This is known as canonicalisation - and which is the conversion of data in different forms to a standardised format.
With this we get details of the algorithm use, the key ID, the public key and the signature. In the following, we see that we are using two rsassa-pss-sha256 keys for signing (5f7879 … 9561de62c and 8b50265 … 8c1082abbac9e7e959ec):
{ "signatures": [ { "keyid": "6e18dfbf96b117b7a36196cfd782ecfeb2d08369fb566539f90ad9ac40f152dd", "sig": "052b3a75b7d2693ba8c0cab42cebfd6fe9547589426bebf8bac863d3e2e454b3c88b1c1ab2586cf16b4f2070e16027e6b5299f6e15ba64bd12000709202e483899766b0d5ce23f0ddcecf58a4456ea09918bb24c0df33f4b3477e7c1454c6b28efb0ed374d1b150fbbca23cd1a296ae5cd74ec55da0d1b58ccc6c6b9ebbeee41860438910417ded40994672690553c1bc354bce5a92bc164188976857bdce56789f9558aee646730d22f511a20efa210e5cd7e87612f5f7738a7a88c391c67cefc29f345b784b877414e026149649d12fbec5c6d6c3d2c7f4eb3edee0dd356a52d2cddb176dfef62413b92f13b719a0bb6ba4fc7122313e5bae50272bc27f3a5bbd75abfee95289168ddf3b3bd061d3f805dde21386a9e72c2beb02500ee1f93724c3e35cca7f8c23223f2f293a1d6f37b2eaf9fb1ae3913c0d0039b74e4b18ff005c4ec18b929b3c0efe0d1a23bdf81237c2c5fb6e3fc5777ea77f44da7cdfbad913a7754549a0ddd9579ea0c2b9cc81bebaacb7be50ea5d35b65b0fecf63cd" } ], "signed": { "_type": "layout", "expires": "2023-12-03T10:05:34Z", "inspect": [ { "_type": "inspection", "expected_materials": [ [ "MATCH", "demo-project.tar.gz", "WITH", "PRODUCTS", "FROM", "package" ] ], "expected_products": [ [ "MATCH", "demo-project/foo.py", "WITH", "PRODUCTS", "FROM", "update-version" ] ], "name": "untar", "run": [ "tar", "xzf", "demo-project.tar.gz" ] } ], "keys": { "5f78794eaa621199f21364cd08ca49090930373e8e06645dbb719869561de62c": { "keyid": "5f78794eaa621199f21364cd08ca49090930373e8e06645dbb719869561de62c", "keyid_hash_algorithms": [ "sha256", "sha512" ], "keytype": "rsa", "keyval": { "private": "", "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA3hxpbQ/Ns7uYIxYRE1Sn\nRkdzzVpb5QWPJ25QrObXYlZNG/kaIEvUQYhyXxbRu0c/RtVAVuOeTlij1U8q1KZa\novgwXb8G2BXZwIJISjzwu8f0QmNBm+lY4GllWhZz6eKo6bQk/43YYP5cpinVDEZY\nxwmdqO6sFpsdxeo3A6eK0erV0Fiy/wz609X1qZr3fyWQyKrq9oeG/WuEndHS7Uuv\nEYt2CYZ5RhjTXG5Gw49hZV4P4RnCn0hKXFEWj0qeUBE4FB6RcfeyC5bnV9GLSHz4\n3ZwGymzp2FhRLIeVfGPd0rgzw1SkFgRPeH/CD+hycyuY+75gZhPxnsrMNANP175z\n0FXdvfFqSLx7sYOU/VLjmvXJbsmeIRFP3v4PurWSU6T3DeSH3aMvMgDFOCrb3uWx\njnKb2Les1YmkoNKUCJYbk1bXoE31fBTW2BLylWCwJ8355kOr/PEHh7gMJUa0EGw5\noUUDgtqUKqjhjSS/aLq2PTN9A1yHy5fHKUFyTSrQjsqFAgMBAAE=\n-----END PUBLIC KEY-----" }, "scheme": "rsassa-pss-sha256" }, "8b50265fd2ba917d5d444ef0bac3fa96a6cfae940d458c1082abbac9e7e959ec": { "keyid": "8b50265fd2ba917d5d444ef0bac3fa96a6cfae940d458c1082abbac9e7e959ec", "keyid_hash_algorithms": [ "sha256", "sha512" ], "keytype": "rsa", "keyval": { "private": "", "public": "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAvfGOZnz7ADHqQ348KLqd\nYd1M0/ZM/BjAvKHx0ql86i2gkULYJSfPK9jjuaTVzQ9ITQZVlbHS9ThEkIQO7/FI\nop1NG8224QVSjf8vD32dH4SyPHrRpkdUgu9EE0DqLiaQXduhQn31BvwMFlIASQvS\nDHKVTk+VO8T1fRmrAQx/3AFAK43ForYCT19b48TwAgL3K3f4C6YesJNmfSDoPYo7\nZjrQ+DdcC0cxGS3AV2NMIqWc6gKoYT6fAd0MTe5g1qrR10lnNC7+dYFuxt9SkoG0\nT4+Gbt5jpczgM/0xOjGQyVXjOJxpX35xzD9a75kocRYQx6gQFgX0kV5HopoaEKgC\n/rYVBZsdj/Wm6qn7vhETNLAveVB2EtSH+P905anrzUDQ6/GlXrPI+tZ9MandklaU\newA7MEaIAWuuTr9DU7igRaIa8MsPT8C5t2MlBa999qsJ8irrWFa3svhrxKb2CDl4\n9F4RD2LE4QElcR+nMkIQb4wXpzsQJoga6d1We8MiIIbNAgMBAAE=\n-----END PUBLIC KEY-----" }, "scheme": "rsassa-pss-sha256" } }, "readme": "", "steps": [ { "_type": "step", "expected_command": [ "git", "clone", "https://github.com/in-toto/demo-project.git" ], "expected_materials": [], "expected_products": [ [ "CREATE", "demo-project/foo.py" ], [ "DISALLOW", "*" ] ], "name": "clone", "pubkeys": [ "5f78794eaa621199f21364cd08ca49090930373e8e06645dbb719869561de62c" ], "threshold": 1 }, { "_type": "step", "expected_command": [], "expected_materials": [ [ "MATCH", "demo-project/*", "WITH", "PRODUCTS", "FROM", "clone" ], [ "DISALLOW", "*" ] ], "expected_products": [ [ "ALLOW", "demo-project/foo.py" ], [ "DISALLOW", "*" ] ], "name": "update-version", "pubkeys": [ "5f78794eaa621199f21364cd08ca49090930373e8e06645dbb719869561de62c" ], "threshold": 1 }, { "_type": "step", "expected_command": [ "tar", "--exclude", ".git", "-zcvf", "demo-project.tar.gz", "demo-project" ], "expected_materials": [ [ "MATCH", "demo-project/*", "WITH", "PRODUCTS", "FROM", "update-version" ], [ "DISALLOW", "*" ] ], "expected_products": [ [ "CREATE", "demo-project.tar.gz" ], [ "DISALLOW", "*" ] ], "name": "package", "pubkeys": [ "8b50265fd2ba917d5d444ef0bac3fa96a6cfae940d458c1082abbac9e7e959ec" ], "threshold": 1 } ] } }
DSSE
The Metablock format in in-toto and TUF need canonicalisation, and but DSSE has no need for this and can thus apply itself to byte streams [here]. It also simplifies the formating of the signaturem and supports an envelope supports multiple signature for the same signed data. DSSE also supports a PAE (Pre-Auth-Encoding) and which provides for a safe way to serialization and deserialization data that is untrusted.
In the following example of DSSE, we have a message of "Hello", and which is "SGVsbG8=" in Base64 encoding. The payload type will define the structure of the payload. In this case it is defined by "http://example.com/testing":
Message: Hello 123 Type: http://example.com/testing Private key: EccKey(curve='NIST P-256', point_x=33796162813166805588710121804061061246441408202115825092749411076575922636412, point_y=445423445026610045218754189758102393455802184758516107499727218923652216278, d=115109344795764689841124184802822027938123581634343585745872628005620037228718) Public key: EccKey(curve='NIST P-256', point_x=33796162813166805588710121804061061246441408202115825092749411076575922636412, point_y=445423445026610045218754189758102393455802184758516107499727218923652216278) Key ID: 2a7141d6 == Signing == {'payload': 'SGVsbG8gMTIz', 'payloadType': 'http://example.com/testing', 'signatures': [{'keyid': '2a7141d6', 'sig': 'i16opxQrAEnXJ7x9kTWYd2LtZwEoRQqhYEvxoexvj2/tKXX6DZE8cbQ3eEGb8ZtuWcMk2LjDLyhpQ2BSGcwQnA=='}]} == Verification == VerifiedPayload(payloadType='http://example.com/testing', payload=b'Hello 123', recognizedSigners=['mykey']) DSSEv1 b'DSSEv1 26 http://example.com/testing 9 Hello 123'
We can see that the envlope used in the signature is:
{'payload': 'SGVsbG8gMTIz', 'payloadType': 'http://example.com/testing', 'signatures': [{'keyid': '2a7141d6', 'sig': 'i16opxQrAEnXJ7x9kTWYd2LtZwEoRQqhYEvxoexvj2/tKXX6DZE8cbQ3eEGb8ZtuWcMk2LjDLyhpQ2BSGcwQnA=='}]}
For this "Hello 123" is Base64 encoding is "SGVsbG8gMTIz". We then have a signature of "i16opxQrAE … wQnA==" and which has been signed with the key ID of "2a7141d6". The key that we have generated as an ID of "2a7141d6", and where we have a private key of d, and a public key of (point_x, point_y):
Private key: EccKey(curve='NIST P-256', point_x=33796162813166805588710121804061061246441408202115825092749411076575922636412, point_y=445423445026610045218754189758102393455802184758516107499727218923652216278, d=115109344795764689841124184802822027938123581634343585745872628005620037228718) Public key: EccKey(curve='NIST P-256', point_x=33796162813166805588710121804061061246441408202115825092749411076575922636412, point_y=445423445026610045218754189758102393455802184758516107499727218923652216278) Key ID: 2a7141d6
The PAE format in this case is:
DSSEv1 b'DSSEv1 26 http://example.com/testing 9 Hello 123'
and where we read 26 characters for the payload type, and then 9 characters for the message. The code for this is taken from :
import base64, binascii, dataclasses, json, struct import os, sys from pprint import pprint import ecdsa_new # Protocol requires Python 3.8+. from typing import Iterable, List, Optional, Protocol, Tuple class Signer(Protocol): def sign(self, message: bytes) -> bytes: """Returns the signature of `message`.""" ... def keyid(self) -> Optional[str]: """Returns the ID of this key, or None if not supported.""" ... class Verifier(Protocol): def verify(self, message: bytes, signature: bytes) -> bool: """Returns true if `message` was signed by `signature`.""" ... def keyid(self) -> Optional[str]: """Returns the ID of this key, or None if not supported.""" ... # Collection of verifiers, each of which is associated with a name. VerifierList = Iterable[Tuple[str, Verifier]] @dataclasses.dataclass class VerifiedPayload: payloadType: str payload: bytes recognizedSigners: List[str] # List of names of signers def b64enc(m: bytes) -> str: return base64.standard_b64encode(m).decode('utf-8') def b64dec(m: str) -> bytes: m = m.encode('utf-8') try: return base64.b64decode(m, validate=True) except binascii.Error: return base64.b64decode(m, altchars='-_', validate=True) def PAE(payloadType: str, payload: bytes) -> bytes: return b'DSSEv1 %d %b %d %b' % ( len(payloadType), payloadType.encode('utf-8'), len(payload), payload) def Sign(payloadType: str, payload: bytes, signer: Signer) -> str: signature = { 'keyid': signer.keyid(), 'sig': b64enc(signer.sign(PAE(payloadType, payload))), } if not signature['keyid']: del signature['keyid'] return json.dumps({ 'payload': b64enc(payload), 'payloadType': payloadType, 'signatures': [signature], }) def Verify(json_signature: str, verifiers: VerifierList) -> VerifiedPayload: wrapper = json.loads(json_signature) payloadType = wrapper['payloadType'] payload = b64dec(wrapper['payload']) pae = PAE(payloadType, payload) recognizedSigners = [] for signature in wrapper['signatures']: for name, verifier in verifiers: if (signature.get('keyid') is not None and verifier.keyid() is not None and signature.get('keyid') != verifier.keyid()): continue if verifier.verify(pae, b64dec(signature['sig'])): recognizedSigners.append(name) if not recognizedSigners: raise ValueError('No valid signature found') return VerifiedPayload(payloadType, payload, recognizedSigners) payload = b'hello world' payloadType = 'http://example.com/HelloWorld' if (len(sys.argv)>1): payload=str(sys.argv[1]).encode() if (len(sys.argv)>2): payloadType=str(sys.argv[2]) print(f"Message: {payload.decode()}") print(f"Type: {payloadType}") signer = ecdsa_new.Signer.generate(curve='P-256') verifier = ecdsa_new.Verifier(signer.public_key) print(f"\nPrivate key: {signer.secret_key}") print(f"Public key: {signer.public_key}") print(f"Key ID: {signer.keyid()}") print("\n\n== Signing ==") signature_json = Sign(payloadType, payload, signer) pprint(json.loads(signature_json)) print("\n\n== Verification ==") result = Verify(signature_json, [('mykey', verifier)]) pprint(result) print("\n\nDSSEv1") pprint(PAE(payloadType, payload))