TimothyClaeys / pycose

A Python implementation of the COSE specification (CBOR Object Signing and Encryption) described in RFC 8152.
https://tools.ietf.org/html/rfc8152
Other
39 stars 24 forks source link

decoding a Sign1Message with a detached payload fails #121

Closed robinbryce closed 1 week ago

robinbryce commented 2 weeks ago

decoding requires that the payload is attached (not None)

I believe the following is a valid (hex encoded) Sign1Message with a detached payload

d284590180a401382204583a6566393930623a6d65726b6c652d6c6f672d7369676e696e672f62356539626435643639313334313136393831346538363766306463653930360da301782c68747470733a2f2f6a6974617669646465626464642d696e6672612e7661756c742e617a7572652e6e65742f02785276312f6d6d72732f74656e616e742f62333431613531612d306361382d343738392d613866332d3739626339306462653433392f302f6d6173736966732f303030303030303030303030303030302e6c6f6708a101a60162454302783a6566393930623a6d65726b6c652d6c6f672d7369676e696e672f62356539626435643639313334313136393831346538363766306463653930360338222065502d333834215830a41855ccb537d003b890841bcba2868bb57de9a90cea10b4f512ece63ea5175a458b08ac6519fbb0b937d729ebb07d5c225830e072e7d78fc320a8f89dd58996dad8c7e585e7e7d1db7ffc8a0f05154152d83461734d00c31c2c51a66138c2d64f2e4a19018b02a119018ca12081a201030282582099eefba9215e3807883ba963ab0c8ea53e82033a698b9af96222b24760cf77c1582072d4e90ef0e472fece57d2f4d9b3957747cba5c0558974c823af4b9f18a87e5af65860240a075af07c9cfbc720d2dcf82f5d2b79c95b463ab648af048d732f13ce548d5d253de08ff6d0baa08e2dc4a87c4d31a124f1889013bcf1dfeeb481466456a12f6b7a0b08c519c4556c5406586f39db0b300f9052bbfeddf40c50161f763f72```

If I explicitly decode like this
    try:
        cbor_msg = cbor2.loads(message)
        cbor_tag = cbor_msg.tag
        cose_obj = cbor_msg.value
    except AttributeError:
        raise AttributeError("Message was not tagged.")
    except ValueError:
        raise ValueError("Decode accepts only bytes as input.")

    message: Sign1Message = None
    # pycose Sign1Message.decode can't handle detached payloads
    if not isinstance(cose_obj, list):
        raise TypeError("Bytes cannot be decoded as COSE message")
    else:
        try:
            # message = Sign1Message._COSE_MSG_ID[cbor_tag].from_cose_obj(cose_obj, True)
            message1 = CoseBase.from_cose_obj(cose_obj, True)
            message = Sign1Message(message1.phdr, message1.uhdr, b'')
        except KeyError as e:
            raise KeyError("CBOR tag is not recognized", e)


It works
robinbryce commented 2 weeks ago

Two IETF drafts which require detached payloads:

robinbryce commented 2 weeks ago

Idealy, there would be a two step decode, so that the payload can be produced from contenent in the unprotected header and other content supplied out of band

BrianSipos commented 2 weeks ago

There is a similar need for detached payloads in draft-ietf-dtn-bpsec-cose.

In my use of pycose I manually manipulate the CBOR-decoded array before using from_cose_obj() but it would be convenient to not need to go through those extra steps and possible failure modes.

robinbryce commented 1 week ago

Yep, I ended up doing this

def decode_sign1_detached(message: bytes, payload=None) -> Sign1Message:

    # decode the cbor encoded cose sign1 message, per the CoseBase implementation
    try:
        cbor_msg = cbor2.loads(message)
        cose_obj = cbor_msg.value
    except AttributeError as e:
        raise AttributeError("Message was not tagged.") from e
    except ValueError as e:
        raise ValueError("Decode accepts only bytes as input.") from e

    if payload is None:
        payload = b""

    cose_obj[2] = (
        payload  # force replace with b'' if payload is detached, will not verify until replaced with correct content
    )
    return Sign1Message.from_cose_obj(cose_obj, True)
robinbryce commented 1 week ago

Of course that ignores the possibility that the provided message actually has a payload. but its fine if the caller knows this a-priori