Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

Commit

Permalink
chore: Add audience option to issuer (#3513)
Browse files Browse the repository at this point in the history
Add audience option to issuer, remove irrelevant to-do(s), remove one integration test, add sha-384 to integration tests.

Signed-off-by: Sandra Vrtikapa <[email protected]>
  • Loading branch information
sandrask authored Feb 3, 2023
1 parent be9f5b2 commit cd83a44
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 169 deletions.
10 changes: 5 additions & 5 deletions pkg/doc/presexch/definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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"]
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"]
Expand Down
150 changes: 2 additions & 148 deletions pkg/doc/sdjwt/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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{}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down
32 changes: 22 additions & 10 deletions pkg/doc/sdjwt/issuer/issuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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{}
Expand Down
21 changes: 16 additions & 5 deletions pkg/doc/sdjwt/issuer/issuer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion pkg/doc/sdjwt/verifier/verifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit cd83a44

Please sign in to comment.