-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NutsEmployeeCredential does not need to be trusted (v5.3 backport) (#…
- Loading branch information
Showing
12 changed files
with
202 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,11 +22,13 @@ import ( | |
"context" | ||
"encoding/json" | ||
"errors" | ||
"github.com/golang/mock/gomock" | ||
"github.com/nuts-foundation/nuts-node/audit" | ||
"github.com/nuts-foundation/nuts-node/auth/services" | ||
"github.com/nuts-foundation/nuts-node/auth/services/selfsigned/types" | ||
"github.com/nuts-foundation/nuts-node/crypto" | ||
"github.com/nuts-foundation/nuts-node/crypto/util" | ||
"github.com/nuts-foundation/nuts-node/vcr/credential" | ||
"github.com/nuts-foundation/nuts-node/vcr/issuer" | ||
"github.com/nuts-foundation/nuts-node/vcr/verifier" | ||
"os" | ||
|
@@ -74,10 +76,11 @@ func TestSigner_Validator_Roundtrip(t *testing.T) { | |
} | ||
signerService := NewSigner(vcrContext.VCR, "http://localhost").(*signer) | ||
roleName := "Administrator" | ||
issuerDID := "did:nuts:8NYzfsndZJHh6GqzKiSBpyERrFxuX64z6tE5raa7nEjm" | ||
createdVP, err := signerService.createVP(audit.TestContext(), types.Session{ | ||
ExpiresAt: issuanceDate.Add(time.Hour * 24), | ||
Contract: testContract, | ||
Employer: "did:nuts:8NYzfsndZJHh6GqzKiSBpyERrFxuX64z6tE5raa7nEjm", | ||
Employer: issuerDID, | ||
Employee: types.Employee{ | ||
Identifier: "[email protected]", | ||
RoleName: &roleName, | ||
|
@@ -86,6 +89,15 @@ func TestSigner_Validator_Roundtrip(t *testing.T) { | |
}}, issuanceDate) | ||
require.NoError(t, err) | ||
|
||
// #2428: NutsEmployeeCredential does not need to be trusted, but the issuer needs to have a trusted NutsOrganizationCredential (chain of trust). | ||
// Issue() automatically trusts the issuer, so untrust it for asserting trust chain behavior | ||
nutsOrgCred, err := vcrContext.VCR.Issuer().Issue(audit.TestContext(), createOrganizationCredential(issuerDID), false, false) | ||
require.NoError(t, err) | ||
err = vcrContext.VCR.StoreCredential(*nutsOrgCred, nil) // Need to explicitly store, since we didn't publish it. | ||
require.NoError(t, err) | ||
err = vcrContext.VCR.Untrust(ssi.MustParseURI(credentialType), did.MustParseDID(issuerDID).URI()) | ||
require.NoError(t, err) | ||
|
||
// Validate VP | ||
validatorService := NewValidator(vcrContext.VCR, contract.StandardContractTemplates) | ||
checkTime := issuanceDate.Add(time.Minute) | ||
|
@@ -107,7 +119,8 @@ func TestValidator_VerifyVP(t *testing.T) { | |
t.Run("ok using mocks", func(t *testing.T) { | ||
mockContext := newMockContext(t) | ||
ss := NewValidator(mockContext.vcr, contract.StandardContractTemplates) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, &vpValidTime).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, true, &vpValidTime).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
mockContext.vcr.EXPECT().Search(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
|
||
result, err := ss.VerifyVP(vp, &vpValidTime) | ||
|
||
|
@@ -128,7 +141,8 @@ func TestValidator_VerifyVP(t *testing.T) { | |
credentialWithoutRole := vc.VerifiableCredential{} | ||
data, _ := os.ReadFile("./test/vc-without-role.json") | ||
_ = json.Unmarshal(data, &credentialWithoutRole) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, &vpValidTime).Return([]vc.VerifiableCredential{credentialWithoutRole}, nil) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, true, &vpValidTime).Return([]vc.VerifiableCredential{credentialWithoutRole}, nil) | ||
mockContext.vcr.EXPECT().Search(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
|
||
result, err := ss.VerifyVP(vp, &vpValidTime) | ||
|
||
|
@@ -139,7 +153,7 @@ func TestValidator_VerifyVP(t *testing.T) { | |
t.Run("technical error on verify", func(t *testing.T) { | ||
mockContext := newMockContext(t) | ||
ss := NewValidator(mockContext.vcr, contract.StandardContractTemplates) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, nil).Return(nil, errors.New("error")) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, true, nil).Return(nil, errors.New("error")) | ||
|
||
_, err := ss.VerifyVP(vp, nil) | ||
|
||
|
@@ -149,7 +163,7 @@ func TestValidator_VerifyVP(t *testing.T) { | |
t.Run("verification error on verify", func(t *testing.T) { | ||
mockContext := newMockContext(t) | ||
ss := NewValidator(mockContext.vcr, contract.StandardContractTemplates) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, nil).Return(nil, verifier.VerificationError{}) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, true, nil).Return(nil, verifier.VerificationError{}) | ||
|
||
result, err := ss.VerifyVP(vp, nil) | ||
|
||
|
@@ -159,18 +173,40 @@ func TestValidator_VerifyVP(t *testing.T) { | |
}) | ||
|
||
t.Run("ok using in-memory DBs", func(t *testing.T) { | ||
vcrContext := vcr.NewTestVCRContext(t, crypto.NewMemoryCryptoInstance()) | ||
keyStore := crypto.NewMemoryStorage() | ||
vcrContext := vcr.NewTestVCRContext(t, crypto.NewTestCryptoInstance(keyStore)) | ||
var didDocument did.Document | ||
{ | ||
ddBytes, _ := os.ReadFile("./test/diddocument.json") | ||
err := json.Unmarshal(ddBytes, &didDocument) | ||
require.NoError(t, err) | ||
} | ||
{ | ||
// Load private key so we can sign | ||
privateKeyData, _ := os.ReadFile("./test/private.pem") | ||
privateKey, err := util.PemToPrivateKey(privateKeyData) | ||
require.NoError(t, err) | ||
err = keyStore.SavePrivateKey(context.Background(), didDocument.VerificationMethod[0].ID.String(), privateKey) | ||
require.NoError(t, err) | ||
} | ||
|
||
ss := NewValidator(vcrContext.VCR, contract.StandardContractTemplates) | ||
didDocument := did.Document{} | ||
ddBytes, _ := os.ReadFile("./test/diddocument.json") | ||
_ = json.Unmarshal(ddBytes, &didDocument) | ||
// test transaction for DIDStore ordering | ||
tx := didstore.TestTransaction(didDocument) | ||
tx.SigningTime = docTXTime | ||
err := vcrContext.DIDStore.Add(didDocument, tx) | ||
require.NoError(t, err) | ||
// Trust issuer, only needed for test | ||
vcrContext.VCR.Trust(ssi.MustParseURI(credentialType), didDocument.ID.URI()) | ||
// #2428: NutsEmployeeCredential issuer needs a trusted NutsOrganizationCredential | ||
issuer.TimeFunc = func() time.Time { | ||
// Issued credentials get the current date/time as issuance date, | ||
// need to set it to a fixed value that corresponds with vpValidTime for testing. | ||
// Otherwise, the NutsOrganizationCredential is not yet valid or might be expired. | ||
return vpValidTime.Add(-1 * time.Hour) | ||
} | ||
nutsOrgCred, err := vcrContext.VCR.Issuer().Issue(audit.TestContext(), createOrganizationCredential(didDocument.ID.String()), false, false) | ||
require.NoError(t, err) | ||
err = vcrContext.VCR.StoreCredential(*nutsOrgCred, &vpValidTime) // Need to explicitly store, since we didn't publish it. | ||
require.NoError(t, err) | ||
|
||
result, err := ss.VerifyVP(vp, &vpValidTime) | ||
|
||
|
@@ -185,7 +221,8 @@ func TestValidator_VerifyVP(t *testing.T) { | |
vpData, _ := os.ReadFile("./test/vp_invalid_contract.json") | ||
_ = json.Unmarshal(vpData, &vp) | ||
ss := NewValidator(mockContext.vcr, contract.StandardContractTemplates) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, nil).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, true, nil).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
mockContext.vcr.EXPECT().Search(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
|
||
result, err := ss.VerifyVP(vp, nil) | ||
|
||
|
@@ -198,7 +235,8 @@ func TestValidator_VerifyVP(t *testing.T) { | |
mockContext := newMockContext(t) | ||
now := time.Now() | ||
ss := NewValidator(mockContext.vcr, contract.StandardContractTemplates) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, &now).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, true, &now).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
mockContext.vcr.EXPECT().Search(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
|
||
result, err := ss.VerifyVP(vp, &now) | ||
|
||
|
@@ -210,7 +248,7 @@ func TestValidator_VerifyVP(t *testing.T) { | |
t.Run("error - missing credential", func(t *testing.T) { | ||
mockContext := newMockContext(t) | ||
ss := NewValidator(mockContext.vcr, contract.StandardContractTemplates) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, nil).Return([]vc.VerifiableCredential{}, nil) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, true, nil).Return([]vc.VerifiableCredential{}, nil) | ||
|
||
result, err := ss.VerifyVP(vp, nil) | ||
|
||
|
@@ -225,7 +263,7 @@ func TestValidator_VerifyVP(t *testing.T) { | |
vpData, _ := os.ReadFile("./test/vp_missing_proof.json") | ||
_ = json.Unmarshal(vpData, &vp) | ||
ss := NewValidator(mockContext.vcr, contract.StandardContractTemplates) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, nil).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, true, nil).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
|
||
result, err := ss.VerifyVP(vp, nil) | ||
|
||
|
@@ -240,7 +278,7 @@ func TestValidator_VerifyVP(t *testing.T) { | |
vpData, _ := os.ReadFile("./test/vp_incorrect_proof_type.json") | ||
_ = json.Unmarshal(vpData, &vp) | ||
ss := NewValidator(mockContext.vcr, contract.StandardContractTemplates) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, nil).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, true, nil).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
|
||
result, err := ss.VerifyVP(vp, nil) | ||
|
||
|
@@ -255,7 +293,7 @@ func TestValidator_VerifyVP(t *testing.T) { | |
vpData, _ := os.ReadFile("./test/vp_incorrect_signer.json") | ||
_ = json.Unmarshal(vpData, &vp) | ||
ss := NewValidator(mockContext.vcr, contract.StandardContractTemplates) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, nil).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, true, nil).Return([]vc.VerifiableCredential{testCredential}, nil) | ||
|
||
result, err := ss.VerifyVP(vp, nil) | ||
|
||
|
@@ -272,14 +310,29 @@ func TestValidator_VerifyVP(t *testing.T) { | |
_ = json.Unmarshal(vpData, &vp) | ||
credential.Issuer = did.MustParseDID("did:nuts:a").URI() | ||
ss := NewValidator(mockContext.vcr, contract.StandardContractTemplates) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, nil).Return([]vc.VerifiableCredential{credential}, nil) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, true, nil).Return([]vc.VerifiableCredential{credential}, nil) | ||
|
||
result, err := ss.VerifyVP(vp, nil) | ||
|
||
require.NoError(t, err) | ||
assert.Equal(t, contract.Invalid, result.Validity()) | ||
assert.Equal(t, "signer must be credentialSubject", result.Reason()) | ||
}) | ||
|
||
t.Run("error - issuer does not have trusted NutsOrganizationCredential", func(t *testing.T) { | ||
mockContext := newMockContext(t) | ||
ss := NewValidator(mockContext.vcr, contract.StandardContractTemplates) | ||
credentialWithoutRole := vc.VerifiableCredential{} | ||
data, _ := os.ReadFile("./test/vc-without-role.json") | ||
_ = json.Unmarshal(data, &credentialWithoutRole) | ||
mockContext.verifier.EXPECT().VerifyVP(vp, true, true, &vpValidTime).Return([]vc.VerifiableCredential{credentialWithoutRole}, nil) | ||
mockContext.vcr.EXPECT().Search(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]vc.VerifiableCredential{}, nil) | ||
|
||
result, err := ss.VerifyVP(vp, &vpValidTime) | ||
|
||
require.NoError(t, err) | ||
assert.Empty(t, result.DisclosedAttribute(services.UserRoleClaim)) | ||
}) | ||
} | ||
|
||
func Test_validateRequiredAttributes(t *testing.T) { | ||
|
@@ -378,3 +431,21 @@ func Test_selfsignedVerificationResult(t *testing.T) { | |
assert.Equal(t, "test2", vr.DisclosedAttribute("dAttr1")) | ||
}) | ||
} | ||
|
||
func createOrganizationCredential(issuerDID string) vc.VerifiableCredential { | ||
orgCred := vc.VerifiableCredential{ | ||
Context: []ssi.URI{credential.NutsV1ContextURI}, | ||
Type: []ssi.URI{ssi.MustParseURI("NutsOrganizationCredential")}, | ||
Issuer: did.MustParseDID(issuerDID).URI(), | ||
CredentialSubject: []interface{}{ | ||
credential.NutsOrganizationCredentialSubject{ | ||
ID: issuerDID, | ||
Organization: map[string]string{ | ||
"name": "CareBears", | ||
"city": "CareTown", | ||
}, | ||
}, | ||
}, | ||
} | ||
return orgCred | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.