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

Unit and integration tests for create VC endpoint

parent c9425555
No related branches found
No related tags found
No related merge requests found
Pipeline #67174 failed with stages
in 4 minutes and 49 seconds
......@@ -5,13 +5,13 @@ import (
"fmt"
"net/http"
"os"
"strings"
"testing"
"github.com/hyperledger/aries-framework-go/pkg/doc/verifiable"
"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"
"github.com/hyperledger/aries-framework-go/pkg/doc/verifiable"
"github.com/piprate/json-gold/ld"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
......@@ -119,7 +119,7 @@ func TestCreateCredentialProof(t *testing.T) {
{
name: "credential with invalid subject id",
vc: []byte(credentialInvalidSubjectID),
errMsg: "invalid format of subject id",
errMsg: "invalid subject id: must be URI",
},
{
name: "credential with numerical subject id",
......@@ -256,7 +256,7 @@ func TestCreatePresentationProof(t *testing.T) {
{
name: "presentation with credential with invalid subject id",
vp: []byte(presentationWithInvalidSubjectID),
errMsg: "invalid format of subject id",
errMsg: "invalid subject id: must be URI",
},
{
name: "presentation with credential with numerical subject id",
......@@ -299,6 +299,90 @@ func TestCreatePresentationProof(t *testing.T) {
}
}
func TestCreateCredential(t *testing.T) {
initTests(t)
tests := []struct {
name string
req map[string]interface{}
contexts []string
errtext string
}{
{
name: "empty request",
errtext: "400 Bad Request",
},
{
name: "invalid request because issuer is missing",
req: map[string]interface{}{
"namespace": "transit",
"key": "key1",
"credentialSubject": map[string]interface{}{"cred1": "value1"},
},
errtext: "400 Bad Request",
},
{
name: "valid request with single credentialSubject claim",
req: map[string]interface{}{
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"namespace": "transit",
"key": "key1",
"credentialSubject": map[string]interface{}{"cred1": "value1"},
},
},
{
name: "valid request with multiple credentialSubject claims",
req: map[string]interface{}{
"issuer": "did:web:comic-bullfrog-smoothly.ngrok-free.app:policy:example:returnDID:1.0:evaluation",
"namespace": "transit",
"key": "key1",
"credentialSubject": map[string]interface{}{
"cred1": "value1",
"cred2": "value2",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
reqData, err := json.Marshal(test.req)
require.NoError(t, err)
signer := client.NewSigner(addr)
vcWithProof, err := signer.CreateCredential(reqData)
if test.errtext != "" {
require.Error(t, err, fmt.Sprintf("got no error but expected %q", test.errtext))
assert.Contains(t, err.Error(), test.errtext)
return
}
vc, err := verifyCredentialProofs(vcWithProof)
require.NoError(t, err)
assert.NotNil(t, vc)
assert.NotEmpty(t, vc.Proofs)
assert.NotEmpty(t, vc.Proofs[0]["jws"])
assert.NotEmpty(t, vc.Proofs[0]["created"])
assert.NotEmpty(t, vc.Proofs[0]["verificationMethod"])
assert.Equal(t, "assertionMethod", vc.Proofs[0]["proofPurpose"])
assert.Equal(t, "JsonWebSignature2020", vc.Proofs[0]["type"])
// hyperledger aries always parse the subject map into an array (unless it's just a string)
subject, ok := vc.Subject.([]verifiable.Subject)
assert.True(t, ok)
expectedClaims, ok := test.req["credentialSubject"].(map[string]interface{})
assert.True(t, ok)
assert.Equal(t, len(expectedClaims), len(subject[0].CustomFields))
for key := range expectedClaims {
assert.Equal(t, expectedClaims[key], subject[0].CustomFields[key])
}
})
}
}
func TestCreatePresentation(t *testing.T) {
initTests(t)
......@@ -317,7 +401,9 @@ func TestCreatePresentation(t *testing.T) {
req: map[string]interface{}{
"namespace": "transit",
"key": "key1",
"data": map[string]interface{}{"cred1": "value1"},
"data": []map[string]interface{}{
{"cred1": "value1"},
},
},
errtext: "400 Bad Request",
},
......@@ -393,7 +479,7 @@ func TestCreatePresentation(t *testing.T) {
creds := vp.Credentials()
requiredCreds, ok := test.req["data"].([]map[string]interface{})
assert.True(t, ok)
assert.Equal(t, len(creds), len(requiredCreds))
assert.Equal(t, len(requiredCreds), len(creds))
for i, cred := range creds {
c, ok := cred.(map[string]interface{})
......@@ -468,15 +554,9 @@ func TestCreateCredentialMultipleProofs(t *testing.T) {
// 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
modifiedSignature, err := modifySignature(jws)
require.NoError(t, err)
parsedVC.Proofs[0]["jws"] = modifiedSignature
} else {
t.Errorf("expected to have proof 1 but it's missing or invalid")
}
......@@ -487,7 +567,7 @@ func TestCreateCredentialMultipleProofs(t *testing.T) {
assert.NotNil(t, modifiedVC)
_, err = verifyCredentialProofs(modifiedVC)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid signature")
})
......@@ -501,15 +581,9 @@ func TestCreateCredentialMultipleProofs(t *testing.T) {
// 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
modifiedSignature, err := modifySignature(jws)
require.NoError(t, err)
parsedVC.Proofs[1]["jws"] = modifiedSignature
} else {
t.Errorf("expected to have proof 2 but it's missing or invalid")
}
......@@ -546,3 +620,14 @@ func verifyCredentialProofs(vcBytes []byte) (*verifiable.Credential, error) {
return vc, err
}
func modifySignature(jws string) (string, error) {
parts := strings.Split(jws, ".")
if len(parts) != 3 {
return "", fmt.Errorf("invalid jws signature")
}
modifiedJWS := parts[0] + "." + parts[1] + "." + "8hiz2aWSW_AWnZ_GnoQyHrYgGia0HxdYTQGYOVYkPLU"
return modifiedJWS, nil
}
......@@ -170,6 +170,25 @@ func (s *Signer) CreatePresentation(data []byte) ([]byte, error) {
return io.ReadAll(resp.Body)
}
func (s *Signer) CreateCredential(data []byte) ([]byte, error) {
req, err := http.NewRequest(http.MethodPost, s.addr+"/v1/credential", bytes.NewReader(data))
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, newErrorResponse(resp)
}
return io.ReadAll(resp.Body)
}
type errorResponse struct {
Code int
Status string
......
......@@ -6,6 +6,7 @@ import (
"crypto/ecdsa"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"testing"
......@@ -641,6 +642,168 @@ func TestService_PresentationProof(t *testing.T) {
}
}
func TestService_CreateCredential(t *testing.T) {
tests := []struct {
name string
signer *signerfakes.FakeVault
supportedKeys []string
issuer string
namespace string
keyname string
credentialSubject map[string]interface{}
errkind errors.Kind
errtext string
contexts []string
types []string
proofPurpose string
proofType string
proofVerificationMethod string
wantedCredentialSubject verifiable.Subject
}{
{
name: "missing credential subject",
errtext: "invalid credential subject: non-empty map is expected",
errkind: errors.BadRequest,
},
{
name: "invalid credential subject id",
credentialSubject: map[string]interface{}{"id": "invalid credential subject id"},
errtext: "invalid format of subject id",
errkind: errors.BadRequest,
},
{
name: "valid credential subject, but error finding signing key",
supportedKeys: []string{"ed25519", "ecdsa-p256"},
issuer: "https://example.com",
namespace: "transit",
keyname: "key2",
credentialSubject: map[string]interface{}{"id": "https://example.com"},
signer: &signerfakes.FakeVault{
KeyStub: func(ctx context.Context, namespace, key string) (*signer.VaultKey, error) {
return nil, fmt.Errorf("no such key")
},
},
errtext: "error getting signing key: no such key",
errkind: errors.Unknown,
},
{
name: "valid credential subject and signing is successful",
supportedKeys: []string{"ed25519", "ecdsa-p256"},
issuer: "https://example.com",
namespace: "transit",
keyname: "key2",
credentialSubject: map[string]interface{}{"id": "https://example.com"},
signer: &signerfakes.FakeVault{
KeyStub: func(ctx context.Context, namespace, key string) (*signer.VaultKey, error) {
return &signer.VaultKey{
Name: "key2",
Type: "ecdsa-p256",
}, nil
},
WithKeyStub: func(namespace, key string) signer.Vault {
return &signerfakes.FakeVault{
SignStub: func(data []byte) ([]byte, error) {
return []byte("test signature"), nil
},
}
},
},
// expected attributes the VC must have
contexts: []string{
"https://www.w3.org/2018/credentials/v1",
"https://w3id.org/security/suites/jws-2020/v1",
"https://schema.org",
},
types: []string{verifiable.VCType},
proofPurpose: "assertionMethod",
proofType: "JsonWebSignature2020",
proofVerificationMethod: "https://example.com#key2",
wantedCredentialSubject: verifiable.Subject{
ID: "https://example.com",
CustomFields: map[string]interface{}{},
},
},
{
name: "valid credential with multiple claims and signing is successful",
supportedKeys: []string{"ed25519", "ecdsa-p256"},
issuer: "https://example.com",
namespace: "transit",
keyname: "key2",
credentialSubject: map[string]interface{}{"id": "https://example.com", "email": "test@mymail.com"},
signer: &signerfakes.FakeVault{
KeyStub: func(ctx context.Context, namespace, key string) (*signer.VaultKey, error) {
return &signer.VaultKey{
Name: "key2",
Type: "ecdsa-p256",
}, nil
},
WithKeyStub: func(namespace, key string) signer.Vault {
return &signerfakes.FakeVault{
SignStub: func(data []byte) ([]byte, error) {
return []byte("test signature"), nil
},
}
},
},
// expected attributes the VC must have
contexts: []string{
"https://www.w3.org/2018/credentials/v1",
"https://w3id.org/security/suites/jws-2020/v1",
"https://schema.org",
},
types: []string{verifiable.VCType},
proofPurpose: "assertionMethod",
proofType: "JsonWebSignature2020",
proofVerificationMethod: "https://example.com#key2",
wantedCredentialSubject: verifiable.Subject{
ID: "https://example.com",
CustomFields: map[string]interface{}{
"email": "test@mymail.com",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
signer := signer.New(test.signer, test.supportedKeys, http.DefaultClient, zap.NewNop())
req := &goasigner.CreateCredentialRequest{
Issuer: test.issuer,
Namespace: test.namespace,
Key: test.keyname,
CredentialSubject: test.credentialSubject,
}
credential, err := signer.CreateCredential(context.Background(), req)
if err != nil {
require.NotEmpty(t, test.errtext, "received error, but test case has no error: %v", err)
assert.Contains(t, err.Error(), test.errtext)
if e, ok := err.(*errors.Error); ok {
assert.Equal(t, test.errkind, e.Kind)
}
assert.Nil(t, credential)
} else {
require.Empty(t, test.errtext, "test case expects error, but got none")
assert.NotNil(t, credential)
vc, ok := credential.(*verifiable.Credential)
assert.True(t, ok)
assert.Equal(t, test.contexts, vc.Context)
assert.Equal(t, test.types, vc.Types)
assert.Equal(t, test.proofPurpose, vc.Proofs[0]["proofPurpose"])
assert.Equal(t, test.proofType, vc.Proofs[0]["type"])
assert.Equal(t, test.proofVerificationMethod, vc.Proofs[0]["verificationMethod"])
assert.NotEmpty(t, vc.Proofs[0]["jws"])
assert.Equal(t, test.wantedCredentialSubject, vc.Subject)
}
})
}
}
// ---------- Verifiable Credentials ---------- //
//nolint:gosec
......
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