Repository: hyperledger/aries-framework-go
Pull request #3513 (2023-02-03 21:21:13)

chore: Add audience option to issuer
Included:
- resolve merge conflict in unit-tests between Filip's id PR and add SD-JWT to presexch PR
- add audience option to issuer
- remove irrelevant to-do(s)
- remove one integration test (was replaced by NewFromVC)
- add sha-384 to integration tests

Signed-off-by: Sandra Vrtikapa <sandra.vrtikapa@securekey.com>



codecov[bot]: # [Codecov](https://codecov.io/gh/hyperledger/aries-framework-go/pull/3513?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger) Report
> Merging [#3513](https://codecov.io/gh/hyperledger/aries-framework-go/pull/3513?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger) (e3153f1) into [main](https://codecov.io/gh/hyperledger/aries-framework-go/commit/3cb064ce98c64728528d602585ffe44947c5ee0f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger) (3cb064c) will **increase** coverage by `0.00%`.
> The diff coverage is `86.00%`.

```diff
@@           Coverage Diff           @@
##             main    #3513   +/-   ##
=======================================
  Coverage   87.70%   87.71%           
=======================================
  Files         351      351           
  Lines       48204    48281   +77     
=======================================
+ Hits        42279    42348   +69     
- Misses       4391     4396    +5     
- Partials     1534     1537    +3     
```


| [Impacted Files](https://codecov.io/gh/hyperledger/aries-framework-go/pull/3513?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger) | Coverage Δ | |
|---|---|---|
| [pkg/doc/presexch/definition.go](https://codecov.io/gh/hyperledger/aries-framework-go/pull/3513?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger#diff-cGtnL2RvYy9wcmVzZXhjaC9kZWZpbml0aW9uLmdv) | `85.41% <84.44%> (-0.07%)` | :arrow_down: |
| [pkg/doc/sdjwt/issuer/issuer.go](https://codecov.io/gh/hyperledger/aries-framework-go/pull/3513?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger#diff-cGtnL2RvYy9zZGp3dC9pc3N1ZXIvaXNzdWVyLmdv) | `92.27% <100.00%> (+0.14%)` | :arrow_up: |
| [pkg/doc/verifiable/jws.go](https://codecov.io/gh/hyperledger/aries-framework-go/pull/3513?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger#diff-cGtnL2RvYy92ZXJpZmlhYmxlL2p3cy5nbw==) | `79.06% <100.00%> (ø)` | |
| [.../tinkcrypto/primitive/secp256k1/subtle/encoding.go](https://codecov.io/gh/hyperledger/aries-framework-go/pull/3513?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger#diff-cGtnL2NyeXB0by90aW5rY3J5cHRvL3ByaW1pdGl2ZS9zZWNwMjU2azEvc3VidGxlL2VuY29kaW5nLmdv) | `61.01% <0.00%> (+5.08%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger)


Patch:
diff --git a/pkg/doc/sdjwt/issuer/issuer.go b/pkg/doc/sdjwt/issuer/issuer.go
index b27b8161..f580880c 100644
--- a/pkg/doc/sdjwt/issuer/issuer.go
+++ b/pkg/doc/sdjwt/issuer/issuer.go
@@ -44,9 +44,10 @@ type Claims jwt.Claims
 
 // newOpts holds options for creating new SD-JWT.
 type newOpts struct {
-	Subject string
-	JTI     string
-	ID      string
+	Subject  string
+	Audience string
+	JTI      string
+	ID       string
 
 	Expiry    *jwt.NumericDate
 	NotBefore *jwt.NumericDate
@@ -89,6 +90,13 @@ func WithIssuedAt(issuedAt *jwt.NumericDate) NewOpt {
 	}
 }
 
+// WithAudience is an option for SD-JWT payload.
+func WithAudience(audience string) NewOpt {
+	return func(opts *newOpts) {
+		opts.Audience = audience
+	}
+}
+
 // WithExpiry is an option for SD-JWT payload.
 func WithExpiry(expiry *jwt.NumericDate) NewOpt {
 	return func(opts *newOpts) {
@@ -277,6 +285,7 @@ func createPayload(issuer string, nOpts *newOpts) *payload {
 		JTI:       nOpts.JTI,
 		ID:        nOpts.ID,
 		Subject:   nOpts.Subject,
+		Audience:  nOpts.Audience,
 		IssuedAt:  nOpts.IssuedAt,
 		Expiry:    nOpts.Expiry,
 		NotBefore: nOpts.NotBefore,
@@ -464,18 +473,21 @@ func keyExistsInMap(key string, claims map[string]interface{}) bool {
 
 // payload represents SD-JWT payload.
 type payload struct {
-	Issuer  string `json:"iss,omitempty"`
-	Subject string `json:"sub,omitempty"`
-	ID      string `json:"id,omitempty"`
-	JTI     string `json:"jti,omitempty"`
-
+	// registered claim names
+	Issuer    string           `json:"iss,omitempty"`
+	Subject   string           `json:"sub,omitempty"`
+	Audience  string           `json:"aud,omitempty"`
+	JTI       string           `json:"jti,omitempty"`
 	Expiry    *jwt.NumericDate `json:"exp,omitempty"`
 	NotBefore *jwt.NumericDate `json:"nbf,omitempty"`
 	IssuedAt  *jwt.NumericDate `json:"iat,omitempty"`
 
-	CNF map[string]interface{} `json:"cnf,omitempty"`
+	// non-registered name that can be used for claims based holder binding
+	ID string `json:"id,omitempty"`
 
-	SDAlg string `json:"_sd_alg,omitempty"`
+	// SD-JWT specific
+	CNF   map[string]interface{} `json:"cnf,omitempty"`
+	SDAlg string                 `json:"_sd_alg,omitempty"`
 }
 
 type unsecuredJWTSigner struct{}


Test patch:
diff --git a/pkg/doc/presexch/definition_test.go b/pkg/doc/presexch/definition_test.go
index 4961b3e7..ac8e9c0c 100644
--- a/pkg/doc/presexch/definition_test.go
+++ b/pkg/doc/presexch/definition_test.go
@@ -522,7 +522,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) {
 
 		require.Len(t, vc.SDJWTDisclosures, 3)
 
-		require.Len(t, vc.Subject.([]verifiable.Subject)[0].CustomFields["_sd"].([]interface{}), 7)
+		require.Len(t, vc.Subject.([]verifiable.Subject)[0].CustomFields["_sd"].([]interface{}), 6)
 		require.NotNil(t, vc.Subject.([]verifiable.Subject)[0].CustomFields["address"])
 
 		_, ok = vc.Subject.([]verifiable.Subject)[0].CustomFields["email"]
@@ -593,7 +593,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) {
 
 		require.Len(t, vc.SDJWTDisclosures, 3)
 
-		require.Len(t, vc.Subject.([]verifiable.Subject)[0].CustomFields["_sd"].([]interface{}), 7)
+		require.Len(t, vc.Subject.([]verifiable.Subject)[0].CustomFields["_sd"].([]interface{}), 6)
 		require.NotNil(t, vc.Subject.([]verifiable.Subject)[0].CustomFields["address"])
 
 		_, ok = vc.Subject.([]verifiable.Subject)[0].CustomFields["email"]
@@ -652,7 +652,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) {
 		// there is only one non-SD claim path is in the fields array - hence no selective disclosures
 		require.Len(t, vc.SDJWTDisclosures, 0)
 
-		require.Len(t, vc.Subject.([]verifiable.Subject)[0].CustomFields["_sd"].([]interface{}), 7)
+		require.Len(t, vc.Subject.([]verifiable.Subject)[0].CustomFields["_sd"].([]interface{}), 6)
 
 		displayVC, err := vc.CreateDisplayCredential(verifiable.DisplayAllDisclosures())
 		require.NoError(t, err)
@@ -705,9 +705,9 @@ func TestPresentationDefinition_CreateVP(t *testing.T) {
 		vc, ok := vp.Credentials()[0].(*verifiable.Credential)
 		require.True(t, ok)
 
-		require.Len(t, vc.SDJWTDisclosures, 11)
+		require.Len(t, vc.SDJWTDisclosures, 10)
 
-		require.Len(t, vc.Subject.([]verifiable.Subject)[0].CustomFields["_sd"].([]interface{}), 7)
+		require.Len(t, vc.Subject.([]verifiable.Subject)[0].CustomFields["_sd"].([]interface{}), 6)
 		require.NotNil(t, vc.Subject.([]verifiable.Subject)[0].CustomFields["address"])
 
 		_, ok = vc.Subject.([]verifiable.Subject)[0].CustomFields["email"]
diff --git a/pkg/doc/sdjwt/integration_test.go b/pkg/doc/sdjwt/integration_test.go
index 2a334e4c..92a1ff3d 100644
--- a/pkg/doc/sdjwt/integration_test.go
+++ b/pkg/doc/sdjwt/integration_test.go
@@ -19,10 +19,8 @@ import (
 	"github.com/go-jose/go-jose/v3/jwt"
 	"github.com/stretchr/testify/require"
 
-	afjose "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"
 	"github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/holder"
 	"github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/issuer"
 	"github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/verifier"
@@ -69,7 +67,6 @@ func TestSDJWTFlow(t *testing.T) {
 
 		printObject(t, "Simple Claims:", simpleClaimsFlatOption)
 
-		// TODO: Should we have one call instead of two (designed based on JWT)
 		combinedFormatForIssuance, err := token.Serialize(false)
 		r.NoError(err)
 
@@ -218,7 +215,8 @@ func TestSDJWTFlow(t *testing.T) {
 		complexClaims := createComplexClaims()
 
 		// Issuer will issue SD-JWT for specified claims. We will use structured(nested) claims in this test.
-		token, err := issuer.New(testIssuer, complexClaims, nil, signer)
+		token, err := issuer.New(testIssuer, complexClaims, nil, signer,
+			issuer.WithHashAlgorithm(crypto.SHA384))
 		r.NoError(err)
 
 		var flatClaims map[string]interface{}
@@ -260,116 +258,6 @@ func TestSDJWTFlow(t *testing.T) {
 		printObject(t, "Verified Claims", verifiedClaims)
 	})
 
-	// TODO: This test will be deleted; you should use NewFromVC api for creating VC
-	t.Run("success - create VC plus holder binding", func(t *testing.T) {
-		holderPublicKey, holderPrivateKey, err := ed25519.GenerateKey(rand.Reader)
-		r.NoError(err)
-
-		holderPublicJWK, err := jwksupport.JWKFromKey(holderPublicKey)
-		require.NoError(t, err)
-
-		credentialSubject := map[string]interface{}{
-			"degree": map[string]interface{}{
-				"degree": "MIT",
-				"type":   "BachelorDegree",
-			},
-			"name":   "Jayden Doe",
-			"spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1",
-		}
-
-		// Note: if issuer can be empty; should I add it as an option then
-		// All reference apps have it as part of call
-		token, err := issuer.New("", credentialSubject, nil, &unsecuredJWTSigner{},
-			issuer.WithID("did:example:ebfeb1f712ebc6f1c276e12ec21"),
-			issuer.WithHolderPublicKey(holderPublicJWK))
-		r.NoError(err)
-
-		credSubjectCFI, err := token.Serialize(false)
-		r.NoError(err)
-
-		cfi := common.ParseCombinedFormatForIssuance(credSubjectCFI)
-
-		var selectiveCredentialSubject map[string]interface{}
-		err = token.DecodeClaims(&selectiveCredentialSubject)
-		r.NoError(err)
-
-		printObject(t, "Selective Credential Subject", selectiveCredentialSubject)
-
-		// create VC - we will use template here
-		var vc map[string]interface{}
-		err = json.Unmarshal([]byte(sampleVC), &vc)
-		r.NoError(err)
-
-		const credentialSubjectKey = "credentialSubject"
-		const vcKey = "vc"
-
-		// move _sd_alg key from credential subject to vc as per example 4 in spec
-		vc[vcKey].(map[string]interface{})[common.SDAlgorithmKey] = selectiveCredentialSubject[common.SDAlgorithmKey]
-		delete(selectiveCredentialSubject, common.SDAlgorithmKey)
-
-		// move cnf key from credential subject to vc as per example 4 in spec
-		cnfObj, ok := selectiveCredentialSubject[common.CNFKey]
-		if ok {
-			vc[vcKey].(map[string]interface{})[common.CNFKey] = cnfObj
-			delete(selectiveCredentialSubject, common.CNFKey)
-		}
-
-		// update VC with 'selective' credential subject
-		vc[vcKey].(map[string]interface{})[credentialSubjectKey] = selectiveCredentialSubject
-
-		// sign VC with 'selective' credential subject
-		signedJWT, err := afjwt.NewSigned(vc, nil, signer)
-		r.NoError(err)
-
-		sdJWT := &issuer.SelectiveDisclosureJWT{Disclosures: cfi.Disclosures, SignedJWT: signedJWT}
-
-		// create combined format for issuance for VC
-		vcCombinedFormatForIssuance, err := sdJWT.Serialize(false)
-		r.NoError(err)
-
-		fmt.Println(fmt.Sprintf("issuer SD-JWT: %s", vcCombinedFormatForIssuance))
-
-		// Holder will parse combined format for issuance and hold on to that
-		// combined format for issuance and the claims that can be selected.
-		claims, err := holder.Parse(vcCombinedFormatForIssuance, holder.WithSignatureVerifier(signatureVerifier))
-		r.NoError(err)
-
-		printObject(t, "Holder Claims", claims)
-
-		r.Equal(3, len(claims))
-
-		const testAudience = "https://test.com/verifier"
-		const testNonce = "nonce"
-
-		holderSigner := afjwt.NewEd25519Signer(holderPrivateKey)
-
-		selectedDisclosures := getDisclosuresFromClaimNames([]string{"given_name", "email", "street_address"}, claims)
-
-		// Holder will disclose only sub-set of claims to verifier.
-		combinedFormatForPresentation, err := holder.CreatePresentation(vcCombinedFormatForIssuance, selectedDisclosures,
-			holder.WithHolderBinding(&holder.BindingInfo{
-				Payload: holder.BindingPayload{
-					Nonce:    testNonce,
-					Audience: testAudience,
-					IssuedAt: jwt.NewNumericDate(time.Now()),
-				},
-				Signer: holderSigner,
-			}))
-		r.NoError(err)
-
-		fmt.Println(fmt.Sprintf("holder SD-JWT: %s", combinedFormatForPresentation))
-
-		// Verifier will validate combined format for presentation and create verified claims.
-		// In this case it will be VC since VC was passed in.
-		verifiedClaims, err := verifier.Parse(combinedFormatForPresentation,
-			verifier.WithSignatureVerifier(signatureVerifier))
-		r.NoError(err)
-
-		printObject(t, "Verified Claims", verifiedClaims)
-
-		r.Equal(len(vc), len(verifiedClaims))
-	})
-
 	t.Run("success - NewFromVC API", func(t *testing.T) {
 		holderPublicKey, holderPrivateKey, err := ed25519.GenerateKey(rand.Reader)
 		r.NoError(err)
@@ -441,18 +329,6 @@ func TestSDJWTFlow(t *testing.T) {
 	})
 }
 
-type unsecuredJWTSigner struct{}
-
-func (s unsecuredJWTSigner) Sign(_ []byte) ([]byte, error) {
-	return []byte(""), nil
-}
-
-func (s unsecuredJWTSigner) Headers() afjose.Headers {
-	return map[string]interface{}{
-		afjose.HeaderAlgorithm: afjwt.AlgorithmNone,
-	}
-}
-
 func createComplexClaims() map[string]interface{} {
 	claims := map[string]interface{}{
 		"sub":          "john_doe_42",
@@ -518,28 +394,6 @@ func prettyPrint(msg []byte) (string, error) {
 	return prettyJSON.String(), nil
 }
 
-const sampleVC = `
-{
-	"iat": 1673987547,
-	"iss": "did:example:76e12ec712ebc6f1c221ebfeb1f",
-	"jti": "http://example.edu/credentials/1872",
-	"nbf": 1673987547,
-	"sub": "did:example:ebfeb1f712ebc6f1c276e12ec21",
-	"vc": {
-		"@context": [
-			"https://www.w3.org/2018/credentials/v1"
-		],
-		"credentialSubject": {},
-		"first_name": "First name",
-		"id": "http://example.edu/credentials/1872",
-		"info": "Info",
-		"issuanceDate": "2023-01-17T22:32:27.468109817+02:00",
-		"issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
-		"last_name": "Last name",
-		"type": "VerifiableCredential"
-	}
-}`
-
 const sampleVCFull = `
 {
 	"iat": 1673987547,
diff --git a/pkg/doc/sdjwt/issuer/issuer_test.go b/pkg/doc/sdjwt/issuer/issuer_test.go
index 67325d26..9644d50d 100644
--- a/pkg/doc/sdjwt/issuer/issuer_test.go
+++ b/pkg/doc/sdjwt/issuer/issuer_test.go
@@ -158,6 +158,7 @@ func TestNew(t *testing.T) {
 			WithJTI("jti"),
 			WithID("id"),
 			WithSubject("subject"),
+			WithAudience("audience"),
 			WithSaltFnc(generateSalt),
 			WithJSONMarshaller(json.Marshal),
 			WithHashAlgorithm(crypto.SHA256),
@@ -177,13 +178,23 @@ func TestNew(t *testing.T) {
 		err = verifyEd25519ViaGoJose(cfi.SDJWT, pubKey, &parsedClaims)
 		r.NoError(err)
 
-		parsedClaimsBytes, err := json.Marshal(parsedClaims)
-		require.NoError(t, err)
+		printObject(t, "Parsed Claims:", parsedClaims)
 
-		prettyJSON, err := prettyPrint(parsedClaimsBytes)
-		require.NoError(t, err)
+		r.Equal(issuer, parsedClaims["iss"])
+		r.Equal("audience", parsedClaims["aud"])
+		r.Equal("subject", parsedClaims["sub"])
+		r.Equal("id", parsedClaims["id"])
+		r.Equal("jti", parsedClaims["jti"])
+		r.Equal("sha-256", parsedClaims["_sd_alg"])
 
-		fmt.Println(prettyJSON)
+		_, ok := parsedClaims["nbf"]
+		r.True(ok)
+
+		_, ok = parsedClaims["iat"]
+		r.True(ok)
+
+		_, ok = parsedClaims["exp"]
+		r.True(ok)
 
 		err = verifyEd25519(cfi.SDJWT, pubKey)
 		r.NoError(err)
diff --git a/pkg/doc/sdjwt/verifier/verifier_test.go b/pkg/doc/sdjwt/verifier/verifier_test.go
index 89a2cdd4..c227b755 100644
--- a/pkg/doc/sdjwt/verifier/verifier_test.go
+++ b/pkg/doc/sdjwt/verifier/verifier_test.go
@@ -555,7 +555,7 @@ func TestHolderBinding(t *testing.T) {
 
 		claims := make(map[string]interface{})
 		claims["cnf"] = "abc"
-		claims["_sd_alg"] = testSDAlg // TODO: Should alg be mandatory if there are no selective disclosures
+		claims["_sd_alg"] = testSDAlg
 
 		sdJWT, err := buildJWS(signer, claims)
 		r.NoError(err)