|
2 | 2 | import cbor2
|
3 | 3 | import logging
|
4 | 4 |
|
5 |
| -from pycose.keys import CoseKey |
| 5 | +from pycose.keys import CoseKey, EC2Key |
6 | 6 | from typing import Union
|
7 | 7 |
|
8 | 8 | from pymdoccbor.mso.issuer import MsoIssuer
|
| 9 | +from pymdoccbor.mdoc.exceptions import MissingPrivateKey |
9 | 10 |
|
10 | 11 | logger = logging.getLogger('pymdoccbor')
|
11 | 12 |
|
12 | 13 |
|
13 | 14 | class MdocCborIssuer:
|
| 15 | + """ |
| 16 | + MdocCborIssuer helper class to create a new mdoc |
| 17 | + """ |
14 | 18 |
|
15 |
| - def __init__(self, private_key: Union[dict, CoseKey] = {}): |
| 19 | + def __init__(self, private_key: Union[dict, EC2Key, CoseKey]): |
| 20 | + """ |
| 21 | + Create a new MdocCborIssuer instance |
| 22 | +
|
| 23 | + :param private_key: the private key to sign the mdoc |
| 24 | + :type private_key: dict | CoseKey |
| 25 | +
|
| 26 | + :raises MissingPrivateKey: if no private key is provided |
| 27 | + """ |
16 | 28 | self.version: str = '1.0'
|
17 | 29 | self.status: int = 0
|
18 |
| - if private_key and isinstance(private_key, dict): |
| 30 | + |
| 31 | + if isinstance(private_key, dict): |
19 | 32 | self.private_key = CoseKey.from_dict(private_key)
|
| 33 | + elif isinstance(private_key, EC2Key): |
| 34 | + ec2_encoded = private_key.encode() |
| 35 | + ec2_decoded = CoseKey.decode(ec2_encoded) |
| 36 | + self.private_key = ec2_decoded |
| 37 | + elif isinstance(private_key, CoseKey): |
| 38 | + self.private_key = private_key |
| 39 | + else: |
| 40 | + raise MissingPrivateKey("You must provide a private key") |
| 41 | + |
20 | 42 |
|
21 | 43 | self.signed :dict = {}
|
22 | 44 |
|
23 | 45 | def new(
|
24 | 46 | self,
|
25 |
| - data: dict, |
| 47 | + data: dict | list[dict], |
26 | 48 | devicekeyinfo: Union[dict, CoseKey],
|
27 |
| - doctype: str |
28 |
| - ): |
| 49 | + doctype: str | None = None |
| 50 | + ) -> dict: |
29 | 51 | """
|
30 | 52 | create a new mdoc with signed mso
|
| 53 | +
|
| 54 | + :param data: the data to sign |
| 55 | + Can be a dict, representing the single document, or a list of dicts containg the doctype and the data |
| 56 | + Example: |
| 57 | + {doctype: "org.iso.18013.5.1.mDL", data: {...}} |
| 58 | + :type data: dict | list[dict] |
| 59 | + :param devicekeyinfo: the device key info |
| 60 | + :type devicekeyinfo: dict | CoseKey |
| 61 | + :param doctype: the document type (optional if data is a list) |
| 62 | + :type doctype: str | None |
| 63 | +
|
| 64 | + :return: the signed mdoc |
| 65 | + :rtype: dict |
31 | 66 | """
|
32 | 67 | if isinstance(devicekeyinfo, dict):
|
33 | 68 | devicekeyinfo = CoseKey.from_dict(devicekeyinfo)
|
34 | 69 | else:
|
35 | 70 | devicekeyinfo: CoseKey = devicekeyinfo
|
36 | 71 |
|
37 |
| - msoi = MsoIssuer( |
38 |
| - data=data, |
39 |
| - private_key=self.private_key |
40 |
| - ) |
| 72 | + if isinstance(data, dict): |
| 73 | + data = [{"doctype": doctype, "data": data}] |
41 | 74 |
|
42 |
| - mso = msoi.sign() |
| 75 | + documents = [] |
| 76 | + |
| 77 | + for doc in data: |
| 78 | + msoi = MsoIssuer( |
| 79 | + data=doc["data"], |
| 80 | + private_key=self.private_key |
| 81 | + ) |
| 82 | + |
| 83 | + mso = msoi.sign() |
| 84 | + |
| 85 | + document = { |
| 86 | + 'docType': doc["doctype"], # 'org.iso.18013.5.1.mDL' |
| 87 | + 'issuerSigned': { |
| 88 | + "nameSpaces": { |
| 89 | + ns: [ |
| 90 | + cbor2.CBORTag(24, value={k: v}) for k, v in dgst.items() |
| 91 | + ] |
| 92 | + for ns, dgst in msoi.disclosure_map.items() |
| 93 | + }, |
| 94 | + "issuerAuth": mso.encode() |
| 95 | + }, |
| 96 | + # this is required during the presentation. |
| 97 | + # 'deviceSigned': { |
| 98 | + # # TODO |
| 99 | + # } |
| 100 | + } |
| 101 | + |
| 102 | + documents.append(document) |
43 | 103 |
|
44 |
| - # TODO: for now just a single document, it would be trivial having |
45 |
| - # also multiple but for now I don't have use cases for this |
46 | 104 | self.signed = {
|
47 | 105 | 'version': self.version,
|
48 |
| - 'documents': [ |
49 |
| - { |
50 |
| - 'docType': doctype, # 'org.iso.18013.5.1.mDL' |
51 |
| - 'issuerSigned': { |
52 |
| - "nameSpaces": { |
53 |
| - ns: [ |
54 |
| - cbor2.CBORTag(24, value={k: v}) for k, v in dgst.items() |
55 |
| - ] |
56 |
| - for ns, dgst in msoi.disclosure_map.items() |
57 |
| - }, |
58 |
| - "issuerAuth": mso.encode() |
59 |
| - }, |
60 |
| - # this is required during the presentation. |
61 |
| - # 'deviceSigned': { |
62 |
| - # # TODO |
63 |
| - # } |
64 |
| - } |
65 |
| - ], |
| 106 | + 'documents': documents, |
66 | 107 | 'status': self.status
|
67 | 108 | }
|
68 | 109 | return self.signed
|
69 | 110 |
|
70 | 111 | def dump(self):
|
71 | 112 | """
|
72 |
| - returns bytes |
| 113 | + Returns the signed mdoc in CBOR format |
| 114 | +
|
| 115 | + :return: the signed mdoc in CBOR format |
| 116 | + :rtype: bytes |
73 | 117 | """
|
74 | 118 | return cbor2.dumps(self.signed)
|
75 | 119 |
|
76 | 120 | def dumps(self):
|
77 | 121 | """
|
78 |
| - returns AF binary repr |
| 122 | + Returns the signed mdoc in AF binary repr |
| 123 | +
|
| 124 | + :return: the signed mdoc in AF binary repr |
| 125 | + :rtype: bytes |
79 | 126 | """
|
80 | 127 | return binascii.hexlify(cbor2.dumps(self.signed))
|
0 commit comments