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)