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

Commit

Permalink
fix: Move cnf and _sd_alg to vc level
Browse files Browse the repository at this point in the history
Move cnf and _sd_alg from credential subject level to vc level

Closes #3494

Signed-off-by: Sandra Vrtikapa <[email protected]>
  • Loading branch information
sandrask committed Jan 24, 2023
1 parent e159c5b commit 1dc5943
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 85 deletions.
34 changes: 7 additions & 27 deletions pkg/doc/sdjwt/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,8 @@ func GetSDAlg(claims map[string]interface{}) (string, error) {

obj, ok := claims[SDAlgorithmKey]
if !ok {
// if claims contain 'vc' claim it may be present in credential subject
obj, ok = GetKeyFromCredentialSubject(SDAlgorithmKey, claims)
// if claims contain 'vc' claim it may be present in vc
obj, ok = GetKeyFromVC(SDAlgorithmKey, claims)
if !ok {
return "", fmt.Errorf("%s must be present in SD-JWT", SDAlgorithmKey)
}
Expand All @@ -287,28 +287,8 @@ func GetSDAlg(claims map[string]interface{}) (string, error) {
return alg, nil
}

// GetKeyFromCredentialSubject returns key value from VC credential subject.
func GetKeyFromCredentialSubject(key string, claims map[string]interface{}) (interface{}, bool) {
csObj, ok := GetCredentialSubject(claims)
if !ok {
return nil, false
}

cs, ok := csObj.(map[string]interface{})
if !ok {
return nil, false
}

obj, ok := cs[key]
if !ok {
return nil, false
}

return obj, true
}

// GetCredentialSubject returns credential subject from vc.
func GetCredentialSubject(claims map[string]interface{}) (interface{}, bool) {
// GetKeyFromVC returns key value from VC.
func GetKeyFromVC(key string, claims map[string]interface{}) (interface{}, bool) {
vcObj, ok := claims["vc"]
if !ok {
return nil, false
Expand All @@ -319,19 +299,19 @@ func GetCredentialSubject(claims map[string]interface{}) (interface{}, bool) {
return nil, false
}

csObj, ok := vc["credentialSubject"]
obj, ok := vc[key]
if !ok {
return nil, false
}

return csObj, true
return obj, true
}

// GetCNF returns confirmation claim 'cnf'.
func GetCNF(claims map[string]interface{}) (map[string]interface{}, error) {
obj, ok := claims[CNFKey]
if !ok {
obj, ok = GetKeyFromCredentialSubject(CNFKey, claims)
obj, ok = GetKeyFromVC(CNFKey, claims)
if !ok {
return nil, fmt.Errorf("%s must be present in SD-JWT", CNFKey)
}
Expand Down
66 changes: 15 additions & 51 deletions pkg/doc/sdjwt/common/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,9 +463,7 @@ func TestGetSDAlg(t *testing.T) {
claims := map[string]interface{}{
"given_name": "John",
"vc": map[string]interface{}{
"credentialSubject": map[string]interface{}{
"_sd_alg": "sha-256",
},
"_sd_alg": "sha-256",
},
}

Expand All @@ -474,21 +472,17 @@ func TestGetSDAlg(t *testing.T) {
r.Equal("sha-256", alg)
})

t.Run("error - algorithm not found (empty claims)", func(t *testing.T) {
t.Run("error - algorithm not found (no vc)", func(t *testing.T) {
alg, err := GetSDAlg(make(map[string]interface{}))
r.Error(err)
r.Empty(alg)

r.Contains(err.Error(), "_sd_alg must be present in SD-JWT")
})

t.Run("error - algorithm not found", func(t *testing.T) {
t.Run("error - algorithm not found (vc is empty)", func(t *testing.T) {
claims := map[string]interface{}{
"vc": map[string]interface{}{
"credentialSubject": map[string]interface{}{
"given_name": "John",
},
},
"vc": map[string]interface{}{},
}

alg, err := GetSDAlg(claims)
Expand All @@ -498,7 +492,7 @@ func TestGetSDAlg(t *testing.T) {
r.Contains(err.Error(), "_sd_alg must be present in SD-JWT")
})

t.Run("error - algorithm not found (vc claim is not a map)", func(t *testing.T) {
t.Run("error - algorithm not found (vc is not a map)", func(t *testing.T) {
claims := map[string]interface{}{
"vc": "invalid",
}
Expand All @@ -510,40 +504,10 @@ func TestGetSDAlg(t *testing.T) {
r.Contains(err.Error(), "_sd_alg must be present in SD-JWT")
})

t.Run("error - algorithm not found (no credential subject in vc)", func(t *testing.T) {
claims := map[string]interface{}{
"vc": map[string]interface{}{
"id": "test-id",
},
}

alg, err := GetSDAlg(claims)
r.Error(err)
r.Empty(alg)

r.Contains(err.Error(), "_sd_alg must be present in SD-JWT")
})

t.Run("error - algorithm not found (credential subject is not a map)", func(t *testing.T) {
claims := map[string]interface{}{
"vc": map[string]interface{}{
"credentialSubject": "invalid",
},
}

alg, err := GetSDAlg(claims)
r.Error(err)
r.Empty(alg)

r.Contains(err.Error(), "_sd_alg must be present in SD-JWT")
})

t.Run("error - algorithm must be a string", func(t *testing.T) {
claims := map[string]interface{}{
"vc": map[string]interface{}{
"credentialSubject": map[string]interface{}{
"_sd_alg": 123,
},
"_sd_alg": 123,
},
}

Expand Down Expand Up @@ -585,7 +549,7 @@ func TestGetCNF(t *testing.T) {
r.NotEmpty(cnf["jwk"])
})

t.Run("success - cnf is in VC credential subject", func(t *testing.T) {
t.Run("success - cnf is in VC", func(t *testing.T) {
var payload map[string]interface{}

err := json.Unmarshal([]byte(vcSample), &payload)
Expand Down Expand Up @@ -662,16 +626,16 @@ const vcSample = `
"goPn0hokFnQBktqzXxgTK-4CCldmLjlRwUVCIltDyRg",
"FAiNODIxDMwGTljNYcVKkx7LBsr1pb-U6XuAfVFuOGY"
],
"_sd_alg": "sha-256",
"cnf": {
"jwk": {
"crv": "Ed25519",
"kty": "OKP",
"x": "7jtkxxk0Pb3E0O6JXJiN8HyIp2DpCiqaHCWfMXl9ZFo"
}
},
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21"
},
"_sd_alg": "sha-256",
"cnf": {
"jwk": {
"crv": "Ed25519",
"kty": "OKP",
"x": "7jtkxxk0Pb3E0O6JXJiN8HyIp2DpCiqaHCWfMXl9ZFo"
}
},
"first_name": "First name",
"id": "http://example.edu/credentials/1872",
"info": "Info",
Expand Down
5 changes: 3 additions & 2 deletions pkg/doc/sdjwt/holder/holder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func TestParse(t *testing.T) {
claims, err := Parse(vcCombinedFormatForIssuance, WithSignatureVerifier(&NoopSignatureVerifier{}))
r.NoError(err)
require.NotNil(t, claims)
require.Equal(t, 3, len(claims))
require.Equal(t, 4, len(claims))
})

t.Run("success - complex claims", func(t *testing.T) {
Expand Down Expand Up @@ -290,4 +290,5 @@ const additionalDisclosure = `WyIzanFjYjY3ejl3a3MwOHp3aUs3RXlRIiwgImdpdmVuX25hbW
// nolint: lll
const specSDJWT = `eyJhbGciOiAiUlMyNTYiLCAia2lkIjogImNBRUlVcUowY21MekQxa3pHemhlaUJhZzBZUkF6VmRsZnhOMjgwTmdIYUEifQ.eyJfc2QiOiBbIk5ZQ29TUktFWXdYZHBlNXlkdUpYQ3h4aHluRVU4ei1iNFR5TmlhcDc3VVkiLCAiU1k4bjJCYmtYOWxyWTNleEhsU3dQUkZYb0QwOUdGOGE5Q1BPLUc4ajIwOCIsICJUUHNHTlBZQTQ2d21CeGZ2MnpuT0poZmRvTjVZMUdrZXpicGFHWkNUMWFjIiwgIlprU0p4eGVHbHVJZFlCYjdDcWtaYkpWbTB3MlY1VXJSZU5UekFRQ1lCanciLCAibDlxSUo5SlRRd0xHN09MRUlDVEZCVnhtQXJ3OFBqeTY1ZEQ2bXRRVkc1YyIsICJvMVNBc0ozM1lNaW9POXBYNVZlQU0xbHh1SEY2aFpXMmtHZGtLS0JuVmxvIiwgInFxdmNxbmN6QU1nWXg3RXlrSTZ3d3RzcHl2eXZLNzkwZ2U3TUJiUS1OdXMiXSwgImlzcyI6ICJodHRwczovL2V4YW1wbGUuY29tL2lzc3VlciIsICJpYXQiOiAxNTE2MjM5MDIyLCAiZXhwIjogMTUxNjI0NzAyMiwgIl9zZF9hbGciOiAic2hhLTI1NiIsICJjbmYiOiB7Imp3ayI6IHsia3R5IjogIlJTQSIsICJuIjogInBtNGJPSEJnLW9ZaEF5UFd6UjU2QVdYM3JVSVhwMTFfSUNEa0dnUzZXM1pXTHRzLWh6d0kzeDY1NjU5a2c0aFZvOWRiR29DSkUzWkdGX2VhZXRFMzBVaEJVRWdwR3dyRHJRaUo5enFwcm1jRmZyM3F2dmtHanR0aDhaZ2wxZU0yYkpjT3dFN1BDQkhXVEtXWXMxNTJSN2c2SmcyT1ZwaC1hOHJxLXE3OU1oS0c1UW9XX21UejEwUVRfNkg0YzdQaldHMWZqaDhocFdObmJQX3B2NmQxelN3WmZjNWZsNnlWUkwwRFYwVjNsR0hLZTJXcWZfZU5HakJyQkxWa2xEVGs4LXN0WF9NV0xjUi1FR21YQU92MFVCV2l0U19kWEpLSnUtdlhKeXcxNG5IU0d1eFRJSzJoeDFwdHRNZnQ5Q3N2cWltWEtlRFRVMTRxUUwxZUU3aWhjdyIsICJlIjogIkFRQUIifX19.xqgKrDO6dK_oBL3fiqdcq_elaIGxM6Z-RyuysglGyddR1O1IiE3mIk8kCpoqcRLR88opkVWN2392K_XYfAuAmeT9kJVisD8ZcgNcv-MQlWW9s8WaViXxBRe7EZWkWRQcQVR6jf95XZ5H2-_KA54POq3L42xjk0y5vDr8yc08Reak6vvJVvjXpp-Wk6uxsdEEAKFspt_EYIvISFJhfTuQqyhCjnaW13X312MSQBPwjbHn74ylUqVLljDvqcemxeqjh42KWJq4C3RqNJ7anA2i3FU1kB4-KNZWsijY7-op49iL7BrnIBxdlAMrbHEkoGTbFWdl7Ki17GHtDxxa1jaxQg~WyJkcVR2WE14UzBHYTNEb2FHbmU5eDBRIiwgInN1YiIsICJqb2huX2RvZV80MiJd~WyIzanFjYjY3ejl3a3MwOHp3aUs3RXlRIiwgImdpdmVuX25hbWUiLCAiSm9obiJd~WyJxUVdtakpsMXMxUjRscWhFTkxScnJ3IiwgImZhbWlseV9uYW1lIiwgIkRvZSJd~WyJLVXhTNWhFX1hiVmFjckdBYzdFRnd3IiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ~WyIzcXZWSjFCQURwSERTUzkzOVEtUml3IiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ~WyIweEd6bjNNaXFzY3RaSV9PcERsQWJRIiwgImFkZHJlc3MiLCB7InN0cmVldF9hZGRyZXNzIjogIjEyMyBNYWluIFN0IiwgImxvY2FsaXR5IjogIkFueXRvd24iLCAicmVnaW9uIjogIkFueXN0YXRlIiwgImNvdW50cnkiOiAiVVMifV0~WyJFUktNMENOZUZKa2FENW1UWFZfWDh3IiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0`

const vcCombinedFormatForIssuance = `eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDp0cnVzdGJsb2M6YWJjI2tleTEifQ.eyJpYXQiOjE2NzM5ODc1NDcsImlzcyI6ImRpZDpleGFtcGxlOjc2ZTEyZWM3MTJlYmM2ZjFjMjIxZWJmZWIxZiIsImp0aSI6Imh0dHA6Ly9leGFtcGxlLmVkdS9jcmVkZW50aWFscy8xODcyIiwibmJmIjoxNjczOTg3NTQ3LCJzdWIiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJfc2QiOlsiVklMLXlXZHlZX3hNbk5icEJvbHRrcnlBOFZESVFVYllLd2dQaHVEUEx1ZyIsIkxDLUg0R2N4UG1OdGN5VWNiSGFDbTlEUDFnZDROYXJsZ2RiUFc4ZEVvZ2siLCJlbHVRRFVtbHpwM19naU9uRFVRVk1WLWpWM1hMNXVIck1BNzRROXI0dVF3Il0sIl9zZF9hbGciOiJzaGEtMjU2IiwiaWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEifSwiZmlyc3RfbmFtZSI6IkZpcnN0IG5hbWUiLCJpZCI6Imh0dHA6Ly9leGFtcGxlLmVkdS9jcmVkZW50aWFscy8xODcyIiwiaW5mbyI6IkluZm8iLCJpc3N1YW5jZURhdGUiOiIyMDIzLTAxLTE3VDIyOjMyOjI3LjQ2ODEwOTgxNyswMjowMCIsImlzc3VlciI6ImRpZDpleGFtcGxlOjc2ZTEyZWM3MTJlYmM2ZjFjMjIxZWJmZWIxZiIsImxhc3RfbmFtZSI6Ikxhc3QgbmFtZSIsInR5cGUiOiJWZXJpZmlhYmxlQ3JlZGVudGlhbCJ9fQ.PvRYW8-EAG7K4QQL3TV-GNF--vaYIGc3TWJrRSoc2qBCVT5sFkez7FTLv7iae24S2mi2GH5lcxy1dx75LSjOBA~WyJIb01DbEdxLUpRUUZIMUVZZnFCN1FBIiwic3BvdXNlIiwiZGlkOmV4YW1wbGU6YzI3NmUxMmVjMjFlYmZlYjFmNzEyZWJjNmYxIl0~WyI5ZDFzTUFYUEVTZkEzaTE0NDNzVTRRIiwiZGVncmVlIix7ImRlZ3JlZSI6Ik1JVCIsInR5cGUiOiJCYWNoZWxvckRlZ3JlZSJ9XQ~WyJiekpGY1pYMkYyRjE3XzVsSFU2MjF3IiwibmFtZSIsIkpheWRlbiBEb2UiXQ` // nolint: lll
// nolint: lll
const vcCombinedFormatForIssuance = `eyJhbGciOiJFZERTQSJ9.eyJpYXQiOjEuNjczOTg3NTQ3ZSswOSwiaXNzIjoiZGlkOmV4YW1wbGU6NzZlMTJlYzcxMmViYzZmMWMyMjFlYmZlYjFmIiwianRpIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzE4NzIiLCJuYmYiOjEuNjczOTg3NTQ3ZSswOSwic3ViIjoiZGlkOmV4YW1wbGU6ZWJmZWIxZjcxMmViYzZmMWMyNzZlMTJlYzIxIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiX3NkX2FsZyI6InNoYS0yNTYiLCJjbmYiOnsiandrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoiZDlYemtRbVJMQncxSXpfeHVGUmVLMUItRmpCdTdjT0N3RTlOR2F1d251SSJ9fSwiY3JlZGVudGlhbFN1YmplY3QiOnsiX3NkIjpbInBBdjJUMU10YmRXNGttUUdxT1VVRUpjQmdTZi1mSFRHV2xQVUV4aWlIbVEiLCI2dDlBRUJCQnEzalZwckJ3bGljOGhFWnNNSmxXSXhRdUw5c3ExMzJZTnYwIl0sImRlZ3JlZSI6eyJfc2QiOlsibzZzV2h4RjcxWHBvZ1cxVUxCbU90bjR1SXFGdjJ3ODF6emRuelJXdlpqYyIsIi1yRklXbU1YR3ZXX0FIYVEtODhpMy11ZzRUVjhLUTg5TjdmZmtneFc2X2MiXX0sImlkIjoiZGlkOmV4YW1wbGU6ZWJmZWIxZjcxMmViYzZmMWMyNzZlMTJlYzIxIn0sImZpcnN0X25hbWUiOiJGaXJzdCBuYW1lIiwiaWQiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMTg3MiIsImluZm8iOiJJbmZvIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0wMS0xN1QyMjozMjoyNy40NjgxMDk4MTcrMDI6MDAiLCJpc3N1ZXIiOiJkaWQ6ZXhhbXBsZTo3NmUxMmVjNzEyZWJjNmYxYzIyMWViZmViMWYiLCJsYXN0X25hbWUiOiJMYXN0IG5hbWUiLCJ0eXBlIjoiVmVyaWZpYWJsZUNyZWRlbnRpYWwifX0.GcfSA6NkONxdsm5Lxj9-988eWx1ZvMz5vJ1uh2x8UK1iKIeQLmhsWpA_34RbtAm2HnuoxW4_ZGeiHBzQ1GLTDQ~WyJFWkVDRVZ1YWVJOXhZWmlWb3VMQldBIiwidHlwZSIsIkJhY2hlbG9yRGVncmVlIl0~WyJyMno1UzZMa25FRTR3TWwteFB0VEx3IiwiZGVncmVlIiwiTUlUIl0~WyJ2VkhfaGhNQy1aSUt5WFdtdDUyOWpnIiwic3BvdXNlIiwiZGlkOmV4YW1wbGU6YzI3NmUxMmVjMjFlYmZlYjFmNzEyZWJjNmYxIl0~WyJrVzh0WVVwbVl1VmRoZktFT050TnFnIiwibmFtZSIsIkpheWRlbiBEb2UiXQ`
24 changes: 23 additions & 1 deletion pkg/doc/sdjwt/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ 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)
Expand Down Expand Up @@ -297,8 +298,22 @@ func TestSDJWTFlow(t *testing.T) {
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["vc"].(map[string]interface{})["credentialSubject"] = selectiveCredentialSubject
vc[vcKey].(map[string]interface{})[credentialSubjectKey] = selectiveCredentialSubject

// sign VC with 'selective' credential subject
signedJWT, err := afjwt.NewSigned(vc, nil, signer)
Expand Down Expand Up @@ -371,6 +386,13 @@ func TestSDJWTFlow(t *testing.T) {
issuer.WithStructuredClaims(true))
r.NoError(err)

var decoded map[string]interface{}

err = token.DecodeClaims(&decoded)
require.NoError(t, err)

printObject(t, "SD-JWT Payload", decoded)

vcCombinedFormatForIssuance, err := token.Serialize(false)
r.NoError(err)

Expand Down
18 changes: 16 additions & 2 deletions pkg/doc/sdjwt/issuer/issuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ const (

decoyMinElements = 1
decoyMaxElements = 4

credentialSubjectKey = "credentialSubject"
vcKey = "vc"
)

var mr = mathrand.New(mathrand.NewSource(time.Now().Unix())) // nolint:gochecknoglobals
Expand Down Expand Up @@ -186,7 +189,7 @@ func New(issuer string, claims interface{}, headers jose.Headers,
// NewFromVC creates new signed Selective Disclosure JWT based on vc.
func NewFromVC(vc map[string]interface{}, headers jose.Headers,
signer jose.Signer, opts ...NewOpt) (*SelectiveDisclosureJWT, error) {
csObj, ok := common.GetCredentialSubject(vc)
csObj, ok := common.GetKeyFromVC(credentialSubjectKey, vc)
if !ok {
return nil, fmt.Errorf("credential subject not found")
}
Expand All @@ -208,8 +211,19 @@ func NewFromVC(vc map[string]interface{}, headers jose.Headers,
return nil, err
}

// 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["vc"].(map[string]interface{})["credentialSubject"] = selectiveCredentialSubject
vc[vcKey].(map[string]interface{})[credentialSubjectKey] = selectiveCredentialSubject

// sign VC with 'selective' credential subject
signedJWT, err := afgjwt.NewSigned(vc, nil, signer)
Expand Down
Loading

0 comments on commit 1dc5943

Please sign in to comment.