Skip to content
Snippets Groups Projects
Commit 671f9390 authored by Lyuben Penkovski's avatar Lyuben Penkovski
Browse files

Support create/verify for multiple proofs on VC

parent ca42fd4c
No related branches found
No related tags found
1 merge request!30Draft: New endpoint for creating verifiable credentials
Pipeline #67092 failed with stages
in 1 minute and 55 seconds
......@@ -9,7 +9,7 @@ var credentialWithSubjectID = `
"https://schema.org"
],
"type": "VerifiableCredential",
"issuer": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"issuanceDate": "2010-01-01T19:23:24Z",
"credentialSubject":
{
......@@ -27,7 +27,7 @@ var credentialWithoutSubjectID = `
"https://schema.org"
],
"type": "VerifiableCredential",
"issuer": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"issuanceDate": "2010-01-01T19:23:24Z",
"credentialSubject":
{
......@@ -44,7 +44,7 @@ var credentialInvalidSubjectID = `
"https://schema.org"
],
"type": "VerifiableCredential",
"issuer": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"issuanceDate": "2010-01-01T19:23:24Z",
"credentialSubject":
{
......@@ -62,7 +62,7 @@ var credentialWithNumericalSubjectID = `
"https://schema.org"
],
"type": "VerifiableCredential",
"issuer": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"issuanceDate": "2010-01-01T19:23:24Z",
"credentialSubject":
{
......
......@@ -3,6 +3,9 @@ package integration
import (
"encoding/json"
"fmt"
"github.com/hyperledger/aries-framework-go/pkg/vdr"
"github.com/hyperledger/aries-framework-go/pkg/vdr/key"
"github.com/hyperledger/aries-framework-go/pkg/vdr/web"
"net/http"
"os"
"testing"
......@@ -54,7 +57,7 @@ func TestCreateAndVerifyCredentialProof(t *testing.T) {
signer := client.NewSigner(addr)
// create proof
vcWithProof, err := signer.CreateCredentialProof(test.vc)
vcWithProof, err := signer.CreateCredentialProof("key1", test.vc)
assert.NoError(t, err)
assert.NotNil(t, vcWithProof)
......@@ -132,7 +135,7 @@ func TestCreateCredentialProof(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
signer := client.NewSigner(addr)
vcWithProof, err := signer.CreateCredentialProof(test.vc)
vcWithProof, err := signer.CreateCredentialProof("key1", test.vc)
if test.errMsg != "" {
require.Error(t, err, fmt.Sprintf("got no error but expected %q", test.errMsg))
assert.Contains(t, err.Error(), test.errMsg)
......@@ -183,7 +186,7 @@ func TestCreateAndVerifyPresentationProof(t *testing.T) {
signer := client.NewSigner(addr)
// create proof
vpWithProof, err := signer.CreatePresentationProof(test.vp)
vpWithProof, err := signer.CreatePresentationProof("key1", test.vp)
require.NoError(t, err)
assert.NotNil(t, vpWithProof)
......@@ -269,7 +272,7 @@ func TestCreatePresentationProof(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
signer := client.NewSigner(addr)
vpWithProof, err := signer.CreatePresentationProof(test.vp)
vpWithProof, err := signer.CreatePresentationProof("key1", test.vp)
if test.errMsg != "" {
require.Error(t, err, fmt.Sprintf("got no error but expected %q", test.errMsg))
assert.Contains(t, err.Error(), test.errMsg)
......@@ -320,7 +323,7 @@ func TestCreatePresentation(t *testing.T) {
{
name: "valid request with single credentialSubject entry",
req: map[string]interface{}{
"issuer": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"namespace": "transit",
"key": "key1",
"data": []map[string]interface{}{
......@@ -331,7 +334,7 @@ func TestCreatePresentation(t *testing.T) {
{
name: "valid request with multiple credentialSubject entry",
req: map[string]interface{}{
"issuer": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"namespace": "transit",
"key": "key1",
"data": []map[string]interface{}{
......@@ -343,7 +346,7 @@ func TestCreatePresentation(t *testing.T) {
{
name: "valid request with additional context",
req: map[string]interface{}{
"issuer": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"namespace": "transit",
"key": "key1",
"context": []string{
......@@ -402,3 +405,143 @@ func TestCreatePresentation(t *testing.T) {
})
}
}
func TestCreateCredentialMultipleProofs(t *testing.T) {
initTests(t)
// first create a credential with one proof
signer := client.NewSigner(addr)
cred := []byte(credentialWithSubjectID)
// create first proof
vcWithProof, err := signer.CreateCredentialProof("key1", cred)
assert.NoError(t, err)
assert.NotNil(t, vcWithProof)
// verify signature
_, err = verifyCredentialProofs(vcWithProof)
assert.NoError(t, err)
// create second proof
vc2Proofs, err := signer.CreateCredentialProof("key1", vcWithProof)
assert.NoError(t, err)
assert.NotNil(t, vc2Proofs)
// verify signatures
_, err = verifyCredentialProofs(vc2Proofs)
require.NoError(t, err)
// run tests modifying the contents of the VC and do proof verifications afterwards
t.Run("modify credential subject and check proofs afterwards", func(t *testing.T) {
correctVC := make([]byte, len(vc2Proofs))
copy(correctVC, vc2Proofs)
parsedVC, err := verifyCredentialProofs(correctVC)
assert.NoError(t, err)
// modify the credentialSubject by adding a new value
// which MUST break signature verification
subject, ok := parsedVC.Subject.([]verifiable.Subject)
assert.True(t, ok)
subject[0].CustomFields["newKey"] = "newValue"
// marshal the modified credential
modifiedVC, err := json.Marshal(parsedVC)
assert.NoError(t, err)
assert.NotNil(t, modifiedVC)
_, err = verifyCredentialProofs(modifiedVC)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid signature")
})
t.Run("modify first signature and check proofs afterwards", func(t *testing.T) {
correctVC := make([]byte, len(vc2Proofs))
copy(correctVC, vc2Proofs)
parsedVC, err := verifyCredentialProofs(correctVC)
assert.NoError(t, err)
// modify JWS value of the first proof by removing the last character
proof1 := parsedVC.Proofs[0]
if jws, ok := proof1["jws"].(string); ok && jws != "" {
// substitute last char of JWS with another one, so that the signature
// should become invalid
var modifiedJWS string
if jws[len(jws)-1] != 'a' {
modifiedJWS = jws[:len(jws)-1] + "a"
} else {
modifiedJWS = jws[:len(jws)-1] + "b"
}
parsedVC.Proofs[0]["jws"] = modifiedJWS
} else {
t.Errorf("expected to have proof 1 but it's missing or invalid")
}
// marshal the modified credential
modifiedVC, err := json.Marshal(parsedVC)
assert.NoError(t, err)
assert.NotNil(t, modifiedVC)
_, err = verifyCredentialProofs(modifiedVC)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid signature")
})
t.Run("modifiy second signature and check proofs afterwards", func(t *testing.T) {
correctVC := make([]byte, len(vc2Proofs))
copy(correctVC, vc2Proofs)
parsedVC, err := verifyCredentialProofs(correctVC)
assert.NoError(t, err)
// modify JWS value of the second proof by removing the last character
proof2 := parsedVC.Proofs[1]
if jws, ok := proof2["jws"].(string); ok && jws != "" {
// substitute last char of JWS with another one, so that the signature
// should become invalid
var modifiedJWS string
if jws[len(jws)-1] != 'a' {
modifiedJWS = jws[:len(jws)-1] + "a"
} else {
modifiedJWS = jws[:len(jws)-1] + "b"
}
parsedVC.Proofs[1]["jws"] = modifiedJWS
} else {
t.Errorf("expected to have proof 2 but it's missing or invalid")
}
// marshal the modified credential
modifiedVC, err := json.Marshal(parsedVC)
assert.NoError(t, err)
assert.NotNil(t, modifiedVC)
_, err = verifyCredentialProofs(modifiedVC)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid signature")
})
}
func verifyCredentialProofs(vcBytes []byte) (*verifiable.Credential, error) {
webVDR := web.New()
keyVDR := key.New()
registry := vdr.New(
vdr.WithVDR(webVDR),
vdr.WithVDR(keyVDR),
)
keyResolver := verifiable.NewVDRKeyResolver(registry)
// parse it to object to modify credentialSubject attribute
vc, err := verifiable.ParseCredential(
vcBytes,
verifiable.WithJSONLDDocumentLoader(loader),
verifiable.WithStrictValidation(),
verifiable.WithJSONLDValidation(),
verifiable.WithJSONLDOnlyValidRDF(),
verifiable.WithPublicKeyFetcher(keyResolver.PublicKeyFetcher()),
)
return vc, err
}
......@@ -16,7 +16,7 @@ func NewSigner(addr string) *Signer {
return &Signer{addr: addr}
}
func (s *Signer) CreateCredentialProof(vc []byte) ([]byte, error) {
func (s *Signer) CreateCredentialProof(key string, vc []byte) ([]byte, error) {
var cred map[string]interface{}
if err := json.Unmarshal(vc, &cred); err != nil {
return nil, err
......@@ -24,7 +24,7 @@ func (s *Signer) CreateCredentialProof(vc []byte) ([]byte, error) {
payload := map[string]interface{}{
"namespace": "transit",
"key": "key2",
"key": key,
"credential": cred,
}
......@@ -83,16 +83,16 @@ func (s *Signer) VerifyCredentialProof(vc []byte) error {
return nil
}
func (s *Signer) CreatePresentationProof(vp []byte) ([]byte, error) {
func (s *Signer) CreatePresentationProof(key string, vp []byte) ([]byte, error) {
var pres map[string]interface{}
if err := json.Unmarshal(vp, &pres); err != nil {
return nil, err
}
payload := map[string]interface{}{
"issuer": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"namespace": "transit",
"key": "key2",
"key": key,
"presentation": pres,
}
......
......@@ -8,7 +8,7 @@ var presentationWithSubjectID = `
"https://w3id.org/security/suites/jws-2020/v1",
"https://schema.org"
],
"id": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"id": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"type": "VerifiablePresentation",
"verifiableCredential":
[
......@@ -18,9 +18,9 @@ var presentationWithSubjectID = `
"https://www.w3.org/2018/credentials/v1",
"https://schema.org"
],
"id": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"id": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"type": "VerifiableCredential",
"issuer": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"issuanceDate": "2010-01-01T19:23:24Z",
"credentialSubject":
{
......@@ -39,7 +39,7 @@ var presentationWithoutSubjectID = `
"https://w3id.org/security/suites/jws-2020/v1",
"https://schema.org"
],
"id": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"id": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"type": "VerifiablePresentation",
"verifiableCredential":
[
......@@ -49,9 +49,9 @@ var presentationWithoutSubjectID = `
"https://www.w3.org/2018/credentials/v1",
"https://schema.org"
],
"id": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"id": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"type": "VerifiableCredential",
"issuer": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"issuanceDate": "2010-01-01T19:23:24Z",
"credentialSubject":
{
......@@ -69,7 +69,7 @@ var presentationWithInvalidSubjectID = `
"https://w3id.org/security/suites/jws-2020/v1",
"https://schema.org"
],
"id": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"id": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"type": "VerifiablePresentation",
"verifiableCredential":
[
......@@ -79,9 +79,9 @@ var presentationWithInvalidSubjectID = `
"https://www.w3.org/2018/credentials/v1",
"https://schema.org"
],
"id": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"id": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"type": "VerifiableCredential",
"issuer": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"issuanceDate": "2010-01-01T19:23:24Z",
"credentialSubject":
{
......@@ -100,7 +100,7 @@ var presentationWithNumericalSubjectID = `
"https://w3id.org/security/suites/jws-2020/v1",
"https://schema.org"
],
"id": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"id": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"type": "VerifiablePresentation",
"verifiableCredential":
[
......@@ -110,9 +110,9 @@ var presentationWithNumericalSubjectID = `
"https://www.w3.org/2018/credentials/v1",
"https://schema.org"
],
"id": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"id": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"type": "VerifiableCredential",
"issuer": "did:web:4db4-85-196-181-2.eu.ngrok.io:policy:policy:example:returnDID:1.0:evaluation",
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"issuanceDate": "2010-01-01T19:23:24Z",
"credentialSubject":
{
......@@ -131,7 +131,7 @@ var presentationWithMissingCredentialContext = `
"https://w3id.org/security/suites/jws-2020/v1",
"https://schema.org"
],
"id": "did:web:gaiax.vereign.com:tsa:policy:policy:example:returnDID:1.0:evaluation",
"id": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"type": "VerifiablePresentation",
"verifiableCredential":
[
......@@ -146,10 +146,10 @@ var presentationWithMissingCredentialContext = `
"age_over": 18,
"allow": false,
"citizenship": "France",
"id": "https://gaiax.vereign.com/tsa/policy/example/ProofRequestResponse/1.0"
"id": "https://ssi-dev.vereign.com/tsa/policy/policy/example/example/1.0/evaluation"
},
"issuanceDate": "2022-07-21T10:24:36.203848291Z",
"issuer": "did:web:gaiax.vereign.com:tsa:policy:policy:example:returnDID:1.0:evaluation",
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"type": "VerifiableCredential"
}
]
......
......@@ -211,6 +211,7 @@ func (s *Service) CredentialProof(ctx context.Context, req *signer.CredentialPro
return nil, errors.New(errors.BadRequest, err.Error())
}
// credential may not have a proof, so disable proofCheck on first round
vc, err := s.parseCredential(vcBytes, false)
if err != nil {
logger.Error("error parsing verifiable credential", zap.Error(err))
......@@ -220,8 +221,13 @@ func (s *Service) CredentialProof(ctx context.Context, req *signer.CredentialPro
return nil, errors.New(errors.BadRequest, err.Error())
}
// if the given credential has at least one proof, check again to verify the proofs
if len(vc.Proofs) > 0 {
return nil, errors.New(errors.Forbidden, "credential already has proof")
vc, err = s.parseCredential(vcBytes, true)
if err != nil {
logger.Error("credential proofs cannot be verified", zap.Error(err))
return nil, errors.New(errors.Forbidden, err.Error())
}
}
if err := validateCredentialSubject(vc.Subject); err != nil {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment