Skip to content

Commit

Permalink
feat: multikey support for ecdsa2019 (#73)
Browse files Browse the repository at this point in the history
* feat: multikey support for ecdsa2019

* fix: test
  • Loading branch information
skynet2 authored Nov 22, 2024
1 parent 59af0e1 commit cb8385b
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 39 deletions.
92 changes: 73 additions & 19 deletions dataintegrity/suite/ecdsa2019/ecdsa2019.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ SPDX-License-Identifier: Apache-2.0
package ecdsa2019

import (
"crypto/elliptic"
"crypto/sha256"
"crypto/sha512"
"encoding/json"
"errors"
"fmt"
"hash"
"reflect"

"github.com/multiformats/go-multibase"
"github.com/piprate/json-gold/ld"
Expand Down Expand Up @@ -218,6 +220,22 @@ func (s *Suite) CreateProof(doc []byte, opts *models.ProofOptions) (*models.Proo
return p, nil
}

func (s *Suite) unmarshalECKey(ecCRV elliptic.Curve, pubKey []byte) ([]byte, error) {
xBig, yBig := elliptic.UnmarshalCompressed(ecCRV, pubKey)

if xBig != nil && yBig != nil {
pubKey = elliptic.Marshal(ecCRV, xBig, yBig)
} else {
pubKey = append([]byte{4}, pubKey...)
}

if pubKey == nil {
return nil, errors.New("failed to unmarshal EC key")
}

return pubKey, nil
}

func (s *Suite) transformAndHash(doc []byte, opts *models.ProofOptions) ([]byte, *pubkey.PublicKey, Verifier, error) {
if opts.SuiteType == "" {
opts.SuiteType = SuiteType
Expand All @@ -230,28 +248,61 @@ func (s *Suite) transformAndHash(doc []byte, opts *models.ProofOptions) ([]byte,
return nil, nil, nil, fmt.Errorf("ecdsa-2019 suite expects JSON-LD payload: %w", err)
}

vmKey := opts.VerificationMethod.JSONWebKey()
if vmKey == nil {
return nil, nil, nil, errors.New("verification method needs JWK")
}

var (
keyType kms.KeyType
h hash.Hash
verifier Verifier
)

switch vmKey.Crv {
case "P-256":
h = sha256.New()
verifier = s.p256Verifier
keyType = kms.ECDSAP256TypeIEEEP1363
case "P-384":
h = sha512.New384()
verifier = s.p384Verifier
keyType = kms.ECDSAP384TypeIEEEP1363
default:
return nil, nil, nil, errors.New("unsupported ECDSA curve")
finalKey := &pubkey.PublicKey{Type: "", JWK: opts.VerificationMethod.JSONWebKey()}

if finalKey.JWK == nil && len(opts.VerificationMethod.Value) > 2 {
header := opts.VerificationMethod.Value[0:2]

var curve elliptic.Curve

// ref https://www.w3.org/TR/controller-document/#Multikey
if reflect.DeepEqual([]byte{0x80, 0x24}, header) ||
reflect.DeepEqual([]byte{0x12, 0x00}, header) {
verifier = s.p256Verifier
h = sha256.New()
curve = elliptic.P256()
} else if reflect.DeepEqual([]byte{0x81, 0x24}, header) ||
reflect.DeepEqual([]byte{0x12, 0x01}, header) {
verifier = s.p384Verifier
h = sha512.New384()
curve = elliptic.P384()
}

if curve == nil {
return nil, nil, nil, errors.New("unsupported ECDSA curve")
}

pubKey, errEc := s.unmarshalECKey(elliptic.P256(), opts.VerificationMethod.Value[2:])
if errEc != nil {
return nil, nil, nil, errors.Join(errors.New("failed to unmarshal EC key"), errEc)
}

finalKey.Type = kms.ECDSAP256TypeIEEEP1363
finalKey.BytesKey = &pubkey.BytesKey{Bytes: pubKey}
}

if finalKey.JWK == nil && finalKey.BytesKey == nil {
return nil, nil, nil, errors.New("verification method needs JWK")
}

if finalKey.JWK != nil {
switch finalKey.JWK.Crv {
case "P-256":
h = sha256.New()
verifier = s.p256Verifier
finalKey.Type = kms.ECDSAP256TypeIEEEP1363
case "P-384":
h = sha512.New384()
verifier = s.p384Verifier
finalKey.Type = kms.ECDSAP384TypeIEEEP1363
default:
return nil, nil, nil, errors.New("unsupported ECDSA curve")
}
}

confData := proofConfig(docData[ldCtxKey], opts)
Expand All @@ -272,7 +323,7 @@ func (s *Suite) transformAndHash(doc []byte, opts *models.ProofOptions) ([]byte,

docHash := hashData(canonDoc, canonConf, h)

return docHash, &pubkey.PublicKey{Type: keyType, JWK: vmKey}, verifier, nil
return docHash, finalKey, verifier, nil
}

// VerifyProof implements the ecdsa-2019 cryptographic suite for CheckJWTProof Proof:
Expand Down Expand Up @@ -328,10 +379,13 @@ func proofConfig(docCtx interface{}, opts *models.ProofOptions) map[string]inter
"type": models.DataIntegrityProof,
"cryptosuite": opts.SuiteType,
"verificationMethod": opts.VerificationMethodID,
"created": opts.Created.Format(models.DateTimeFormat),
"proofPurpose": opts.Purpose,
}

if !opts.Created.IsZero() {
proof["created"] = opts.Created.Format(models.DateTimeFormat)
}

if opts.Challenge != "" {
proof["challenge"] = opts.Challenge
}
Expand Down
14 changes: 1 addition & 13 deletions dataintegrity/suite/ecdsa2019/ecdsa2019_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,19 +279,7 @@ func TestSharedFailures(t *testing.T) {

testSign(t, tc)
})

t.Run("no jwk in vm", func(t *testing.T) {
tc := successCase(t)

tc.proofOpts.VerificationMethod = &did.VerificationMethod{
ID: tc.proofOpts.VerificationMethodID,
Value: []byte(fooBar),
}
tc.errStr = "verification method needs JWK"

testSign(t, tc)
})


t.Run("unsupported ECDSA curve", func(t *testing.T) {
tc := successCase(t)

Expand Down
5 changes: 4 additions & 1 deletion dataintegrity/suite/eddsa2022/eddsa2022.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,13 @@ func proofConfig(docCtx interface{}, opts *models.ProofOptions) map[string]inter
"type": models.DataIntegrityProof,
"cryptosuite": suiteType,
"verificationMethod": opts.VerificationMethodID,
"created": opts.Created.Format(models.DateTimeFormat),
"proofPurpose": opts.Purpose,
}

if !opts.Created.IsZero() {
proof["created"] = opts.Created.Format(models.DateTimeFormat)
}

if opts.Challenge != "" {
proof["challenge"] = opts.Challenge
}
Expand Down
2 changes: 1 addition & 1 deletion dataintegrity/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func (v *Verifier) VerifyProof(doc []byte, opts *models.ProofOptions) error { //
return ErrMalformedProof
}

if opts.Created.IsZero() {
if opts.Created.IsZero() && proof.Created != "" {
var parsedCreatedTime time.Time

parsedCreatedTime, err = time.Parse(models.DateTimeFormat, proof.Created)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ require (
github.com/tidwall/gjson v1.14.3
github.com/tidwall/sjson v1.1.4
github.com/trustbloc/bbs-signature-go v1.0.2
github.com/trustbloc/did-go v1.3.1-0.20241021165331-5721a3ff7396
github.com/trustbloc/did-go v1.3.1-0.20241122115441-c010226da580
github.com/trustbloc/kms-go v1.1.2
github.com/veraison/go-cose v1.1.1-0.20240126165338-2300d5c96dbd
github.com/xeipuuv/gojsonschema v1.2.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ github.com/tidwall/sjson v1.1.4 h1:bTSsPLdAYF5QNLSwYsKfBKKTnlGbIuhqL3CpRsjzGhg=
github.com/tidwall/sjson v1.1.4/go.mod h1:wXpKXu8CtDjKAZ+3DrKY5ROCorDFahq8l0tey/Lx1fg=
github.com/trustbloc/bbs-signature-go v1.0.2 h1:gepEsbLiZHv/vva9FKG5gF38mGtOIyGez7desZxiI1o=
github.com/trustbloc/bbs-signature-go v1.0.2/go.mod h1:xYotcXHAbcE0TO+SteW0J6XI3geQaXq4wdnXR2k+XCU=
github.com/trustbloc/did-go v1.3.1-0.20241021165331-5721a3ff7396 h1:z9x5gLgDeUtcPUS8uQgHD/KQ/PL5VK2QV9oENYjsWbU=
github.com/trustbloc/did-go v1.3.1-0.20241021165331-5721a3ff7396/go.mod h1:L5m4TVlPwe7VN5FRrANPMg6EJN8wIlthC8CvossDZVI=
github.com/trustbloc/did-go v1.3.1-0.20241122115441-c010226da580 h1:ZCRVqTJfEZD33IvHcTYFI3M00UdYzn9mdAeCuqOvR/c=
github.com/trustbloc/did-go v1.3.1-0.20241122115441-c010226da580/go.mod h1:vD37dDNNfeVci/vJpicD1A4vhF9HcdQVB1kNGKu0rQ4=
github.com/trustbloc/json-gold v0.5.1 h1:0HHf0ildMnN4rUr7Rgxwnm1CO116JoGMrgoWIFngM1U=
github.com/trustbloc/json-gold v0.5.1/go.mod h1:RVhE35veDX19r5gfUAR+IYHkAUuPwJO8Ie/qVeFaIzw=
github.com/trustbloc/kms-go v1.1.2 h1:nAlhDoHkSyX1eQFRz/sJsdgmJuNadyX7FJEy/9ROwys=
Expand Down
3 changes: 1 addition & 2 deletions verifiable/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,8 +400,7 @@ const SchemaTemplateV2 = `{
"required": [
"type",
"proofPurpose",
"verificationMethod",
"created"
"verificationMethod"
],
"additionalProperties": true
},
Expand Down
33 changes: 33 additions & 0 deletions verifiable/credential_ldp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@ package verifiable
import (
"crypto/ed25519"
"crypto/sha256"
_ "embed"
"encoding/base64"
"encoding/json"
"net/http"
"testing"

"github.com/btcsuite/btcutil/base58"
"github.com/google/uuid"
jsonld "github.com/piprate/json-gold/ld"
"github.com/stretchr/testify/require"
"github.com/trustbloc/did-go/method/web"
vdrpkg "github.com/trustbloc/did-go/vdr"

"github.com/trustbloc/vc-go/crypto-ext/testutil"
"github.com/trustbloc/vc-go/dataintegrity"
"github.com/trustbloc/vc-go/dataintegrity/suite/ecdsa2019"
"github.com/trustbloc/vc-go/dataintegrity/suite/eddsa2022"
"github.com/trustbloc/vc-go/internal/testutil/kmscryptoutil"
"github.com/trustbloc/vc-go/proof/creator"
"github.com/trustbloc/vc-go/proof/defaults"
Expand Down Expand Up @@ -1156,3 +1164,28 @@ func TestCredential_AddLinkedDataProof(t *testing.T) {
r.Equal(rootCapability, capabilities[0])
})
}

//go:embed testdata/example-uscis.json
var exampleUscis []byte

func TestIntegrationCred(t *testing.T) {
vdr := vdrpkg.New(vdrpkg.WithVDR(web.New()))

docLoaded := jsonld.NewDefaultDocumentLoader(http.DefaultClient)
verifier, err := dataintegrity.NewVerifier(&dataintegrity.Options{
DIDResolver: vdr,
}, eddsa2022.NewVerifierInitializer(&eddsa2022.VerifierInitializerOptions{
LDDocumentLoader: docLoaded,
}), ecdsa2019.NewVerifierInitializer(&ecdsa2019.VerifierInitializerOptions{
LDDocumentLoader: docLoaded,
}))
require.NoError(t, err)

resp, err := ParseCredential(exampleUscis,
WithJSONLDDocumentLoader(jsonld.NewDefaultDocumentLoader(http.DefaultClient)),
WithDataIntegrityVerifier(verifier),
)

require.NoError(t, err)
require.NotNil(t, resp)
}
75 changes: 75 additions & 0 deletions verifiable/testdata/example-uscis.json

Large diffs are not rendered by default.

0 comments on commit cb8385b

Please sign in to comment.