From 9ac8c01e925aa7b21e91f8ff20ba5dbfd4c3699d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 31 May 2026 03:52:35 +0000 Subject: [PATCH] Bump github.com/Microsoft/cosesign1go from 1.4.0 to 1.5.0 Bumps [github.com/Microsoft/cosesign1go](https://github.com/Microsoft/cosesign1go) from 1.4.0 to 1.5.0. - [Release notes](https://github.com/Microsoft/cosesign1go/releases) - [Commits](https://github.com/Microsoft/cosesign1go/compare/v1.4.0...v1.5.0) --- updated-dependencies: - dependency-name: github.com/Microsoft/cosesign1go dependency-version: 1.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +- .../cosesign1go/pkg/cosesign1/Makefile | 36 +- .../cosesign1go/pkg/cosesign1/Makefile.certs | 4 +- .../cosesign1go/pkg/cosesign1/check.go | 393 +++++++++++++++++- .../cosesign1go/pkg/cosesign1/constants.go | 41 ++ .../cosesign1/esrp_cp_ledger_pub_keys.json | 11 + .../cosesign1/esrp_db_ledger_pub_keys.json | 18 + vendor/modules.txt | 2 +- 9 files changed, 485 insertions(+), 26 deletions(-) create mode 100644 vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/constants.go create mode 100644 vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/esrp_cp_ledger_pub_keys.json create mode 100644 vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/esrp_db_ledger_pub_keys.json diff --git a/go.mod b/go.mod index c12fab7e1a..49495fe289 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ tool ( ) require ( - github.com/Microsoft/cosesign1go v1.4.0 + github.com/Microsoft/cosesign1go v1.5.0 github.com/Microsoft/didx509go v0.0.3 github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 github.com/blang/semver/v4 v4.0.0 diff --git a/go.sum b/go.sum index ce144cee6c..93c028236e 100644 --- a/go.sum +++ b/go.sum @@ -362,8 +362,8 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= -github.com/Microsoft/cosesign1go v1.4.0 h1:VdiqzsilEE6t1GQi98I/h0WpVFM7AyMEeyP8ud7V/BY= -github.com/Microsoft/cosesign1go v1.4.0/go.mod h1:1La/HcGw19rRLhPW0S6u55K6LKfti+GQSgGCtrfhVe8= +github.com/Microsoft/cosesign1go v1.5.0 h1:YmQCF8z7dGp50Rp/+rLTLFOFgIfZ1GSUHXPgLLlOlNk= +github.com/Microsoft/cosesign1go v1.5.0/go.mod h1:s7E3nBWxb//ZLhuLAU5u9EZ1qMGBdgZzrKIUW1H/OIY= github.com/Microsoft/didx509go v0.0.3 h1:n/owuFOXVzCEzSyzivMEolKEouBm9G0NrEDgoTekM8A= github.com/Microsoft/didx509go v0.0.3/go.mod h1:wWt+iQsLzn3011+VfESzznLIp/Owhuj7rLF7yLglYbk= github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 h1:0kQAzHq8vLs7Pptv+7TxjdETLf/nIqJpIB4oC6Ba4vY= diff --git a/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/Makefile b/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/Makefile index 8c982132c2..6527ca105d 100644 --- a/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/Makefile +++ b/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/Makefile @@ -24,12 +24,11 @@ # -# note test-fail is expected to fail - -AUTOPARSE_CHAIN:=0 +# Opaque label written into the COSE `iss` header. It is NOT validated against +# the chain at create time; the Go unit test asserts the round-trip preserves +# this exact string, so don't change it without updating the test. ISSUER_DID:="TestIssuer" FEED:="TestFeed" -DID_FINGERPRINT:="" all: chain.pem cose test-fail test-pass @@ -38,11 +37,6 @@ cose: infra.rego.cose %.pem: $(MAKE) -f Makefile.certs chain.pem -ifeq "$(AUTOPARSE_CHAIN)" "1" -ISSUER_DID = $(shell ./sign1util did-x509 -chain chain.pem -policy cn) -DID_FINGERPRINT = $(shell ./sign1util did-x509 -chain chain.pem -policy cn | cut -d: -f5) -endif - # from these media types have to match containerd. The also need to change and the security policy one ought to be x-ms-ccepolicy-frag # fragment atrifact type = application/x-ms-ccepolicy-frag # fragment media type = application/cose-x509+rego @@ -74,13 +68,13 @@ show: sign1util didx509: chain.pem sign1util ./sign1util did-x509 -chain chain.pem -i 1 -policy "subject:CN:Test Leaf (DO NOT TRUST)" -verbose -info: chain.pem sign1util - @echo "ISSUER_DID: $(ISSUER_DID)" - @echo "DID_FINGERPRINT: $(DID_FINGERPRINT)" - -# for this to pass the did:x509 fingerprint (RgpNsHOK5hPlCAfTtiGY_BcDhFRxQbJnhlxNDhxps6U here) needs to be the one output from make print -did-check: chain.pem infra.rego.cose sign1util info - ./sign1util check -in infra.rego.cose -did $(ISSUER_DID) +# did-check derives the REAL did:x509 from chain.pem at run time and resolves +# it against the chain. Fails loudly if did-x509 returns empty. +did-check: chain.pem infra.rego.cose sign1util + @did="$$(./sign1util did-x509 -chain chain.pem -policy cn)"; \ + test -n "$$did" || { echo "did-x509 returned empty - check chain.pem"; exit 1; }; \ + echo "did-check: using did=$$did"; \ + ./sign1util check -in infra.rego.cose -did "$$did" # For normal workflow start from the chain.pem, here we'd take the chain from inside the cose sign1 doc, eg to manually confirm it is # as otherwise expected (ie that the issuer DID matches the chain) or to shortcut getting a DID from a cose document. @@ -92,12 +86,18 @@ did-from-cose: sign1util infra.rego.cose # note that since the infra.rego.cose is actually good the first part of the check will report a pass "checkCoseSign1 passed" # expect "DID resolvers failed: err: DID verification failed: unexpected certificate fingerprint" +# The recipe is expected to fail at the tool level; invert the exit code so the make target succeeds. did-fail-fingerprint: chain.pem sign1util infra.rego.cose - ./sign1util check -in infra.rego.cose -did did:x509:0:sha256:XXXi_nuWegx4NiLaeGabiz36bDUhDDiHEFl8HXMA_4o::subject:CN:Test+Leaf+%28DO+NOT+TRUST%29 + ! ./sign1util check -in infra.rego.cose -did did:x509:0:sha256:XXXi_nuWegx4NiLaeGabiz36bDUhDDiHEFl8HXMA_4o::subject:CN:Test+Leaf+%28DO+NOT+TRUST%29 # expect "DID resolvers failed: err: DID verification failed: invalid subject value: CN=Test XXXX (DO NOT TRUST)" +# Builds a DID with the REAL fingerprint but a WRONG subject; recipe must still +# fail at the tool level - `!` inverts that into a make-level success. did-fail-subject: chain.pem sign1util infra.rego.cose - ./sign1util check -in infra.rego.cose -did did:x509:0:sha256:$(DID_FINGERPRINT)::subject:CN:Test+XXXX+%28DO+NOT+TRUST%29 + @fp="$$(./sign1util did-x509 -chain chain.pem -policy cn | cut -d: -f5)"; \ + test -n "$$fp" || { echo "could not derive fingerprint - check chain.pem"; exit 1; }; \ + ! ./sign1util check -in infra.rego.cose \ + -did "did:x509:0:sha256:$$fp::subject:CN:Test+XXXX+%28DO+NOT+TRUST%29" did-fail: did-fail-subject did-fail-fingerprint diff --git a/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/Makefile.certs b/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/Makefile.certs index 9a0ce35257..e2452a7728 100644 --- a/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/Makefile.certs +++ b/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/Makefile.certs @@ -8,12 +8,12 @@ all: chain.pem root.cert.pem: root.private.pem openssl req -new -key $< -out $@.tmp.csr -subj "/CN=Test Root CA (DO NOT TRUST)" -addext 'basicConstraints=critical,CA:TRUE' -addext 'keyUsage=digitalSignature,keyCertSign' - openssl x509 -req -days 365 -in $@.tmp.csr -signkey $< -out $@ -CAcreateserial -extfile cert.extensions.cfg + openssl x509 -req -days 3650 -in $@.tmp.csr -signkey $< -out $@ -CAcreateserial -extfile cert.extensions.cfg rm -rf $@.tmp.csr intermediate.cert.pem: intermediate.private.pem | root.private.pem openssl req -new -key $< -out $@.tmp.csr -subj "/CN=Test Intermediate CA (DO NOT TRUST)" -addext 'basicConstraints=critical,CA:TRUE' -addext 'keyUsage=digitalSignature,keyCertSign' - openssl x509 -req -days 365 -in $@.tmp.csr -CA ${subst private,cert,$|} -CAkey $| -out $@ -CAcreateserial -extfile cert.extensions.cfg + openssl x509 -req -days 1825 -in $@.tmp.csr -CA ${subst private,cert,$|} -CAkey $| -out $@ -CAcreateserial -extfile cert.extensions.cfg rm $@.tmp.csr leaf.cert.pem: leaf.private.pem | intermediate.private.pem diff --git a/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/check.go b/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/check.go index 7366c37897..7f10862daa 100644 --- a/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/check.go +++ b/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/check.go @@ -1,11 +1,16 @@ package cosesign1 import ( + "bytes" + "crypto" + "crypto/sha256" "crypto/x509" "fmt" + "math" didx509resolver "github.com/Microsoft/didx509go/pkg/did-x509-resolver" + "github.com/fxamacker/cbor/v2" "github.com/sirupsen/logrus" "github.com/veraison/go-cose" @@ -63,6 +68,84 @@ type UnpackedCoseSign1 struct { ChainPem string Payload []byte CertChain []*x509.Certificate + Protected cose.ProtectedHeader + Unprotected cose.UnprotectedHeader + // Receipts contains the parsed COSE Receipts attached to the unprotected + // `receipts` header (label 394), if any. Receipts are parsed but not + // validated; use (ParsedCOSEReceipt).Validate to validate each. + Receipts []ParsedCOSEReceipt +} + +// ParsedCOSEReceipt is a parsed COSE Receipt attached to a COSE Sign1 +// envelope. It carries the original CBOR-encoded blob alongside the decoded +// COSE_Sign1 message and a few convenience fields extracted from its +// protected header. +type ParsedCOSEReceipt struct { + // Raw is the original CBOR-encoded COSE_Sign1 receipt blob. + Raw []byte + // Message is the decoded COSE_Sign1 receipt. + Message cose.Sign1Message + // Issuer is the value of CWT claim `iss` from the receipt's protected CWT + // Claims header + Issuer string + // The value of the receipt's protected `kid` header, interpreted + // as a string (CCF uses ASCII hex) + Kid string + // The expected hash of the Signed Statement this receipt is for. + ExpectedDataHash []byte +} + +// parseCOSEReceipts decodes the unprotected `receipts` header (label 394) +// into []ParsedCOSEReceipt. It does not validate the receipts. +func parseCOSEReceipts(unprotected cose.UnprotectedHeader) ([]ParsedCOSEReceipt, error) { + rcptsVal, ok := unprotected[COSE_Header_Receipts] + if !ok { + return nil, nil + } + rcptsArr, ok := rcptsVal.([]interface{}) + if !ok { + return nil, fmt.Errorf("receipts header is not an array (got %T)", rcptsVal) + } + out := make([]ParsedCOSEReceipt, 0, len(rcptsArr)) + for i, r := range rcptsArr { + rb, ok := r.([]byte) + if !ok { + return nil, fmt.Errorf("receipt %d is not a byte string (got %T)", i, r) + } + var msg cose.Sign1Message + if err := msg.UnmarshalCBOR(rb); err != nil { + return nil, fmt.Errorf("receipt %d: parsing COSE_Sign1: %w", i, err) + } + rcpt := ParsedCOSEReceipt{Raw: rb, Message: msg} + if kidVal, ok := msg.Headers.Protected[COSE_Header_kid]; ok { + if kidBytes, ok := kidVal.([]byte); ok { + rcpt.Kid = string(kidBytes) + } else { + return nil, fmt.Errorf("receipt %d: kid is not a byte string (got %T)", i, kidVal) + } + } else { + return nil, fmt.Errorf("receipt %d: kid header missing", i) + } + if cwtVal, ok := msg.Headers.Protected[COSE_Header_CWTClaims]; ok { + if cwt, ok := cwtVal.(map[interface{}]interface{}); ok { + issVal, issPresent := cwt[CWT_Issuer] + if !issPresent { + return nil, fmt.Errorf("receipt %d: issuer (iss) claim missing from CWT claims", i) + } + if iss, ok := issVal.(string); ok { + rcpt.Issuer = iss + } else { + return nil, fmt.Errorf("receipt %d: issuer is not a string (got %T)", i, issVal) + } + } else { + return nil, fmt.Errorf("receipt %d: CWT claims is not a map (got %T)", i, cwtVal) + } + } else { + return nil, fmt.Errorf("receipt %d: CWT claims missing", i) + } + out = append(out, rcpt) + } + return out, nil } // This function is rather unpleasant in that it both decodes the COSE Sign1 document and its various @@ -177,10 +260,36 @@ func UnpackAndValidateCOSE1CertChain(raw []byte) (*UnpackedCoseSign1, error) { return nil, err } - issuer := getStringValue(protected, "iss") - feed := getStringValue(protected, "feed") + cwt, hasCwt := protected[COSE_Header_CWTClaims] + var issuer, feed string + if hasCwt { + cwt, ok := cwt.(map[interface{}]interface{}) + if !ok { + return nil, fmt.Errorf("expected CWTClaims header to be a map[any]any, got %T", cwt) + } + issuer = getStringValue(cwt, CWT_Issuer) + feed = getStringValue(cwt, CWT_Subject) + } else { + issuer = getStringValue(protected, "iss") + feed = getStringValue(protected, "feed") + } + contenttype := getStringValue(protected, cose.HeaderLabelContentType) + receipts, err := parseCOSEReceipts(msg.Headers.Unprotected) + if err != nil { + return nil, fmt.Errorf("parsing receipts: %w", err) + } + if len(receipts) > 0 { + dataHash, err := computeSignedStatementDataHash(raw) + if err != nil { + return nil, fmt.Errorf("computing signed statement data hash: %w", err) + } + for i := range receipts { + receipts[i].ExpectedDataHash = dataHash + } + } + return &UnpackedCoseSign1{ Pubcert: leafCertBase64, Feed: feed, @@ -190,5 +299,285 @@ func UnpackAndValidateCOSE1CertChain(raw []byte) (*UnpackedCoseSign1, error) { ContentType: contenttype, Payload: msg.Payload, CertChain: chain, + Protected: protected, + Unprotected: msg.Headers.Unprotected, + Receipts: receipts, }, nil } + +// asInt64 coerces a CBOR-decoded integer value (which may be returned as +// int64, uint64 or int by different decoders) to an int64. +func asInt64(v interface{}) (int64, bool) { + switch n := v.(type) { + case int64: + return n, true + case int: + return int64(n), true + case uint64: + if n > math.MaxInt64 { + logrus.Errorf("Unable to convert %v to int64 due to overflow", n) + return 0, false + } + return int64(n), true + case uint: + // uint is 64bit on 64bit platforms, so can overflow int64 + if n > math.MaxInt64 { + logrus.Errorf("Unable to convert %v to int64 due to overflow", n) + return 0, false + } + return int64(n), true + } + return 0, false +} + +// Validate validates the COSE Receipt's structure and signature. See +// https://www.ietf.org/archive/id/draft-ietf-cose-merkle-tree-proofs-18.html +// for details about COSE Receipts. +// +// It checks that: +// - the protected header carries a vds (label 395), +// - the payload is detached, +// - the unprotected `vdp` header (label 396) contains at least one +// inclusion proof (key -1) encoded as a byte string, +// - the Merkle root recomputed from each inclusion proof verifies the +// receipt's COSE_Sign1 signature, using the public key in `keys` indexed by +// r.Kid. +// - The data-hash in the receipt matches the expected hash of the signed +// statement it is for. +func (r ParsedCOSEReceipt) Validate(keys map[string]crypto.PublicKey) error { + msg := r.Message + + vdsVal, ok := msg.Headers.Protected[COSE_Header_vds] + if !ok { + return fmt.Errorf("missing vds (label %d) in protected header", COSE_Header_vds) + } + vds, ok := asInt64(vdsVal) + if !ok { + return fmt.Errorf("vds has wrong type: %T", vdsVal) + } + + if msg.Payload != nil { + return fmt.Errorf("payload must be detached but has %d bytes", len(msg.Payload)) + } + + algoVal, ok := msg.Headers.Protected[cose.HeaderLabelAlgorithm] + if !ok { + return fmt.Errorf("missing algorithm in protected header") + } + algo, ok := algoVal.(cose.Algorithm) + if !ok { + return fmt.Errorf("algorithm has wrong type: %T", algoVal) + } + + pubKey, ok := keys[r.Kid] + if !ok { + return fmt.Errorf("no key for kid %s", r.Kid) + } + + vdpVal, ok := msg.Headers.Unprotected[COSE_Header_vdp] + if !ok { + return fmt.Errorf("missing vdp (label %d) in unprotected header", COSE_Header_vdp) + } + vdpMap, ok := vdpVal.(map[interface{}]interface{}) + if !ok { + return fmt.Errorf("vdp has wrong type: %T", vdpVal) + } + inclVal, ok := vdpMap[COSE_ProofInclusion] + if !ok { + return fmt.Errorf("no inclusion proofs (key %d) in vdp", COSE_ProofInclusion) + } + inclArr, ok := inclVal.([]interface{}) + if !ok { + return fmt.Errorf("inclusion proofs has wrong type: %T", inclVal) + } + if len(inclArr) == 0 { + return fmt.Errorf("inclusion proofs array is empty") + } + + verifier, err := cose.NewVerifier(algo, pubKey) + if err != nil { + return fmt.Errorf("cose.NewVerifier (algo %d): %w", algo, err) + } + + for i, p := range inclArr { + pb, ok := p.([]byte) + if !ok { + return fmt.Errorf("inclusion proof %d is not a byte string (got %T)", i, p) + } + var root, dataHash []byte + switch vds { + case COSE_vds_CCF_LEDGER_SHA256: + root, dataHash, err = CCF_ComputeRoot(pb) + default: + return fmt.Errorf("only receipts with CCF profile supported (got vds %d)", vds) + } + if err != nil { + return fmt.Errorf("inclusion proof %d: %w", i, err) + } + if !bytes.Equal(dataHash, r.ExpectedDataHash) { + return fmt.Errorf("inclusion proof %d: leaf data-hash %x does not match the expected value %x for the signed envelope", i, dataHash, r.ExpectedDataHash) + } + logrus.Debugf("receipt inclusion proof %d recomputed root: %x", i, root) + // Verify the receipt's COSE_Sign1 signature using the recomputed + // Merkle root as the detached payload. + msg.Payload = root + if err := msg.Verify(nil, verifier); err != nil { + return fmt.Errorf("inclusion proof %d: signature verification failed (recomputed root=%x, kid=%s, alg=%d): %w", i, root, r.Kid, algo, err) + } + msg.Payload = nil + } + return nil +} + +// Decodes a CCF inclusion proof (the bstr-wrapped CBOR `ccf-inclusion-proof` +// structure) and recomputes the Merkle root using the algorithm described in +// section 3.2 of +// https://datatracker.ietf.org/doc/html/draft-ietf-scitt-receipts-ccf-profile-02 +// Returns the recomputed Merkle root and the data-hash from the leaf (this +// needs to be verified by the caller against an expected value). +func CCF_ComputeRoot(proofBytes []byte) ([]byte, []byte, error) { + var proof map[int64]interface{} + if err := cbor.Unmarshal(proofBytes, &proof); err != nil { + return nil, nil, fmt.Errorf("decoding inclusion proof: %w", err) + } + // ccf-inclusion-proof = bstr .cbor { + // &(leaf: 1) => ccf-leaf + // &(path: 2) => [+ ccf-proof-element] + // } + leafVal, ok := proof[1] + if !ok { + return nil, nil, fmt.Errorf("missing leaf (key 1)") + } + pathVal, ok := proof[2] + if !ok { + return nil, nil, fmt.Errorf("missing path (key 2)") + } + + // ccf-leaf = [ + // ; Byte string of size HASH_SIZE(32) + // internal-transaction-hash: bstr .size 32 + // + // ; Text string of at most 1024 bytes + // internal-evidence: tstr .size (1..1024) + // + // ; Byte string of size HASH_SIZE(32) + // data-hash: bstr .size 32 + // ] + leafArr, ok := leafVal.([]interface{}) + if !ok || len(leafArr) != 3 { + return nil, nil, fmt.Errorf("leaf must be a 3-element array, got %T len %d", leafVal, lenOf(leafVal)) + } + internalTxHash, ok := leafArr[0].([]byte) + if !ok || len(internalTxHash) != 32 { + return nil, nil, fmt.Errorf("leaf.internal-transaction-hash must be a 32-byte bstr, got %T", leafArr[0]) + } + internalEvidenceStr, ok := leafArr[1].(string) + if !ok { + return nil, nil, fmt.Errorf("leaf.internal-evidence must be a text tstr, got %T", leafArr[1]) + } + internalEvidence := []byte(internalEvidenceStr) + if len(internalEvidence) < 1 || len(internalEvidence) > 1024 { + return nil, nil, fmt.Errorf("leaf.internal-evidence has invalid length %d", len(internalEvidence)) + } + dataHash, ok := leafArr[2].([]byte) + if !ok || len(dataHash) != 32 { + return nil, nil, fmt.Errorf("leaf.data-hash must be a 32-byte bstr, got %T", leafArr[2]) + } + + // Leaf hash: + // h := HASH(internal-transaction-hash || HASH(internal-evidence) || data-hash) + evidenceHash := sha256.Sum256(internalEvidence) + leafConcat := make([]byte, 0, 32+32+32) + leafConcat = append(leafConcat, internalTxHash...) + leafConcat = append(leafConcat, evidenceHash[:]...) + leafConcat = append(leafConcat, dataHash...) + leafHash := sha256.Sum256(leafConcat) + h := leafHash[:] + logrus.Debugf("CCF leaf: internal-tx-hash=%x evidence=%q (hash=%x) data-hash=%x -> leaf=%x", internalTxHash, internalEvidence, evidenceHash[:], dataHash, h) + + pathArr, ok := pathVal.([]interface{}) + if !ok { + return nil, nil, fmt.Errorf("path must be an array") + } + if len(pathArr) == 0 { + return nil, nil, fmt.Errorf("path must contain at least one element") + } + + for i, el := range pathArr { + // ccf-proof-element = [ + // ; Position of the element + // left: bool + // + // ; Hash of the proof element: byte string of size HASH_SIZE(32) + // hash: bstr .size 32 + // ] + elArr, ok := el.([]interface{}) + if !ok || len(elArr) != 2 { + return nil, nil, fmt.Errorf("path element %d must be a 2-element array", i) + } + left, ok := elArr[0].(bool) + if !ok { + return nil, nil, fmt.Errorf("path element %d left flag must be a bool", i) + } + hash, ok := elArr[1].([]byte) + if !ok { + return nil, nil, fmt.Errorf("path element %d hash must be a 32-byte bstr, got %T", i, elArr[1]) + } + if len(hash) != 32 { + return nil, nil, fmt.Errorf("path element %d hash must be 32 bytes, got %d bytes", i, len(hash)) + } + var concat []byte + if left { + concat = append(concat, hash...) + concat = append(concat, h...) + } else { + concat = append(concat, h...) + concat = append(concat, hash...) + } + sum := sha256.Sum256(concat) + h = sum[:] + logrus.Debugf("CCF path step %d: left=%v sibling=%x -> h=%x", i, left, hash, h) + } + return h, dataHash, nil +} + +// computeSignedStatementDataHash returns sha256 of the tagged COSE_Sign1 +// envelope with its unprotected header reset to an empty map. This should match +// the data-hash in the CCF receipt. +// +// This is the hash of the Signed Statement as defined by +// https://datatracker.ietf.org/doc/html/draft-ietf-scitt-architecture-22 +func computeSignedStatementDataHash(envelope []byte) ([]byte, error) { + var arr struct { + _ struct{} `cbor:",toarray"` + Protected cbor.RawMessage + Unprot map[interface{}]interface{} + Payload cbor.RawMessage + Signature cbor.RawMessage + } + if err := cbor.Unmarshal(envelope, &arr); err != nil { + return nil, fmt.Errorf("decoding COSE_Sign1: %w", err) + } + arr.Unprot = map[interface{}]interface{}{} + em, err := cbor.CanonicalEncOptions().EncMode() + if err != nil { + return nil, err + } + body, err := em.Marshal(arr) + if err != nil { + return nil, fmt.Errorf("encoding stripped COSE_Sign1: %w", err) + } + tagged, err := em.Marshal(cbor.Tag{Number: COSE_Sign1_Tag, Content: cbor.RawMessage(body)}) + if err != nil { + return nil, fmt.Errorf("tagging COSE_Sign1: %w", err) + } + digest := sha256.Sum256(tagged) + return digest[:], nil +} + +func lenOf(v interface{}) int { + if a, ok := v.([]interface{}); ok { + return len(a) + } + return -1 +} diff --git a/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/constants.go b/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/constants.go new file mode 100644 index 0000000000..eb4d01f7e6 --- /dev/null +++ b/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/constants.go @@ -0,0 +1,41 @@ +package cosesign1 + +const COSE_Sign1_Tag = 18 + +// COSE Header Parameters +// https://www.iana.org/assignments/cose/cose.xhtml +const ( + COSE_Header_kid = int64(4) + COSE_Header_CWTClaims = int64(15) + COSE_Header_x5chain = int64(33) + COSE_Header_x5t = int64(34) + COSE_Header_PayloadHashAlg = int64(258) + COSE_Header_PreimageContentType = int64(259) + COSE_Header_PayloadLocation = int64(260) + COSE_Header_Receipts = int64(394) + COSE_Header_vds = int64(395) + COSE_Header_vdp = int64(396) +) + +// COSE Verifiable Data Structure Algorithms +// (Values for COSE_HeaderLabelvds) +const ( + COSE_vds_RFC9162_SHA256 = int64(1) + + // TBD_1 in https://www.ietf.org/archive/id/draft-birkholz-cose-receipts-ccf-profile-05.html + COSE_vds_CCF_LEDGER_SHA256 = int64(2) +) + +// COSE Verifiable Data Structure Proofs +// (These are the map keys inside a COSE_HeaderLabelReceipts header). +const ( + COSE_ProofInclusion = int64(-1) + COSE_ProofConsistency = int64(-2) +) + +// CWT Claims +// https://www.iana.org/assignments/cwt/cwt.xhtml +const ( + CWT_Issuer = int64(1) + CWT_Subject = int64(2) +) diff --git a/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/esrp_cp_ledger_pub_keys.json b/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/esrp_cp_ledger_pub_keys.json new file mode 100644 index 0000000000..5f9507329e --- /dev/null +++ b/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/esrp_cp_ledger_pub_keys.json @@ -0,0 +1,11 @@ +{ + "keys": [ + { + "crv": "P-384", + "kid": "a7ad3b7729516ca443fa472a0f2faa4a984ee3da7eafd17f98dcffbac4a6a10f", + "kty": "EC", + "x": "m0kQ1A_uqHWuP9fdGSKatSq2brcAJ6-q3aZ5P35wjbgtNnlm2u-NLF1qM-yC4I2n", + "y": "J9cJFrdWvUf6PCMkrWFTgB16uEq4mSMCI4NPVytnwYX6xNnuJ2GTrPtafKYg1VNi" + } + ] +} \ No newline at end of file diff --git a/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/esrp_db_ledger_pub_keys.json b/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/esrp_db_ledger_pub_keys.json new file mode 100644 index 0000000000..c1355ba425 --- /dev/null +++ b/vendor/github.com/Microsoft/cosesign1go/pkg/cosesign1/esrp_db_ledger_pub_keys.json @@ -0,0 +1,18 @@ +{ + "keys": [ + { + "crv": "P-384", + "kid": "23d48c280f71abf575c81e89f18a4dc9f3b33d8a3b149b16ad836c8553f95bc0", + "kty": "EC", + "x": "2GIJv9nAhste7hDWrpea1-hd_BAPXg4ZIxLy4C4hAX2eCpqT4siLqohA2KIVJti8", + "y": "aTT6XYHZPBgdI4RLFo2BaP1RVuOG2rFg5JBhYvt871HIwmtzNtwXl3_NBwfcqr8O" + }, + { + "crv": "P-384", + "kid": "da7694f16def5a056ca96afb21e89a9450e4cc875e2de351da76d99544a3e849", + "kty": "EC", + "x": "GeQ_qA3ZxYoaan3D0nA7xriMcmiMqQ0UNY1DLs7C5kIEaI_RL_2duRcG1Ii6g-8-", + "y": "uKiRr4UU8aXumcA8wu6LOatH0qL2AjFy3_8iBx3mbt1foS5xNHlXchMMLTSCvRLn" + } + ] +} diff --git a/vendor/modules.txt b/vendor/modules.txt index c9d971c854..e9e95de11c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -4,7 +4,7 @@ cyphar.com/go-pathrs cyphar.com/go-pathrs/internal/fdutils cyphar.com/go-pathrs/internal/libpathrs cyphar.com/go-pathrs/procfs -# github.com/Microsoft/cosesign1go v1.4.0 +# github.com/Microsoft/cosesign1go v1.5.0 ## explicit; go 1.20 github.com/Microsoft/cosesign1go/pkg/cosesign1 # github.com/Microsoft/didx509go v0.0.3