From bdbe5ca862be0d3ca6579470bf80a5dec3323d84 Mon Sep 17 00:00:00 2001 From: Roland Groen Date: Tue, 9 Apr 2024 15:42:53 +0200 Subject: [PATCH] Refactor VerifiableCredentials function to include nonce The VerifiableCredentials function has been revised to involve the nonce from token response in IAM authentication process. The nonce, which is used in the JWT proof of possession, was previously not included in the VerifiableCredentials function. Necessary updates were also made in related test cases to pass the appropriate nonce. --- auth/api/iam/api.go | 2 +- auth/api/iam/api_test.go | 8 +++++--- auth/client/iam/interface.go | 2 +- auth/client/iam/mock.go | 8 ++++---- auth/client/iam/openid4vp.go | 10 +++++++--- auth/client/iam/openid4vp_test.go | 11 ++++++----- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/auth/api/iam/api.go b/auth/api/iam/api.go index 74e8590415..e5e3566031 100644 --- a/auth/api/iam/api.go +++ b/auth/api/iam/api.go @@ -734,7 +734,7 @@ func (r Wrapper) CallbackOid4vciCredentialIssuance(ctx context.Context, request log.Logger().WithError(err).Errorf("error while fetching the access_token from endpoint: %s", tokenEndpoint) return nil, withCallbackURI(oauthError(oauth.AccessDenied, fmt.Sprintf("error while fetching the access_token from endpoint: %s, error : %s", tokenEndpoint, err.Error())), oid4vciSession.remoteRedirectUri()) } - credentials, err := r.auth.IAMClient().VerifiableCredentials(ctx, credentialEndpoint, response.AccessToken, *holderDid, *issuerDid) + credentials, err := r.auth.IAMClient().VerifiableCredentials(ctx, credentialEndpoint, response.AccessToken, response.CNonce, *holderDid, *issuerDid) if err != nil { log.Logger().WithError(err).Errorf("error while fetching the credential from endpoint: %s", credentialEndpoint) return nil, withCallbackURI(oauthError(oauth.ServerError, fmt.Sprintf("error while fetching the credential from endpoint %s, error : %s", credentialEndpoint, err.Error())), oid4vciSession.remoteRedirectUri()) diff --git a/auth/api/iam/api_test.go b/auth/api/iam/api_test.go index 05ea87632d..6cb8dda7ce 100644 --- a/auth/api/iam/api_test.go +++ b/auth/api/iam/api_test.go @@ -1068,6 +1068,7 @@ func TestWrapper_CallbackOid4vciCredentialIssuance(t *testing.T) { redirectURI := "https://test.test/iam/123/cb" authServer := "https://auth.server" tokenEndpoint := authServer + "/token" + cNonce := crypto.GenerateNonce() credEndpoint := authServer + "/credz" pkceParams := generatePKCEParams() code := "code" @@ -1088,6 +1089,7 @@ func TestWrapper_CallbackOid4vciCredentialIssuance(t *testing.T) { tokenResponse := oauth.Oid4vciTokenResponse{ AccessToken: accessToken, TokenType: "Bearer", + CNonce: &cNonce, } credentialResponse := iam.CredentialResponse{ Format: "jwt_vc", @@ -1097,7 +1099,7 @@ func TestWrapper_CallbackOid4vciCredentialIssuance(t *testing.T) { ctx := newTestClient(t) ctx.client.storageEngine.GetSessionDatabase().GetStore(15*time.Minute, "oid4vci").Put(state, &session) ctx.iamClient.EXPECT().AccessTokenOid4vci(nil, holderDID.String(), tokenEndpoint, redirectURI, code, &pkceParams.Verifier).Return(&tokenResponse, nil) - ctx.iamClient.EXPECT().VerifiableCredentials(nil, credEndpoint, accessToken, holderDID, issuerDID).Return(&credentialResponse, nil) + ctx.iamClient.EXPECT().VerifiableCredentials(nil, credEndpoint, accessToken, &cNonce, holderDID, issuerDID).Return(&credentialResponse, nil) ctx.vcVerifier.EXPECT().Verify(*verifiableCredential, true, true, nil) ctx.wallet.EXPECT().Put(nil, *verifiableCredential) @@ -1163,7 +1165,7 @@ func TestWrapper_CallbackOid4vciCredentialIssuance(t *testing.T) { ctx := newTestClient(t) ctx.client.storageEngine.GetSessionDatabase().GetStore(15*time.Minute, "oid4vci").Put(state, &session) ctx.iamClient.EXPECT().AccessTokenOid4vci(nil, holderDID.String(), tokenEndpoint, redirectURI, code, &pkceParams.Verifier).Return(&tokenResponse, nil) - ctx.iamClient.EXPECT().VerifiableCredentials(nil, credEndpoint, accessToken, holderDID, issuerDID).Return(nil, errors.New("FAIL")) + ctx.iamClient.EXPECT().VerifiableCredentials(nil, credEndpoint, accessToken, &cNonce, holderDID, issuerDID).Return(nil, errors.New("FAIL")) callback, err := ctx.client.CallbackOid4vciCredentialIssuance(nil, CallbackOid4vciCredentialIssuanceRequestObject{ Params: CallbackOid4vciCredentialIssuanceParams{ @@ -1180,7 +1182,7 @@ func TestWrapper_CallbackOid4vciCredentialIssuance(t *testing.T) { ctx := newTestClient(t) ctx.client.storageEngine.GetSessionDatabase().GetStore(15*time.Minute, "oid4vci").Put(state, &session) ctx.iamClient.EXPECT().AccessTokenOid4vci(nil, holderDID.String(), tokenEndpoint, redirectURI, code, &pkceParams.Verifier).Return(&tokenResponse, nil) - ctx.iamClient.EXPECT().VerifiableCredentials(nil, credEndpoint, accessToken, holderDID, issuerDID).Return(&credentialResponse, nil) + ctx.iamClient.EXPECT().VerifiableCredentials(nil, credEndpoint, accessToken, &cNonce, holderDID, issuerDID).Return(&credentialResponse, nil) ctx.vcVerifier.EXPECT().Verify(*verifiableCredential, true, true, nil).Return(errors.New("FAIL")) callback, err := ctx.client.CallbackOid4vciCredentialIssuance(nil, CallbackOid4vciCredentialIssuanceRequestObject{ diff --git a/auth/client/iam/interface.go b/auth/client/iam/interface.go index db919502ba..742ba076a7 100644 --- a/auth/client/iam/interface.go +++ b/auth/client/iam/interface.go @@ -62,7 +62,7 @@ type Client interface { AccessTokenOid4vci(ctx context.Context, clientId string, tokenEndpoint string, redirectUri string, code string, pkceCodeVerifier *string) (*oauth.Oid4vciTokenResponse, error) - VerifiableCredentials(ctx context.Context, credentialEndpoint string, accessToken string, holderDid did.DID, audienceDid did.DID) (*CredentialResponse, error) + VerifiableCredentials(ctx context.Context, credentialEndpoint string, accessToken string, cNonce *string, holderDid did.DID, audienceDid did.DID) (*CredentialResponse, error) } // RequestModifier is a function that modifies the claims/params of a unsigned or signed request (JWT) diff --git a/auth/client/iam/mock.go b/auth/client/iam/mock.go index 3e83f979bb..88743ec03e 100644 --- a/auth/client/iam/mock.go +++ b/auth/client/iam/mock.go @@ -210,16 +210,16 @@ func (mr *MockClientMockRecorder) RequestRFC021AccessToken(ctx, requestHolder, v } // VerifiableCredentials mocks base method. -func (m *MockClient) VerifiableCredentials(ctx context.Context, credentialEndpoint, accessToken string, holderDid, audienceDid did.DID) (*CredentialResponse, error) { +func (m *MockClient) VerifiableCredentials(ctx context.Context, credentialEndpoint, accessToken string, cNonce *string, holderDid, audienceDid did.DID) (*CredentialResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "VerifiableCredentials", ctx, credentialEndpoint, accessToken, holderDid, audienceDid) + ret := m.ctrl.Call(m, "VerifiableCredentials", ctx, credentialEndpoint, accessToken, cNonce, holderDid, audienceDid) ret0, _ := ret[0].(*CredentialResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // VerifiableCredentials indicates an expected call of VerifiableCredentials. -func (mr *MockClientMockRecorder) VerifiableCredentials(ctx, credentialEndpoint, accessToken, holderDid, audienceDid any) *gomock.Call { +func (mr *MockClientMockRecorder) VerifiableCredentials(ctx, credentialEndpoint, accessToken, cNonce, holderDid, audienceDid any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifiableCredentials", reflect.TypeOf((*MockClient)(nil).VerifiableCredentials), ctx, credentialEndpoint, accessToken, holderDid, audienceDid) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifiableCredentials", reflect.TypeOf((*MockClient)(nil).VerifiableCredentials), ctx, credentialEndpoint, accessToken, cNonce, holderDid, audienceDid) } diff --git a/auth/client/iam/openid4vp.go b/auth/client/iam/openid4vp.go index 2feb87db5d..a86301e699 100644 --- a/auth/client/iam/openid4vp.go +++ b/auth/client/iam/openid4vp.go @@ -304,7 +304,7 @@ func (c *OpenID4VPClient) AccessTokenOid4vci(ctx context.Context, clientId strin return rsp, nil } -func (c *OpenID4VPClient) proofJwt(ctx context.Context, holderDid did.DID, audienceDid did.DID) (string, error) { +func (c *OpenID4VPClient) proofJwt(ctx context.Context, holderDid did.DID, audienceDid did.DID, nonce *string) (string, error) { kid, _, err := c.keyResolver.ResolveKey(holderDid, nil, resolver.NutsSigningKeyType) if err != nil { return "", fmt.Errorf("failed to resolve key for did (%s): %w", holderDid.String(), err) @@ -318,14 +318,18 @@ func (c *OpenID4VPClient) proofJwt(ctx context.Context, holderDid did.DID, audie "aud": audienceDid.String(), "jti": jti.String(), } + if nonce != nil { + claims["nonce"] = nonce + } proofJwt, err := c.jwtSigner.SignJWT(ctx, claims, nil, kid.String()) if err != nil { return "", fmt.Errorf("failed to sign the JWT with kid (%s): %w", kid.String(), err) } return proofJwt, nil } -func (c *OpenID4VPClient) VerifiableCredentials(ctx context.Context, credentialEndpoint string, accessToken string, holderDid did.DID, audienceDid did.DID) (*CredentialResponse, error) { - proofJwt, err := c.proofJwt(ctx, holderDid, audienceDid) +func (c *OpenID4VPClient) VerifiableCredentials(ctx context.Context, credentialEndpoint string, accessToken string, cNonce *string, holderDid did.DID, audienceDid did.DID) (*CredentialResponse, error) { + // The cNonce becomes the nonce in the JWT proof of possession. + proofJwt, err := c.proofJwt(ctx, holderDid, audienceDid, cNonce) if err != nil { return nil, err } diff --git a/auth/client/iam/openid4vp_test.go b/auth/client/iam/openid4vp_test.go index 1030002bbe..eff8448fea 100644 --- a/auth/client/iam/openid4vp_test.go +++ b/auth/client/iam/openid4vp_test.go @@ -634,6 +634,7 @@ func TestIAMClient_AccessTokenOid4vci(t *testing.T) { func TestIAMClient_VerifiableCredentials(t *testing.T) { walletDID := did.MustParseDID("did:web:test.test:iam:123") accessToken := "code" + cNonce := crypto.GenerateNonce() t.Run("ok", func(t *testing.T) { keyId := walletDID.URI() @@ -651,7 +652,7 @@ func TestIAMClient_VerifiableCredentials(t *testing.T) { return "signed JWT", nil }) - response, err := ctx.client.VerifiableCredentials(context.Background(), ctx.openIDCredentialIssuerMetadata.CredentialEndpoint, accessToken, walletDID, ctx.issuerDID) + response, err := ctx.client.VerifiableCredentials(context.Background(), ctx.openIDCredentialIssuerMetadata.CredentialEndpoint, accessToken, &cNonce, walletDID, ctx.issuerDID) require.NoError(t, err) require.NotNil(t, response) @@ -676,7 +677,7 @@ func TestIAMClient_VerifiableCredentials(t *testing.T) { ctx.credentials = nil - response, err := ctx.client.VerifiableCredentials(context.Background(), ctx.openIDCredentialIssuerMetadata.CredentialEndpoint, accessToken, walletDID, ctx.issuerDID) + response, err := ctx.client.VerifiableCredentials(context.Background(), ctx.openIDCredentialIssuerMetadata.CredentialEndpoint, accessToken, &cNonce, walletDID, ctx.issuerDID) assert.EqualError(t, err, "remote server: failed to retrieve credentials: server returned HTTP 404 (expected: 200)") assert.Nil(t, response) @@ -704,7 +705,7 @@ func TestIAMClient_VerifiableCredentials(t *testing.T) { return } - response, err := ctx.client.VerifiableCredentials(context.Background(), ctx.openIDCredentialIssuerMetadata.CredentialEndpoint, accessToken, walletDID, ctx.issuerDID) + response, err := ctx.client.VerifiableCredentials(context.Background(), ctx.openIDCredentialIssuerMetadata.CredentialEndpoint, accessToken, &cNonce, walletDID, ctx.issuerDID) assert.Error(t, err) assert.Nil(t, response) @@ -719,7 +720,7 @@ func TestIAMClient_VerifiableCredentials(t *testing.T) { ctx.credentials = nil - response, err := ctx.client.VerifiableCredentials(context.Background(), ctx.openIDCredentialIssuerMetadata.CredentialEndpoint, accessToken, walletDID, ctx.issuerDID) + response, err := ctx.client.VerifiableCredentials(context.Background(), ctx.openIDCredentialIssuerMetadata.CredentialEndpoint, accessToken, &cNonce, walletDID, ctx.issuerDID) assert.EqualError(t, err, "failed to resolve key for did (did:web:test.test:iam:123): "+resolver.ErrKeyNotFound.Error()) assert.Nil(t, response) @@ -736,7 +737,7 @@ func TestIAMClient_VerifiableCredentials(t *testing.T) { return "", errors.New("signature failed") }) - response, err := ctx.client.VerifiableCredentials(context.Background(), ctx.openIDCredentialIssuerMetadata.CredentialEndpoint, accessToken, walletDID, ctx.issuerDID) + response, err := ctx.client.VerifiableCredentials(context.Background(), ctx.openIDCredentialIssuerMetadata.CredentialEndpoint, accessToken, &cNonce, walletDID, ctx.issuerDID) assert.EqualError(t, err, "failed to sign the JWT with kid (did:web:test.test:iam:123#1): signature failed") assert.Nil(t, response)