40 lines
1.7 KiB
Python
40 lines
1.7 KiB
Python
"""Canonical, domain-separated machine receipts, distinct from Owner grants."""
|
|
import base64
|
|
import hashlib
|
|
import json
|
|
from cryptography.exceptions import InvalidSignature
|
|
|
|
|
|
def canonical(value):
|
|
return json.dumps(value,sort_keys=True,separators=(',',':'),ensure_ascii=False,allow_nan=False).encode()
|
|
|
|
|
|
def digest(value):
|
|
return hashlib.sha256(canonical(value)).hexdigest()
|
|
|
|
|
|
class ReceiptSigner:
|
|
def __init__(self, private_key, *, key_id='baseline-machine', trusted_public_keys=None):
|
|
self.private_key=private_key
|
|
self.public_key=private_key.public_key()
|
|
self.key_id=key_id
|
|
self.trusted_public_keys=dict(trusted_public_keys or {})
|
|
self.trusted_public_keys[key_id]=self.public_key
|
|
|
|
def sign(self,payload):
|
|
raw=canonical({'domain':'aios.operation-receipt.v1','key_id':self.key_id,'payload':payload})
|
|
return {'schema':'aios.operation-receipt.v1','key_id':self.key_id,'payload':payload,
|
|
'signature':base64.b64encode(self.private_key.sign(raw)).decode()}
|
|
|
|
def verify(self,envelope):
|
|
try:
|
|
if type(envelope) is not dict or set(envelope)!={'schema','key_id','payload','signature'} or envelope['schema']!='aios.operation-receipt.v1' or type(envelope['payload']) is not dict:
|
|
return False
|
|
public=self.trusted_public_keys.get(envelope['key_id'])
|
|
if public is None: return False
|
|
raw=canonical({'domain':envelope['schema'],'key_id':envelope['key_id'],'payload':envelope['payload']})
|
|
public.verify(base64.b64decode(envelope['signature'],validate=True),raw)
|
|
return True
|
|
except (ValueError,TypeError,InvalidSignature,KeyError):
|
|
return False
|