-
Notifications
You must be signed in to change notification settings - Fork 30
feat: cose hash envelope #212
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+891
−11
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
f6f0572
feat: cose hash envelope
shizhMSFT 556fe71
refactor: unify get algorithm
shizhMSFT ba45647
docs: unify the experimental notice
shizhMSFT f306f0d
test: test algorithm
shizhMSFT 55e0b67
test: fix and test headers
shizhMSFT d56fd7a
refactor!: remove unnecessary utility functions
shizhMSFT bdcdc9e
test: test and fix SignHashEnvelope
shizhMSFT 32712bd
test: test and fix VerifyHashEnvelope
shizhMSFT 589b7ba
docs: update to the latest draft 04
shizhMSFT dde1237
docs: update example to draft-04
shizhMSFT 87052d1
chore: update to draft 05
shizhMSFT File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| package cose | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "io" | ||
| "maps" | ||
| ) | ||
|
|
||
| // HashEnvelopePayload indicates the payload of a Hash_Envelope object. | ||
| // It is used by the [SignHashEnvelope] function. | ||
| // | ||
| // # Experimental | ||
| // | ||
| // Notice: The COSE Hash Envelope API is EXPERIMENTAL and may be changed or | ||
| // removed in a later release. | ||
| type HashEnvelopePayload struct { | ||
| // HashAlgorithm is the hash algorithm used to produce the hash value. | ||
| HashAlgorithm Algorithm | ||
|
|
||
| // HashValue is the hash value of the payload. | ||
| HashValue []byte | ||
|
|
||
| // PreimageContentType is the content type of the data that has been hashed. | ||
| // The value is either an unsigned integer (RFC 7252 Section 12.3) or a | ||
| // string (RFC 9110 Section 8.3). | ||
| // This field is optional. | ||
| // | ||
| // References: | ||
| // - https://www.iana.org/assignments/core-parameters/core-parameters.xhtml | ||
| // - https://www.iana.org/assignments/media-types/media-types.xhtml | ||
| PreimageContentType any // uint / string | ||
|
|
||
| // Location is the location of the hash value in the payload. | ||
| // This field is optional. | ||
| Location string | ||
| } | ||
|
|
||
| // SignHashEnvelope signs a [Sign1Message] using the provided [Signer] and | ||
| // produces a Hash_Envelope object. | ||
| // | ||
| // Hash_Envelope_Protected_Header = { | ||
| // ? &(alg: 1) => int, | ||
| // &(payload_hash_alg: 258) => int | ||
| // &(payload_preimage_content_type: 259) => uint / tstr | ||
| // ? &(payload_location: 260) => tstr | ||
| // * int / tstr => any | ||
| // } | ||
| // | ||
| // Hash_Envelope_Unprotected_Header = { | ||
| // * int / tstr => any | ||
| // } | ||
| // | ||
| // Hash_Envelope_as_COSE_Sign1 = [ | ||
| // protected : bstr .cbor Hash_Envelope_Protected_Header, | ||
| // unprotected : Hash_Envelope_Unprotected_Header, | ||
| // payload: bstr / nil, | ||
| // signature : bstr | ||
| // ] | ||
| // | ||
| // Hash_Envelope = #6.18(Hash_Envelope_as_COSE_Sign1) | ||
| // | ||
| // Reference: https://www.ietf.org/archive/id/draft-ietf-cose-hash-envelope-05.html | ||
| // | ||
| // # Experimental | ||
| // | ||
| // Notice: The COSE Hash Envelope API is EXPERIMENTAL and may be changed or | ||
| // removed in a later release. | ||
| func SignHashEnvelope(rand io.Reader, signer Signer, headers Headers, payload HashEnvelopePayload) ([]byte, error) { | ||
| if err := validateHash(payload.HashAlgorithm, payload.HashValue); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| headers.Protected = setHashEnvelopeProtectedHeader(headers.Protected, &payload) | ||
| headers.RawProtected = nil | ||
| if err := validateHashEnvelopeHeaders(&headers); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return Sign1(rand, signer, headers, payload.HashValue, nil) | ||
| } | ||
|
|
||
| // VerifyHashEnvelope verifies a Hash_Envelope object using the provided | ||
| // [Verifier]. | ||
| // It returns the decoded [Sign1Message] if the verification is successful. | ||
| // | ||
| // # Experimental | ||
| // | ||
| // Notice: The COSE Hash Envelope API is EXPERIMENTAL and may be changed or | ||
| // removed in a later release. | ||
| func VerifyHashEnvelope(verifier Verifier, envelope []byte) (*Sign1Message, error) { | ||
| // parse and validate the Hash_Envelope object | ||
| var message Sign1Message | ||
| if err := message.UnmarshalCBOR(envelope); err != nil { | ||
| return nil, err | ||
| } | ||
| if err := validateHashEnvelopeHeaders(&message.Headers); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // verify the Hash_Envelope object | ||
| if err := message.Verify(nil, verifier); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // cast to type Algorithm | ||
| hashAlgorithm, err := message.Headers.Protected.PayloadHashAlgorithm() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| message.Headers.Protected[HeaderLabelPayloadHashAlgorithm] = hashAlgorithm | ||
|
|
||
| // validate the hash value | ||
| if err := validateHash(hashAlgorithm, message.Payload); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return &message, nil | ||
| } | ||
|
|
||
| // validateHash checks the validity of the known hash. | ||
| func validateHash(alg Algorithm, value []byte) error { | ||
| hash := alg.hashFunc() | ||
| if hash == 0 { | ||
| return nil // no check on unsupported hash algorithms | ||
| } | ||
| if size := hash.Size(); size != len(value) { | ||
| return fmt.Errorf("%v: size mismatch: expected %d, got %d", alg, size, len(value)) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // setHashEnvelopeProtectedHeader sets the protected header for a Hash_Envelope | ||
| // object. | ||
| func setHashEnvelopeProtectedHeader(base ProtectedHeader, payload *HashEnvelopePayload) ProtectedHeader { | ||
| header := maps.Clone(base) | ||
| if header == nil { | ||
| header = make(ProtectedHeader) | ||
| } | ||
| header[HeaderLabelPayloadHashAlgorithm] = payload.HashAlgorithm | ||
| if payload.PreimageContentType != nil { | ||
| header[HeaderLabelPayloadPreimageContentType] = payload.PreimageContentType | ||
| } | ||
| if payload.Location != "" { | ||
| header[HeaderLabelPayloadLocation] = payload.Location | ||
| } | ||
| return header | ||
| } | ||
|
|
||
| // validateHashEnvelopeHeaders validates the headers of a Hash_Envelope object. | ||
| // See https://www.ietf.org/archive/id/draft-ietf-cose-hash-envelope-05.html | ||
| // section 4 for more details. | ||
| func validateHashEnvelopeHeaders(headers *Headers) error { | ||
| var foundPayloadHashAlgorithm bool | ||
| for label, value := range headers.Protected { | ||
| // Validate that all header labels are integers or strings. | ||
| // Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-1.4 | ||
| label, ok := normalizeLabel(label) | ||
| if !ok { | ||
| return errors.New("header label: require int / tstr type") | ||
| } | ||
|
|
||
| switch label { | ||
| case HeaderLabelContentType: | ||
| return errors.New("protected header parameter: content type: not allowed") | ||
| case HeaderLabelPayloadHashAlgorithm: | ||
| _, isAlg := value.(Algorithm) | ||
| if !isAlg && !canInt(value) { | ||
| return errors.New("protected header parameter: payload hash alg: require int type") | ||
| } | ||
| foundPayloadHashAlgorithm = true | ||
| case HeaderLabelPayloadPreimageContentType: | ||
| if !canUint(value) && !canTstr(value) { | ||
| return errors.New("protected header parameter: payload preimage content type: require uint / tstr type") | ||
| } | ||
| case HeaderLabelPayloadLocation: | ||
| if !canTstr(value) { | ||
| return errors.New("protected header parameter: payload location: require tstr type") | ||
| } | ||
| } | ||
| } | ||
| if !foundPayloadHashAlgorithm { | ||
| return errors.New("protected header parameter: payload hash alg: required") | ||
| } | ||
|
|
||
| for label := range headers.Unprotected { | ||
| // Validate that all header labels are integers or strings. | ||
| // Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-1.4 | ||
| label, ok := normalizeLabel(label) | ||
| if !ok { | ||
| return errors.New("header label: require int / tstr type") | ||
| } | ||
|
|
||
| switch label { | ||
| case HeaderLabelContentType: | ||
| return errors.New("unprotected header parameter: content type: not allowed") | ||
| case HeaderLabelPayloadHashAlgorithm: | ||
| return errors.New("unprotected header parameter: payload hash alg: not allowed") | ||
| case HeaderLabelPayloadPreimageContentType: | ||
| return errors.New("unprotected header parameter: payload preimage content type: not allowed") | ||
| case HeaderLabelPayloadLocation: | ||
| return errors.New("unprotected header parameter: payload location: not allowed") | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.