From 7ae77295d59311e296383701422fed698152df91 Mon Sep 17 00:00:00 2001 From: Sandra Vrtikapa Date: Wed, 11 Jan 2023 13:50:15 -0500 Subject: [PATCH] feat: SD-JWT Issuer: Add Holder Public Key Claim (JWK) If the Issuer wants to enable Holder Binding, it MAY include a public key associated with the Holder, or a reference thereto. We use confirmation "cnf" claim to include raw public key by value in SD-JWT. In this first iteration of "cnf" claim we will use "jwk" confirmation method and include public key information in JWK format. Other confirmation methods such as "jwe", "kid" and "jku" may be implemented at later time (if required). Signed-off-by: Sandra Vrtikapa --- pkg/doc/sdjwt/common/common.go | 2 ++ pkg/doc/sdjwt/issuer/issuer.go | 35 ++++++++++++++++++++++----- pkg/doc/sdjwt/issuer/issuer_test.go | 37 ++++++++++++++++++++++++++++- 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/pkg/doc/sdjwt/common/common.go b/pkg/doc/sdjwt/common/common.go index e35077ed3..fc218e954 100644 --- a/pkg/doc/sdjwt/common/common.go +++ b/pkg/doc/sdjwt/common/common.go @@ -41,6 +41,8 @@ type Payload struct { NotBefore *jwt.NumericDate `json:"nbf,omitempty"` IssuedAt *jwt.NumericDate `json:"iat,omitempty"` + CNF map[string]interface{} `json:"cnf,omitempty"` + SD []string `json:"_sd,omitempty"` SDAlg string `json:"_sd_alg,omitempty"` } diff --git a/pkg/doc/sdjwt/issuer/issuer.go b/pkg/doc/sdjwt/issuer/issuer.go index 209ad046c..bcf57888a 100644 --- a/pkg/doc/sdjwt/issuer/issuer.go +++ b/pkg/doc/sdjwt/issuer/issuer.go @@ -20,6 +20,7 @@ import ( "github.com/go-jose/go-jose/v3/jwt" "github.com/hyperledger/aries-framework-go/pkg/doc/jose" + "github.com/hyperledger/aries-framework-go/pkg/doc/jose/jwk" afgjwt "github.com/hyperledger/aries-framework-go/pkg/doc/jwt" "github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/common" ) @@ -48,6 +49,8 @@ type newOpts struct { NotBefore *jwt.NumericDate IssuedAt *jwt.NumericDate + HolderPublicKey *jwk.JWK + HashAlg crypto.Hash jsonMarshal func(v interface{}) ([]byte, error) @@ -108,6 +111,13 @@ func WithID(id string) NewOpt { } } +// WithHolderPublicKey is an option for SD-JWT payload. +func WithHolderPublicKey(jwk *jwk.JWK) NewOpt { + return func(opts *newOpts) { + opts.HolderPublicKey = jwk + } +} + // WithHashAlgorithm is an option for hashing disclosures. func WithHashAlgorithm(alg crypto.Hash) NewOpt { return func(opts *newOpts) { @@ -162,6 +172,23 @@ func New(issuer string, claims interface{}, headers jose.Headers, return nil, err } + payload := createPayload(issuer, digests, nOpts) + + signedJWT, err := afgjwt.NewSigned(payload, headers, signer) + if err != nil { + return nil, err + } + + return &SelectiveDisclosureJWT{Disclosures: disclosures, SignedJWT: signedJWT}, nil +} + +func createPayload(issuer string, digests []string, nOpts *newOpts) *common.Payload { + var cnf map[string]interface{} + if nOpts.HolderPublicKey != nil { + cnf = make(map[string]interface{}) + cnf["jwk"] = nOpts.HolderPublicKey + } + payload := &common.Payload{ Issuer: issuer, ID: nOpts.ID, @@ -169,16 +196,12 @@ func New(issuer string, claims interface{}, headers jose.Headers, IssuedAt: nOpts.IssuedAt, Expiry: nOpts.Expiry, NotBefore: nOpts.NotBefore, + CNF: cnf, SD: digests, SDAlg: strings.ToLower(nOpts.HashAlg.String()), } - signedJWT, err := afgjwt.NewSigned(payload, headers, signer) - if err != nil { - return nil, err - } - - return &SelectiveDisclosureJWT{Disclosures: disclosures, SignedJWT: signedJWT}, nil + return payload } func createDigests(disclosures []string, nOpts *newOpts) ([]string, error) { diff --git a/pkg/doc/sdjwt/issuer/issuer_test.go b/pkg/doc/sdjwt/issuer/issuer_test.go index 329c7f737..90ef72d2a 100644 --- a/pkg/doc/sdjwt/issuer/issuer_test.go +++ b/pkg/doc/sdjwt/issuer/issuer_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/require" "github.com/hyperledger/aries-framework-go/pkg/doc/jose" + "github.com/hyperledger/aries-framework-go/pkg/doc/jose/jwk/jwksupport" afjwt "github.com/hyperledger/aries-framework-go/pkg/doc/jwt" "github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/common" ) @@ -133,7 +134,6 @@ func TestNew(t *testing.T) { WithNotBefore(jwt.NewNumericDate(notBefore)), WithID("id"), WithSubject("subject"), - // TODO: Audience ??? WithSaltFnc(generateSalt), WithJSONMarshaller(json.Marshal), WithHashAlgorithm(crypto.SHA256), @@ -198,6 +198,41 @@ func TestNew(t *testing.T) { } }) + t.Run("Create JWS with holder public key", func(t *testing.T) { + r := require.New(t) + + pubKey, privKey, err := ed25519.GenerateKey(rand.Reader) + r.NoError(err) + + _, holderPublicKey, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + holderJWK, err := jwksupport.JWKFromKey(holderPublicKey) + require.NoError(t, err) + + token, err := New(issuer, claims, nil, afjwt.NewEd25519Signer(privKey), + WithHolderPublicKey(holderJWK)) + r.NoError(err) + combinedFormatForIssuance, err := token.Serialize(false) + require.NoError(t, err) + + cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance) + require.Equal(t, 1, len(cfi.Disclosures)) + + var parsedClaims map[string]interface{} + err = verifyEd25519ViaGoJose(cfi.SDJWT, pubKey, &parsedClaims) + r.NoError(err) + require.NotEmpty(t, parsedClaims["cnf"]) + + parsedClaimsBytes, err := json.Marshal(parsedClaims) + require.NoError(t, err) + + prettyJSON, err := prettyPrint(parsedClaimsBytes) + require.NoError(t, err) + + fmt.Println(prettyJSON) + }) + t.Run("error - create decoy disclosures failed", func(t *testing.T) { r := require.New(t)