Skip to content

Commit 39cda8c

Browse files
authored
Merge pull request #444 from kentakayama/add-detached-content-converter
Add detached content converter
2 parents 3139d1b + 753d090 commit 39cda8c

2 files changed

Lines changed: 98 additions & 4 deletions

File tree

cwt/cose_message.py

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from typing import Any, Dict, List, Optional, TypeVar
1+
from __future__ import annotations
2+
3+
from typing import Any, Dict, List, Optional, Tuple, TypeVar
24

35
from cbor2 import CBORTag, loads
46

@@ -98,6 +100,20 @@ def __init__(self, type: COSETypes, msg: List[Any]):
98100
raise ValueError(f"Invalid COSETypes({type}) for COSE message.")
99101
return
100102

103+
def __eq__(self: COSEMessage, other: object) -> bool:
104+
if not isinstance(other, COSEMessage):
105+
return NotImplemented
106+
return (
107+
self._type == other._type
108+
and self._protected == other._protected
109+
and self._unprotected == other._unprotected
110+
and self._payload == other._payload
111+
and self._other_fields == other._other_fields
112+
)
113+
114+
def __ne__(self: COSEMessage, other: object) -> bool:
115+
return not self.__eq__(other)
116+
101117
@classmethod
102118
def loads(cls, msg: bytes):
103119
tagged = loads(msg)
@@ -266,8 +282,8 @@ def _validate_cose_message(self, msg: List[Any]):
266282
raise ValueError("The protected headers should be bytes.")
267283
if not isinstance(msg[1], dict):
268284
raise ValueError("The unprotected headers should be Dict[int, Any].")
269-
if not isinstance(msg[2], bytes):
270-
raise ValueError("The payload should be bytes.")
285+
if not isinstance(msg[2], bytes) and msg[2] is not None:
286+
raise ValueError("The payload should be bytes or null.")
271287

272288
countersignatures = msg[1].get(11, None)
273289
if countersignatures is None:
@@ -287,3 +303,36 @@ def _validate_cose_message(self, msg: List[Any]):
287303
def _get_kid(self, sig: list) -> Optional[bytes]:
288304
kid = sig[1].get(4, None)
289305
return kid if kid else self._loads(sig[0]).get(4, None)
306+
307+
def detach_payload(self: Self) -> Tuple[COSEMessage, bytes]:
308+
"""
309+
Detach a payload from the COSE message
310+
311+
Returns:
312+
Tuple[COSEMessage, bytes]: A byte string of the encoded COSE or a
313+
cbor2.CBORTag object, and a byte string of the detached payload.
314+
Raises:
315+
ValueError: The payload does not exist.
316+
"""
317+
318+
if not isinstance(self._payload, bytes):
319+
raise ValueError("The payload does not exist.")
320+
321+
return COSEMessage(self._type, [self._msg[0], self._msg[1], None, *self._msg[3:]]), self._payload
322+
323+
def attach_payload(self: Self, payload: bytes) -> COSEMessage:
324+
"""
325+
Attach a detached content to the COSE message
326+
327+
Args:
328+
payload (bytes): A byte string of detached payload.
329+
Returns:
330+
COSEMessage: The COSE message (self).
331+
Raises:
332+
ValueError: The payload already exist.
333+
"""
334+
335+
if self._payload is not None:
336+
raise ValueError("The payload already exist.")
337+
338+
return COSEMessage(self._type, [self._msg[0], self._msg[1], payload, *self._msg[3:]])

tests/test_cose_message.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,6 @@ def test_cose_usage_examples_cose_mac_countersignature(self):
306306
(COSETypes.MAC0, [], "Invalid COSE message."),
307307
(COSETypes.MAC0, [{}, {}, b""], "The protected headers should be bytes."),
308308
(COSETypes.MAC0, [b"", b"", b""], "The unprotected headers should be Dict[int, Any]."),
309-
(COSETypes.MAC0, [b"", {}, {}], "The payload should be bytes."),
310309
(COSETypes.MAC0, [b"", {11: {}}, b""], "The countersignature should be array."),
311310
(COSETypes.MAC0, [b"", {11: []}, b""], "Invalid countersignature."),
312311
(COSETypes.MAC0, [b"", {11: [b""]}, b""], "Invalid COSE message."),
@@ -461,3 +460,49 @@ def test_cose_message_counterverify_with_different_abbreviated_countersignature(
461460
COSEMessage.loads(countersigned).counterverify(pub_key)
462461
pytest.fail("counterverify() should not fail.")
463462
assert "Failed to verify." in str(err.value)
463+
464+
def test_cose_message_detach_and_attach(self):
465+
"""
466+
Detach the payload from a COSE message.
467+
For example, [an example message](https://github.com/cose-wg/Examples/blob/master/ecdsa-examples/ecdsa-sig-01.json)
468+
```
469+
18([
470+
/ protected: / h'A201260300',
471+
/ unprotected: / {4: h'3131'},
472+
/ payload: / h'546869732069732074686520636F6E74656E742E',
473+
/ signature: / h'6520BBAF2081D7E0ED0F95F76EB0733D667005F7467CEC4B87B9381A6BA1EDE8E00DF29F32A37230F39A842A54821FDD223092819D7728EFB9D3A0080B75380B'
474+
])
475+
```
476+
would be separated into
477+
```
478+
18([
479+
/ protected: / h'A201260300',
480+
/ unprotected: / {4: h'3131'},
481+
/ payload: / null / detached /,
482+
/ signature: / h'6520BBAF2081D7E0ED0F95F76EB0733D667005F7467CEC4B87B9381A6BA1EDE8E00DF29F32A37230F39A842A54821FDD223092819D7728EFB9D3A0080B75380B'
483+
])
484+
```
485+
and
486+
```
487+
546869732069732074686520636F6E74656E742E
488+
```
489+
490+
"""
491+
ecdsa_cose_sign1_example = COSEMessage.loads(
492+
bytes.fromhex(
493+
"D28445A201260300A10442313154546869732069732074686520636F6E74656E742E58406520BBAF2081D7E0ED0F95F76EB0733D667005F7467CEC4B87B9381A6BA1EDE8E00DF29F32A37230F39A842A54821FDD223092819D7728EFB9D3A0080B75380B"
494+
)
495+
)
496+
expected_detached_cose_message = COSEMessage.loads(
497+
bytes.fromhex(
498+
"D28445A201260300A104423131F658406520BBAF2081D7E0ED0F95F76EB0733D667005F7467CEC4B87B9381A6BA1EDE8E00DF29F32A37230F39A842A54821FDD223092819D7728EFB9D3A0080B75380B"
499+
)
500+
)
501+
expected_payload = bytes.fromhex("546869732069732074686520636F6E74656E742E")
502+
503+
detached_content_cose_message, detached_payload = ecdsa_cose_sign1_example.detach_payload()
504+
assert expected_detached_cose_message == detached_content_cose_message
505+
assert expected_payload == detached_payload
506+
507+
reverted_cose_message = detached_content_cose_message.attach_payload(detached_payload)
508+
assert ecdsa_cose_sign1_example == reverted_cose_message

0 commit comments

Comments
 (0)