|
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