forked from secure-systems-lab/dsse
-
Notifications
You must be signed in to change notification settings - Fork 0
/
signing_spec.py
134 lines (100 loc) · 4.07 KB
/
signing_spec.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
r"""Reference implementation of signing-spec.
Copyright 2021 Google LLC.
SPDX-License-Identifier: Apache-2.0
The following example requires `pip3 install pycryptodome` and uses ecdsa.py in
the same directory as this file.
>>> import os, sys
>>> from pprint import pprint
>>> sys.path.insert(0, os.path.dirname(__file__))
>>> import ecdsa
>>> signer = ecdsa.Signer.construct(
... curve='P-256',
... d=97358161215184420915383655311931858321456579547487070936769975997791359926199,
... point_x=46950820868899156662930047687818585632848591499744589407958293238635476079160,
... point_y=5640078356564379163099075877009565129882514886557779369047442380624545832820)
>>> verifier = ecdsa.Verifier(signer.public_key)
>>> payloadType = 'http://example.com/HelloWorld'
>>> payload = b'hello world'
Signing example:
>>> signature_json = Sign(payloadType, payload, signer)
>>> pprint(json.loads(signature_json))
{'payload': 'aGVsbG8gd29ybGQ=',
'payloadType': 'http://example.com/HelloWorld',
'signatures': [{'keyid': '66301bbf',
'sig': 'A3JqsQGtVsJ2O2xqrI5IcnXip5GToJ3F+FnZ+O88SjtR6rDAajabZKciJTfUiHqJPcIAriEGAHTVeCUjW2JIZA=='}]}
Verification example:
>>> result = Verify(signature_json, [('mykey', verifier)])
>>> pprint(result)
VerifiedPayload(payloadType='http://example.com/HelloWorld', payload=b'hello world', recognizedSigners=['mykey'])
PAE:
>>> PAE(payloadType, payload)
b'DSSEv1 29 http://example.com/HelloWorld 11 hello world'
"""
import base64, binascii, dataclasses, json, struct
# 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)
if __name__ == '__main__':
import doctest
doctest.testmod()