Skip to content

Commit 894a055

Browse files
authored
Merge pull request #30 from MarkLodato/ref-impl
Reimplement and fix reference implementation in Python.
2 parents a87474d + 13078ef commit 894a055

File tree

4 files changed

+175
-1
lines changed

4 files changed

+175
-1
lines changed

implementation/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Signing-spec reference implementation
2+
3+
The Python module `signing_spec.py` contains a reference implementation. A test
4+
vector is contained as a docstring at the top of the file.

implementation/ecdsa.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Example crypto implementation: ECDSA with deterministic-rfc6979 and SHA256.
2+
3+
Copyright 2021 Google LLC.
4+
SPDX-License-Identifier: Apache-2.0
5+
"""
6+
7+
from Crypto.Hash import SHA256
8+
from Crypto.PublicKey import ECC
9+
from Crypto.Signature import DSS
10+
11+
12+
class Signer:
13+
def __init__(self, secret_key):
14+
self.secret_key = secret_key
15+
self.public_key = self.secret_key.public_key()
16+
17+
@classmethod
18+
def construct(cls, *, curve, d, point_x, point_y):
19+
return cls(
20+
ECC.construct(curve=curve, d=d, point_x=point_x, point_y=point_y))
21+
22+
@classmethod
23+
def generate(cls, *, curve, randfunc=None):
24+
return cls(ECC.generate(curve=curve, randfunc=randfunc))
25+
26+
def sign(self, message: bytes) -> bytes:
27+
"""Returns the signature of `message`."""
28+
h = SHA256.new(message)
29+
return DSS.new(self.secret_key, 'deterministic-rfc6979').sign(h)
30+
31+
32+
class Verifier:
33+
def __init__(self, public_key):
34+
self.public_key = public_key
35+
36+
def verify(self, message: bytes, signature: bytes) -> bool:
37+
"""Returns true if `message` was signed by `signature`."""
38+
h = SHA256.new(message)
39+
try:
40+
DSS.new(self.public_key, 'fips-186-3').verify(h, signature)
41+
return True
42+
except ValueError:
43+
return False

implementation/signing_spec.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
r"""Reference implementation of signing-spec.
2+
3+
Copyright 2021 Google LLC.
4+
SPDX-License-Identifier: Apache-2.0
5+
6+
The following example requires `pip3 install pycryptodome` and uses ecdsa.py in
7+
the same directory as this file.
8+
9+
>>> import binascii, os, sys, textwrap
10+
>>> from pprint import pprint
11+
>>> sys.path.insert(0, os.path.dirname(__file__))
12+
>>> import ecdsa
13+
14+
>>> signer = ecdsa.Signer.construct(
15+
... curve='P-256',
16+
... d=97358161215184420915383655311931858321456579547487070936769975997791359926199,
17+
... point_x=46950820868899156662930047687818585632848591499744589407958293238635476079160,
18+
... point_y=5640078356564379163099075877009565129882514886557779369047442380624545832820)
19+
>>> verifier = ecdsa.Verifier(signer.public_key)
20+
>>> payloadType = 'http://example.com/HelloWorld'
21+
>>> payload = b'hello world'
22+
23+
Signing example:
24+
25+
>>> signature_json = Sign(payloadType, payload, signer)
26+
>>> pprint(json.loads(signature_json))
27+
{'payload': 'aGVsbG8gd29ybGQ=',
28+
'payloadType': 'http://example.com/HelloWorld',
29+
'signatures': [{'sig': 'Cc3RkvYsLhlaFVd+d6FPx4ZClhqW4ZT0rnCYAfv6/ckoGdwT7g/blWNpOBuL/tZhRiVFaglOGTU8GEjm4aEaNA=='}]}
30+
31+
Verification example:
32+
33+
>>> result = Verify(signature_json, [('mykey', verifier)])
34+
>>> pprint(result)
35+
VerifiedPayload(payloadType='http://example.com/HelloWorld', payload=b'hello world', recognizedSigners=['mykey'])
36+
37+
PAE:
38+
39+
>>> def print_hex(b: bytes):
40+
... octets = ' '.join(textwrap.wrap(binascii.hexlify(b).decode('utf-8'), 2))
41+
... print(*textwrap.wrap(octets, 48), sep='\n')
42+
>>> print_hex(PAE(payloadType, payload))
43+
02 00 00 00 00 00 00 00 1d 00 00 00 00 00 00 00
44+
68 74 74 70 3a 2f 2f 65 78 61 6d 70 6c 65 2e 63
45+
6f 6d 2f 48 65 6c 6c 6f 57 6f 72 6c 64 0b 00 00
46+
00 00 00 00 00 68 65 6c 6c 6f 20 77 6f 72 6c 64
47+
"""
48+
49+
import base64, binascii, dataclasses, json, struct
50+
51+
# Protocol requires Python 3.8+.
52+
from typing import Iterable, List, Protocol, Tuple
53+
54+
55+
class Signer(Protocol):
56+
def sign(self, message: bytes) -> bytes:
57+
"""Returns the signature of `message`."""
58+
...
59+
60+
61+
class Verifier(Protocol):
62+
def verify(self, message: bytes, signature: bytes) -> bool:
63+
"""Returns true if `message` was signed by `signature`."""
64+
...
65+
66+
67+
# Collection of verifiers, each of which is associated with a name.
68+
VerifierList = Iterable[Tuple[str, Verifier]]
69+
70+
71+
@dataclasses.dataclass
72+
class VerifiedPayload:
73+
payloadType: str
74+
payload: bytes
75+
recognizedSigners: List[str] # List of names of signers
76+
77+
78+
def b64enc(m: bytes) -> str:
79+
return base64.standard_b64encode(m).decode('utf-8')
80+
81+
82+
def b64dec(m: str) -> bytes:
83+
m = m.encode('utf-8')
84+
try:
85+
return base64.b64decode(m, validate=True)
86+
except binascii.Error:
87+
return base64.b64decode(m, altchars='-_', validate=True)
88+
89+
90+
def PAE(payloadType: str, payload: bytes) -> bytes:
91+
return b''.join([
92+
struct.pack('<Q', 2),
93+
struct.pack('<Q', len(payloadType)),
94+
payloadType.encode('utf-8'),
95+
struct.pack('<Q', len(payload)), payload
96+
])
97+
98+
99+
def Sign(payloadType: str, payload: bytes, signer: Signer) -> str:
100+
return json.dumps({
101+
'payload':
102+
b64enc(payload),
103+
'payloadType':
104+
payloadType,
105+
'signatures': [{
106+
'sig': b64enc(signer.sign(PAE(payloadType, payload)))
107+
}],
108+
})
109+
110+
111+
def Verify(json_signature: str, verifiers: VerifierList) -> VerifiedPayload:
112+
wrapper = json.loads(json_signature)
113+
payloadType = wrapper['payloadType']
114+
payload = b64dec(wrapper['payload'])
115+
pae = PAE(payloadType, payload)
116+
recognizedSigners = []
117+
for signature in wrapper['signatures']:
118+
for name, verifier in verifiers:
119+
if verifier.verify(pae, b64dec(signature['sig'])):
120+
recognizedSigners.append(name)
121+
if not recognizedSigners:
122+
raise ValueError('No valid signature found')
123+
return VerifiedPayload(payloadType, payload, recognizedSigners)
124+
125+
126+
if __name__ == '__main__':
127+
import doctest
128+
doctest.testmod()

reference_implementation.ipynb

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)