diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000000..bd138bacac --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,8 @@ +run: + tests: false +issues: + exclude-files: + - ".*/generated.go" + - ".*/.*_test.go" + - ".*/test.go" + - "docs/.*" \ No newline at end of file diff --git a/auth/api/iam/api.go b/auth/api/iam/api.go index f4d5963762..05f9936a38 100644 --- a/auth/api/iam/api.go +++ b/auth/api/iam/api.go @@ -60,7 +60,8 @@ var oauthRequestObjectKey = []string{"oauth", "requestobject"} const apiPath = "iam" const apiModuleName = auth.ModuleName + "/" + apiPath -const httpRequestContextKey = "http-request" + +type httpRequestContextKey struct{} // accessTokenValidity defines how long access tokens are valid. // TODO: Might want to make this configurable at some point @@ -87,7 +88,6 @@ type Wrapper struct { auth auth.AuthenticationServices policyBackend policy.PDPBackend storageEngine storage.Engine - keyStore cryptoNuts.KeyStore vcr vcr.VCR vdr vdr.VDR jwtSigner cryptoNuts.JWTSigner @@ -147,7 +147,7 @@ func middleware(ctx echo.Context, operationID string) { ctx.Set(core.ModuleNameContextKey, apiModuleName) // Add http.Request to context, to allow reading URL query parameters - requestCtx := context.WithValue(ctx.Request().Context(), httpRequestContextKey, ctx.Request()) + requestCtx := context.WithValue(ctx.Request().Context(), httpRequestContextKey{}, ctx.Request()) ctx.SetRequest(ctx.Request().WithContext(requestCtx)) if strings.HasPrefix(ctx.Request().URL.Path, "/oauth2/") { ctx.Set(core.ErrorWriterContextKey, &oauth.Oauth2ErrorWriter{ @@ -284,7 +284,7 @@ func (r Wrapper) IntrospectAccessToken(_ context.Context, request IntrospectAcce if token.InputDescriptorConstraintIdMap != nil { for _, reserved := range []string{"iss", "sub", "exp", "iat", "active", "client_id", "scope"} { if _, exists := token.InputDescriptorConstraintIdMap[reserved]; exists { - return nil, errors.New(fmt.Sprintf("IntrospectAccessToken: InputDescriptorConstraintIdMap contains reserved claim name '%s'", reserved)) + return nil, fmt.Errorf("IntrospectAccessToken: InputDescriptorConstraintIdMap contains reserved claim name '%s'", reserved) } } response.AdditionalProperties = token.InputDescriptorConstraintIdMap @@ -302,7 +302,7 @@ func (r Wrapper) HandleAuthorizeRequest(ctx context.Context, request HandleAutho // Workaround: deepmap codegen doesn't support dynamic query parameters. // See https://github.com/deepmap/oapi-codegen/issues/1129 - httpRequest := ctx.Value(httpRequestContextKey).(*http.Request) + httpRequest := ctx.Value(httpRequestContextKey{}).(*http.Request) queryParams := httpRequest.URL.Query() // parse and validate as JAR (RFC9101, JWT Authorization Request) @@ -748,38 +748,31 @@ func (r Wrapper) CallbackOid4vciCredentialIssuance(ctx context.Context, request tokenEndpoint := oid4vciSession.IssuerTokenEndpoint credentialEndpoint := oid4vciSession.IssuerCredentialEndpoint if err != nil { - log.Logger().WithError(err).Error("cannot fetch the right endpoints") return nil, withCallbackURI(oauthError(oauth.ServerError, fmt.Sprintf("cannot fetch the right endpoints: %s", err.Error())), oid4vciSession.remoteRedirectUri()) } response, err := r.auth.IAMClient().AccessToken(ctx, code, *issuerDid, oid4vciSession.RedirectUri, *holderDid, pkceParams.Verifier) if err != nil { - 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()) } cNonce := response.Get(oauth.CNonceParam) proofJWT, err := r.proofJwt(ctx, *holderDid, *issuerDid, &cNonce) if err != nil { - log.Logger().WithError(err).Error("error while building proof") - return nil, withCallbackURI(oauthError(oauth.ServerError, fmt.Sprintf("error while fetching the credential from endpoint %s, error: %s", credentialEndpoint, err.Error())), oid4vciSession.remoteRedirectUri()) + return nil, withCallbackURI(oauthError(oauth.ServerError, fmt.Sprintf("error building proof to fetch the credential from endpoint %s, error: %s", credentialEndpoint, err.Error())), oid4vciSession.remoteRedirectUri()) } credentials, err := r.auth.IAMClient().VerifiableCredentials(ctx, credentialEndpoint, response.AccessToken, proofJWT) 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()) } credential, err := vc.ParseVerifiableCredential(credentials.Credential) if err != nil { - log.Logger().WithError(err).Errorf("error while parsing the credential: %s", credentials.Credential) return nil, withCallbackURI(oauthError(oauth.ServerError, fmt.Sprintf("error while parsing the credential: %s, error: %s", credentials.Credential, err.Error())), oid4vciSession.remoteRedirectUri()) } err = r.vcr.Verifier().Verify(*credential, true, true, nil) if err != nil { - log.Logger().WithError(err).Errorf("error while verifying the credential from issuer: %s", credential.Issuer.String()) return nil, withCallbackURI(oauthError(oauth.ServerError, fmt.Sprintf("error while verifying the credential from issuer: %s, error: %s", credential.Issuer.String(), err.Error())), oid4vciSession.remoteRedirectUri()) } err = r.vcr.Wallet().Put(ctx, *credential) if err != nil { - log.Logger().WithError(err).Errorf("error while storing credential with id: %s", credential.ID) return nil, withCallbackURI(oauthError(oauth.ServerError, fmt.Sprintf("error while storing credential with id: %s, error: %s", credential.ID, err.Error())), oid4vciSession.remoteRedirectUri()) } @@ -795,19 +788,18 @@ func (r Wrapper) openidIssuerEndpoints(ctx context.Context, issuerDid did.DID) ( if err != nil { return "", "", "", err } - for i := range metadata.AuthorizationServers { - serverURL := metadata.AuthorizationServers[i] - openIdConfiguration, err := r.auth.IAMClient().OpenIdConfiguration(ctx, serverURL) - if err != nil { - return "", "", "", err - } - authorizationEndpoint := openIdConfiguration.AuthorizationEndpoint - tokenEndpoint := openIdConfiguration.TokenEndpoint - credentialEndpoint := metadata.CredentialEndpoint - return authorizationEndpoint, tokenEndpoint, credentialEndpoint, nil + if len(metadata.AuthorizationServers) == 0 { + return "", "", "", fmt.Errorf("cannot locate any authorization endpoint in %s", issuerDid.String()) + } + serverURL := metadata.AuthorizationServers[0] + openIdConfiguration, err := r.auth.IAMClient().OpenIdConfiguration(ctx, serverURL) + if err != nil { + return "", "", "", err } - err = errors.New(fmt.Sprintf("cannot locate any authorization endpoint in %s", issuerDid.String())) - return "", "", "", err + authorizationEndpoint := openIdConfiguration.AuthorizationEndpoint + tokenEndpoint := openIdConfiguration.TokenEndpoint + credentialEndpoint := metadata.CredentialEndpoint + return authorizationEndpoint, tokenEndpoint, credentialEndpoint, nil } // CreateAuthorizationRequest creates an OAuth2.0 authorizationRequest redirect URL that redirects to the authorization server. diff --git a/auth/api/iam/api_test.go b/auth/api/iam/api_test.go index e7be19d43e..5b703dede3 100644 --- a/auth/api/iam/api_test.go +++ b/auth/api/iam/api_test.go @@ -1412,7 +1412,7 @@ func requestContext(queryParams map[string]interface{}) context.Context { RawQuery: vals.Encode(), }, } - return context.WithValue(audit.TestContext(), httpRequestContextKey, httpRequest) + return context.WithValue(audit.TestContext(), httpRequestContextKey{}, httpRequest) } // statusCodeFrom returns the statuscode for the given error diff --git a/auth/api/iam/openid4vp.go b/auth/api/iam/openid4vp.go index 24bc7e41c9..7caf4d2679 100644 --- a/auth/api/iam/openid4vp.go +++ b/auth/api/iam/openid4vp.go @@ -110,7 +110,7 @@ func (r Wrapper) handleAuthorizeRequestFromHolder(ctx context.Context, verifier } metadata, err := r.auth.IAMClient().AuthorizationServerMetadata(ctx, *walletDID) if err != nil { - return nil, withCallbackURI(oauthError(oauth.ServerError, "failed to get metadata from wallet"), redirectURL) + return nil, withCallbackURI(oauthError(oauth.ServerError, "failed to get metadata from wallet", err), redirectURL) } // check metadata for supported client_id_schemes if !slices.Contains(metadata.ClientIdSchemesSupported, didScheme) { @@ -408,7 +408,7 @@ func (r Wrapper) handleAuthorizeResponseSubmission(ctx context.Context, request pexEnvelope, err := pe.ParseEnvelope([]byte(*request.Body.VpToken)) if err != nil || len(pexEnvelope.Presentations) == 0 { - return nil, oauthError(oauth.InvalidRequest, "invalid vp_token") + return nil, oauthError(oauth.InvalidRequest, "invalid vp_token", err) } // note: instead of using the challenge to lookup the oauth session, we could also add a client state from the verifier. @@ -416,12 +416,12 @@ func (r Wrapper) handleAuthorizeResponseSubmission(ctx context.Context, request // extract the nonce from the vp(s) nonce, err := extractChallenge(pexEnvelope.Presentations[0]) - if nonce == "" { - return nil, oauthError(oauth.InvalidRequest, "failed to extract nonce from vp_token") + if nonce == "" || err != nil { + return nil, oauthError(oauth.InvalidRequest, "failed to extract nonce from vp_token", err) } var stateFromNonce string if err = r.oauthNonceStore().Get(nonce, &stateFromNonce); err != nil { - return nil, oauthError(oauth.InvalidRequest, "invalid or expired nonce") + return nil, oauthError(oauth.InvalidRequest, "invalid or expired nonce", err) } // Retrieve session through state, since we need to update it given the state. // Also asserts that nonce and state reference the same OAuthSession @@ -431,7 +431,7 @@ func (r Wrapper) handleAuthorizeResponseSubmission(ctx context.Context, request return nil, oauthError(oauth.InvalidRequest, "invalid nonce/state") } if err = r.oauthClientStateStore().Get(state, &session); err != nil { - return nil, oauthError(oauth.InvalidRequest, "invalid or expired session") + return nil, oauthError(oauth.InvalidRequest, "invalid or expired session", err) } // any future error can be sent to the client using the redirectURI from the oauthSession @@ -553,6 +553,9 @@ func (r Wrapper) validatePresentationNonce(presentations []vc.VerifiablePresenta var returnErr error for _, presentation := range presentations { nextNonce, err := extractChallenge(presentation) + if err != nil { + return err + } if nextNonce == "" { // fallback on nonce instead of challenge, todo: should be uniform, check vc data model specs for JWT/JSON-LD nextNonce, err = extractNonce(presentation) @@ -595,7 +598,7 @@ func (r Wrapper) handleAccessTokenRequest(ctx context.Context, request HandleTok var oauthSession OAuthSession err := r.oauthCodeStore().Get(*request.Code, &oauthSession) if err != nil { - return nil, oauthError(oauth.InvalidGrant, "invalid authorization code") + return nil, oauthError(oauth.InvalidGrant, "invalid authorization code", err) } // check if the client_id matches the one from the authorization request if oauthSession.ClientID != *request.ClientId { @@ -607,14 +610,6 @@ func (r Wrapper) handleAccessTokenRequest(ctx context.Context, request HandleTok return nil, oauthError(oauth.InvalidGrant, "invalid code_verifier") } - var submissions []PresentationSubmission - for _, submission := range oauthSession.OpenID4VPVerifier.Submissions { - submissions = append(submissions, submission) - } - presentationDefinitions := make([]PresentationDefinition, 0) - for _, curr := range oauthSession.OpenID4VPVerifier.RequiredPresentationDefinitions { - presentationDefinitions = append(presentationDefinitions, curr) - } walletDID, err := did.ParseDID(oauthSession.ClientID) if err != nil { return nil, err @@ -664,7 +659,7 @@ func (r Wrapper) handleCallback(ctx context.Context, request CallbackRequestObje } // lookup client state if err := r.oauthClientStateStore().Get(*request.Params.State, &oauthSession); err != nil { - return nil, oauthError(oauth.InvalidRequest, "invalid or expired state") + return nil, oauthError(oauth.InvalidRequest, "invalid or expired state", err) } // extract callback URI at calling app from OAuthSession // this is the URI where the user-agent will be redirected to @@ -702,9 +697,10 @@ func (r Wrapper) oauthNonceStore() storage.SessionStore { return r.storageEngine.GetSessionDatabase().GetStore(oAuthFlowTimeout, oauthNonceKey...) } -func oauthError(code oauth.ErrorCode, description string) oauth.OAuth2Error { +func oauthError(code oauth.ErrorCode, description string, internalError ...error) oauth.OAuth2Error { return oauth.OAuth2Error{ - Code: code, - Description: description, + Code: code, + Description: description, + InternalError: errors.Join(internalError...), } } diff --git a/auth/api/iam/params.go b/auth/api/iam/params.go index 72fc1eb82b..8c66ad3721 100644 --- a/auth/api/iam/params.go +++ b/auth/api/iam/params.go @@ -18,8 +18,6 @@ package iam -import "net/url" - // oauthParameters is a helper for oauth params. // oauth params can be derived from query params or JWT claims (RFC9101). // in theory all params could be string, arrays or numbers. Our handlers only want single string values for all params. @@ -27,14 +25,6 @@ import "net/url" // array values with len == 1 will be treated as single string values. type oauthParameters map[string]interface{} -func parseQueryParams(values url.Values) oauthParameters { - underlying := make(map[string]interface{}) - for key, value := range values { - underlying[key] = value - } - return underlying -} - func parseJWTClaims(claims map[string]interface{}) oauthParameters { return claims } // get returns the string value if present and if an actual string diff --git a/auth/api/iam/s2s_vptoken.go b/auth/api/iam/s2s_vptoken.go index c409dfda56..ae58d0b555 100644 --- a/auth/api/iam/s2s_vptoken.go +++ b/auth/api/iam/s2s_vptoken.go @@ -131,9 +131,7 @@ func (r Wrapper) createAccessToken(issuer did.DID, walletDID did.DID, issueTime InputDescriptorConstraintIdMap: fieldsMap, } for _, envelope := range pexState.SubmittedEnvelopes { - for _, presentation := range envelope.Presentations { - accessToken.VPToken = append(accessToken.VPToken, presentation) - } + accessToken.VPToken = append(accessToken.VPToken, envelope.Presentations...) } err = r.accessTokenServerStore().Put(accessToken.Token, accessToken) if err != nil { diff --git a/auth/api/iam/types.go b/auth/api/iam/types.go index 0ca460fe0d..76b3907736 100644 --- a/auth/api/iam/types.go +++ b/auth/api/iam/types.go @@ -25,7 +25,6 @@ import ( "github.com/nuts-foundation/nuts-node/vcr/pe" "github.com/nuts-foundation/nuts-node/vdr/resolver" "net/http" - "time" ) // DIDDocument is an alias @@ -66,10 +65,6 @@ type WalletOwnerType = pe.WalletOwnerType // RequiredPresentationDefinitions is an alias type RequiredPresentationDefinitions = pe.WalletOwnerMapping -const ( - sessionExpiry = 5 * time.Minute -) - // CookieReader is an interface for reading cookies from an HTTP request. // It is implemented by echo.Context and http.Request. type CookieReader interface { @@ -159,11 +154,3 @@ const presentationDefParam = "presentation_definition" // presentationDefUriParam is the name of the OpenID4VP presentation_definition_uri parameter. // Specified by https://openid.bitbucket.io/connect/openid-4-verifiable-presentations-1_0.html#name-presentation_definition_uri const presentationDefUriParam = "presentation_definition_uri" - -// presentationSubmissionParam is the name of the OpenID4VP presentation_submission parameter. -// Specified by https://openid.bitbucket.io/connect/openid-4-verifiable-presentations-1_0.html#name-response-parameters -const presentationSubmissionParam = "presentation_submission" - -// vpTokenParam is the name of the OpenID4VP vp_token parameter. -// Specified by https://openid.bitbucket.io/connect/openid-4-verifiable-presentations-1_0.html#name-response-type-vp_token -const vpTokenParam = "vp_token" diff --git a/auth/client/iam/client.go b/auth/client/iam/client.go index e37aeab8b5..f01a0addb6 100644 --- a/auth/client/iam/client.go +++ b/auth/client/iam/client.go @@ -154,7 +154,7 @@ func (hb HTTPClient) AccessToken(ctx context.Context, tokenEndpoint string, data if len(responseBodyString) > core.HttpResponseBodyLogClipAt { responseBodyString = responseBodyString[:core.HttpResponseBodyLogClipAt] + "...(clipped)" } - return token, fmt.Errorf("unable to unmarshal response: %w, %s", err, string(responseData)) + return token, fmt.Errorf("unable to unmarshal response: %w, %s", err, responseBodyString) } return token, nil } @@ -231,7 +231,6 @@ type CredentialResponse struct { } func (hb HTTPClient) VerifiableCredentials(ctx context.Context, credentialEndpoint string, accessToken string, proofJwt string) (*CredentialResponse, error) { - credentialEndpointURL, err := url.Parse(credentialEndpoint) if err != nil { return nil, err @@ -243,7 +242,7 @@ func (hb HTTPClient) VerifiableCredentials(ctx context.Context, credentialEndpoi Jwt: proofJwt, }, } - jsonBody, err := json.Marshal(credentialRequest) + jsonBody, _ := json.Marshal(credentialRequest) request, err := http.NewRequestWithContext(ctx, http.MethodPost, credentialEndpointURL.String(), bytes.NewBuffer(jsonBody)) if err != nil { return nil, err diff --git a/auth/services/dummy/dummy.go b/auth/services/dummy/dummy.go index d31b41003e..ecfd0585eb 100644 --- a/auth/services/dummy/dummy.go +++ b/auth/services/dummy/dummy.go @@ -182,8 +182,7 @@ func (d signingSessionResult) VerifiablePresentation() (*vc.VerifiablePresentati }, nil } -func (d Dummy) Start(ctx context.Context) { - return +func (d Dummy) Start(_ context.Context) { } // VerifyVP check a Dummy VerifiablePresentation. It Returns a verificationResult if all was fine, an error otherwise. @@ -264,7 +263,7 @@ func (d Dummy) StartSigningSession(contract contract.Contract, params map[string return nil, errNotEnabled } sessionBytes := make([]byte, 16) - rand.Reader.Read(sessionBytes) + _, _ = rand.Reader.Read(sessionBytes) sessionID := hex.EncodeToString(sessionBytes) d.Status[sessionID] = SessionCreated diff --git a/auth/services/irma/signer.go b/auth/services/irma/signer.go index 25401506eb..e0c30530b1 100644 --- a/auth/services/irma/signer.go +++ b/auth/services/irma/signer.go @@ -77,7 +77,6 @@ func (s SessionPtr) MarshalJSON() ([]byte, error) { const NutsIrmaSignedContract = "NutsIrmaSignedContract" func (v Signer) Start(ctx context.Context) { - return } // StartSigningSession accepts a rawContractText and creates an IRMA signing session. diff --git a/auth/services/selfsigned/signer.go b/auth/services/selfsigned/signer.go index 70c93d3800..07fcdb0ffa 100644 --- a/auth/services/selfsigned/signer.go +++ b/auth/services/selfsigned/signer.go @@ -146,7 +146,6 @@ func (v *signer) createVP(ctx context.Context, s types.Session, issuanceDate tim func (v *signer) Start(ctx context.Context) { v.store.Start(ctx) - return } func (v *signer) StartSigningSession(userContract contract.Contract, params map[string]interface{}) (contract.SessionPointer, error) { diff --git a/cmd/root.go b/cmd/root.go index 5bf28ef138..46b2ed3ffe 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -136,7 +136,9 @@ func startServer(ctx context.Context, system *core.System) error { if err != nil { return err } - pprof.StartCPUProfile(f) + if err := pprof.StartCPUProfile(f); err != nil { + return err + } defer pprof.StopCPUProfile() } else { logrus.Warn("Ignoring CPU profile option, strictmode is enabled") diff --git a/core/engine.go b/core/engine.go index 4a154e6a86..e209664c01 100644 --- a/core/engine.go +++ b/core/engine.go @@ -179,7 +179,7 @@ func (system *System) VisitEnginesE(visitor func(engine Engine) error) error { // FindEngineByName looks up given target engine by name, or nil if not found. func (system *System) FindEngineByName(target string) Engine { for _, curr := range system.engines { - if strings.ToLower(engineName(curr)) == strings.ToLower(target) { + if strings.EqualFold(engineName(curr), strings.ToLower(target)) { return curr } } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 9b233841bb..c48fe585c8 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -45,7 +45,8 @@ func TestCrypto_Exists(t *testing.T) { client := createCrypto(t) kid := "kid" - client.New(audit.TestContext(), StringNamingFunc(kid)) + _, err := client.New(audit.TestContext(), StringNamingFunc(kid)) + require.NoError(t, err) t.Run("returns true for existing key", func(t *testing.T) { assert.True(t, client.Exists(ctx, kid)) diff --git a/crypto/jwx.go b/crypto/jwx.go index 651ce5e976..bc2ba04b7f 100644 --- a/crypto/jwx.go +++ b/crypto/jwx.go @@ -138,11 +138,13 @@ func jwkKey(signer crypto.Signer) (key jwk.Key, err error) { switch k := signer.(type) { case *rsa.PrivateKey: - key.Set(jwk.AlgorithmKey, jwa.PS256) + _ = key.Set(jwk.AlgorithmKey, jwa.PS256) case *ecdsa.PrivateKey: var alg jwa.SignatureAlgorithm alg, err = ecAlg(k) - key.Set(jwk.AlgorithmKey, alg) + if err == nil { + _ = key.Set(jwk.AlgorithmKey, alg) + } default: err = errors.New("unsupported signing private key") } @@ -430,27 +432,13 @@ func SignatureAlgorithm(key crypto.PublicKey) (jwa.SignatureAlgorithm, error) { } func encryptionAlgorithm(key crypto.PublicKey) (jwa.KeyEncryptionAlgorithm, error) { - var ptr interface{} - switch v := key.(type) { - case crypto.PublicKey: - ptr = &v - case rsa.PublicKey: - ptr = &v - case ecdsa.PublicKey: - ptr = &v - default: - ptr = v - } - - switch ptr.(type) { - case *crypto.PublicKey: - return defaultEcEncryptionAlgorithm, nil + switch key.(type) { case *rsa.PublicKey: return defaultRsaEncryptionAlgorithm, nil case *ecdsa.PublicKey: return defaultEcEncryptionAlgorithm, nil default: - return "", fmt.Errorf("could not determine signature algorithm for key type '%T'", key) + return "", fmt.Errorf("could not determine encryption algorithm for key type '%T'", key) } } diff --git a/crypto/jwx_test.go b/crypto/jwx_test.go index 495a258c08..0ca937de98 100644 --- a/crypto/jwx_test.go +++ b/crypto/jwx_test.go @@ -248,7 +248,7 @@ func TestCrypto_EncryptJWE(t *testing.T) { public := key.Public() headers := map[string]interface{}{"typ": "JWT", "kid": key.KID()} - t.Run("creates valid JWE", func(t *testing.T) { + t.Run("creates valid JWE (EC)", func(t *testing.T) { payload, _ := json.Marshal(map[string]interface{}{"iss": "nuts"}) tokenString, err := client.EncryptJWE(audit.TestContext(), payload, headers, public) @@ -267,6 +267,23 @@ func TestCrypto_EncryptJWE(t *testing.T) { assert.Equal(t, "nuts", body["iss"]) }) + t.Run("creates valid JWE (RSA)", func(t *testing.T) { + keyPair, _ := rsa.GenerateKey(rand.Reader, 1024) + payload, _ := json.Marshal(map[string]interface{}{"iss": "nuts"}) + tokenString, err := client.EncryptJWE(audit.TestContext(), payload, headers, keyPair.Public()) + + require.NoError(t, err) + + token, err := jwe.Decrypt([]byte(tokenString), jwe.WithKey(defaultRsaEncryptionAlgorithm, keyPair)) + require.NoError(t, err) + + var body = make(map[string]interface{}) + err = json.Unmarshal(token, &body) + + require.NoError(t, err) + + assert.Equal(t, "nuts", body["iss"]) + }) t.Run("creates valid JWE, alt alg", func(t *testing.T) { payload, _ := json.Marshal(map[string]interface{}{"iss": "nuts"}) headers := map[string]interface{}{"typ": "JWT", "alg": "ECDH-ES"} @@ -319,7 +336,7 @@ func TestCrypto_EncryptJWE(t *testing.T) { _, err := client.EncryptJWE(audit.TestContext(), []byte{1, 2, 3}, headers, public) require.NoError(t, err) - auditLogs.AssertContains(t, ModuleName, "EncryptJWE", audit.TestActor, fmt.Sprintf("Encrypting a JWE")) + auditLogs.AssertContains(t, ModuleName, "EncryptJWE", audit.TestActor, "Encrypting a JWE") }) } diff --git a/discovery/api/v1/api.go b/discovery/api/v1/api.go index b2a59c226a..39f03be681 100644 --- a/discovery/api/v1/api.go +++ b/discovery/api/v1/api.go @@ -32,7 +32,7 @@ import ( var _ StrictServerInterface = (*Wrapper)(nil) -const requestQueryContextKey = "request.url.query" +type requestQueryContextKey struct{} type Wrapper struct { Client discovery.Client @@ -48,7 +48,7 @@ func (w *Wrapper) Routes(router core.EchoRouter) { // deepmap/openapi codegen does not support dynamic query parameters ("exploded form parameters"), // so we expose the request URL query parameters to the request context, // so the API handler can use them directly. - newContext := context.WithValue(ctx.Request().Context(), requestQueryContextKey, ctx.Request().URL.Query()) + newContext := context.WithValue(ctx.Request().Context(), requestQueryContextKey{}, ctx.Request().URL.Query()) newRequest := ctx.Request().WithContext(newContext) ctx.SetRequest(newRequest) return f(ctx, request) @@ -62,7 +62,7 @@ func (w *Wrapper) Routes(router core.EchoRouter) { func (w *Wrapper) SearchPresentations(ctx context.Context, request SearchPresentationsRequestObject) (SearchPresentationsResponseObject, error) { // Use query parameters provided in request context (see Routes()) - queryValues := ctx.Value(requestQueryContextKey).(url.Values) + queryValues := ctx.Value(requestQueryContextKey{}).(url.Values) query := make(map[string]string) for path, values := range queryValues { query[path] = values[0] diff --git a/discovery/api/v1/api_test.go b/discovery/api/v1/api_test.go index 2cc1e12ec5..1c0d1e3635 100644 --- a/discovery/api/v1/api_test.go +++ b/discovery/api/v1/api_test.go @@ -108,7 +108,7 @@ func TestWrapper_DeactivateServiceForDID(t *testing.T) { } func TestWrapper_SearchPresentations(t *testing.T) { - ctx := context.WithValue(audit.TestContext(), requestQueryContextKey, url.Values{ + ctx := context.WithValue(audit.TestContext(), requestQueryContextKey{}, url.Values{ "foo": []string{"bar"}, }) expectedQuery := map[string]string{ diff --git a/discovery/interface.go b/discovery/interface.go index 45e9e8c3c8..e9fd655633 100644 --- a/discovery/interface.go +++ b/discovery/interface.go @@ -23,7 +23,6 @@ import ( "errors" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/go-did/vc" - "math" "strconv" "strings" ) @@ -50,10 +49,6 @@ func (t Tag) Timestamp(tagPrefix string) *Timestamp { // Not a number return nil } - if result < 0 || result > math.MaxUint64 { - // Invalid uint64 - return nil - } lamport := Timestamp(result) return &lamport } diff --git a/discovery/module.go b/discovery/module.go index 827d13f60f..06674477e3 100644 --- a/discovery/module.go +++ b/discovery/module.go @@ -194,7 +194,7 @@ func (m *Module) verifyRegistration(definition ServiceDefinition, presentation v return errors.Join(ErrInvalidPresentation, errPresentationWithoutExpiration) } // VPs should not be valid for too long, as that would prevent the server from pruning them. - if int(expiration.Sub(time.Now()).Seconds()) > definition.PresentationMaxValidity { + if time.Until(expiration) > time.Duration(definition.PresentationMaxValidity)*time.Second { return errors.Join(ErrInvalidPresentation, fmt.Errorf("presentation is valid for too long (max %s)", time.Duration(definition.PresentationMaxValidity)*time.Second)) } // Check if the presentation already exists diff --git a/makefile b/makefile index 3a06e96dbf..0353a22acf 100644 --- a/makefile +++ b/makefile @@ -7,6 +7,7 @@ install-tools: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.33.0 go install go.uber.org/mock/mockgen@v0.4.0 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2 gen-mocks: mockgen -destination=auth/mock.go -package=auth -source=auth/interface.go @@ -102,6 +103,9 @@ all-docs: cli-docs gen-diagrams fix-copyright: go run ./docs copyright +lint: + golangci-lint run -v + test: go test ./... diff --git a/network/dag/signing.go b/network/dag/signing.go index 63a9da7e7a..143dadfddc 100644 --- a/network/dag/signing.go +++ b/network/dag/signing.go @@ -71,7 +71,7 @@ func (d transactionSigner) Sign(ctx context.Context, input UnsignedTransaction, if err != nil { return nil, fmt.Errorf(errSigningTransactionFmt, err) } - key.Set(jwk.KeyIDKey, d.key.KID()) + _ = key.Set(jwk.KeyIDKey, d.key.KID()) } prevsAsString := make([]string, len(input.Previous())) diff --git a/pki/cmd.go b/pki/cmd.go index 329ef33cec..074fe85b45 100644 --- a/pki/cmd.go +++ b/pki/cmd.go @@ -36,7 +36,11 @@ func FlagSet() *pflag.FlagSet { // Changing these config values is not recommended, and they are expected to almost always be the same value, so // do not show them in the config dump - flagSet.MarkHidden("pki.denylist.trustedsigner") - flagSet.MarkHidden("pki.denylist.url") + if err := flagSet.MarkHidden("pki.denylist.trustedsigner"); err != nil { + panic(err) + } + if err := flagSet.MarkHidden("pki.denylist.url"); err != nil { + panic(err) + } return flagSet } diff --git a/pki/denylist.go b/pki/denylist.go index 7859727963..1f836b9a0e 100644 --- a/pki/denylist.go +++ b/pki/denylist.go @@ -248,7 +248,7 @@ func certKeyJWKThumbprint(cert *x509.Certificate) string { if key, _ := jwk.PublicKeyOf(cert.PublicKey); key != nil { // Compute the fingerprint of the key - jwk.AssignKeyID(key) + _ = jwk.AssignKeyID(key) // Retrieve the fingerprint, which annoyingly is an "any" return type fingerprint, _ := key.Get(jwk.KeyIDKey) diff --git a/pki/validator.go b/pki/validator.go index cf0936c86b..3043668a3d 100644 --- a/pki/validator.go +++ b/pki/validator.go @@ -405,8 +405,8 @@ func (v *validator) updateCRL(endpoint string, current *revocationList) error { } // parse revocations - revoked := make(map[string]bool, len(crl.RevokedCertificates)) - for _, rev := range crl.RevokedCertificates { + revoked := make(map[string]bool, len(crl.RevokedCertificateEntries)) + for _, rev := range crl.RevokedCertificateEntries { revoked[rev.SerialNumber.String()] = true } // set the new CRL diff --git a/test/http/handler.go b/test/http/handler.go index 59814aba33..0958d028de 100644 --- a/test/http/handler.go +++ b/test/http/handler.go @@ -57,5 +57,5 @@ func (h *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { writer.Header().Add(k, v[0]) } writer.WriteHeader(h.StatusCode) - writer.Write(bytes) + _, _ = writer.Write(bytes) } diff --git a/vcr/holder/openid.go b/vcr/holder/openid.go index be08732253..e1eef8d807 100644 --- a/vcr/holder/openid.go +++ b/vcr/holder/openid.go @@ -193,6 +193,9 @@ func getPreAuthorizedCodeFromOffer(offer openid4vci.CredentialOffer) string { func (h *openidHandler) retrieveCredential(ctx context.Context, issuerClient openid4vci.IssuerAPIClient, offer *openid4vci.CredentialDefinition, tokenResponse *oauth.TokenResponse) (*vc.VerifiableCredential, error) { keyID, _, err := h.resolver.ResolveKey(h.did, nil, resolver.NutsSigningKeyType) + if err != nil { + return nil, err + } headers := map[string]interface{}{ "typ": openid4vci.JWTTypeOpenID4VCIProof, // MUST be openid4vci-proof+jwt, which explicitly types the proof JWT as recommended in Section 3.11 of [RFC8725]. "kid": keyID.String(), // JOSE Header containing the key ID. If the Credential shall be bound to a DID, the kid refers to a DID URL which identifies a particular key in the DID Document that the Credential shall be bound to. diff --git a/vcr/pe/presentation_definition.go b/vcr/pe/presentation_definition.go index 587597a40a..1b336b49ed 100644 --- a/vcr/pe/presentation_definition.go +++ b/vcr/pe/presentation_definition.go @@ -460,7 +460,7 @@ func matchFilter(filter Filter, value interface{}) (bool, error) { return false, nil } - switch value.(type) { + switch typedValue := value.(type) { case string: if filter.Type != "string" { return false, nil @@ -478,8 +478,7 @@ func matchFilter(filter Filter, value interface{}) (bool, error) { return false, nil } case []interface{}: - values := value.([]interface{}) - for _, v := range values { + for _, v := range typedValue { match, err := matchFilter(filter, v) if err != nil { return false, err diff --git a/vcr/pe/util.go b/vcr/pe/util.go index efd7b0afb7..75ced72c07 100644 --- a/vcr/pe/util.go +++ b/vcr/pe/util.go @@ -101,10 +101,10 @@ func parseJSONArrayEnvelope(arr []interface{}) (interface{}, []vc.VerifiablePres for _, entry := range arr { // Each entry can be a VP as JWT (string) or JSON (object) var entryBytes []byte - switch entry.(type) { + switch typedEntry := entry.(type) { case string: // JWT - entryBytes = []byte(entry.(string)) + entryBytes = []byte(typedEntry) default: var err error entryBytes, err = json.Marshal(entry) diff --git a/vcr/revocation/statuslist2021_issuer.go b/vcr/revocation/statuslist2021_issuer.go index f06892e07c..bde71a4e03 100644 --- a/vcr/revocation/statuslist2021_issuer.go +++ b/vcr/revocation/statuslist2021_issuer.go @@ -437,8 +437,7 @@ func (cs *StatusList2021) Revoke(ctx context.Context, credentialID ssi.URI, entr } // append new revocation and re-issue the StatusList2021Credential. - credRecord := new(credentialRecord) - _, credRecord, err = cs.updateCredential(ctx, issuerRecord, key) + _, credRecord, err := cs.updateCredential(ctx, issuerRecord, key) if err != nil { return err } diff --git a/vcr/vcr.go b/vcr/vcr.go index 040a94bfa4..76a5fa65fc 100644 --- a/vcr/vcr.go +++ b/vcr/vcr.go @@ -240,10 +240,6 @@ func (c *vcr) Configure(config core.ServerConfig) error { return c.trustConfig.Load() } -func (c *vcr) credentialsDBPath() string { - return path.Join(c.datadir, "vcr", "credentials.db") -} - func (c *vcr) createCredentialsStore() error { credentialsStorePath := path.Join(c.datadir, "vcr", "credentials.db") credentialsBackupStore, err := c.storageClient.GetProvider(ModuleName).GetKVStore("backup-credentials", storage.PersistentStorageClass) diff --git a/vcr/vcr_test.go b/vcr/vcr_test.go index 88e1b194fe..79e2fdd985 100644 --- a/vcr/vcr_test.go +++ b/vcr/vcr_test.go @@ -116,7 +116,7 @@ func TestVCR_Start(t *testing.T) { t.Run("ok", func(t *testing.T) { instance := NewTestVCRInstance(t) - _, err := os.Stat(instance.credentialsDBPath()) + _, err := os.Stat(credentialsDBPath(instance.datadir)) assert.NoError(t, err) }) @@ -150,7 +150,7 @@ func TestVCR_Start(t *testing.T) { t.Fatal(err) } - dbPath := instance.credentialsDBPath() + dbPath := credentialsDBPath(instance.datadir) db, err := bbolt.Open(dbPath, os.ModePerm, nil) if err != nil { t.Fatal(err) @@ -454,3 +454,7 @@ func TestWhitespaceOrExactTokenizer(t *testing.T) { assert.Equal(t, []string{"a", "b", "c", "a b c"}, whitespaceOrExactTokenizer(input)) } + +func credentialsDBPath(datadir string) string { + return path.Join(datadir, "vcr", "credentials.db") +} diff --git a/vdr/didnuts/ambassador.go b/vdr/didnuts/ambassador.go index 890b888849..837962b048 100644 --- a/vdr/didnuts/ambassador.go +++ b/vdr/didnuts/ambassador.go @@ -26,21 +26,18 @@ import ( "encoding/json" "errors" "fmt" + "github.com/lestrrat-go/jwx/v2/jwk" "github.com/nats-io/nats.go" + "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/go-stoabs" "github.com/nuts-foundation/nuts-node/core" - "github.com/nuts-foundation/nuts-node/vdr/didnuts/didstore" - "github.com/nuts-foundation/nuts-node/vdr/resolver" - "sort" - - "github.com/lestrrat-go/jwx/v2/jwk" - "github.com/nuts-foundation/go-did/did" nutsCrypto "github.com/nuts-foundation/nuts-node/crypto" - "github.com/nuts-foundation/nuts-node/crypto/hash" "github.com/nuts-foundation/nuts-node/events" "github.com/nuts-foundation/nuts-node/network" "github.com/nuts-foundation/nuts-node/network/dag" + "github.com/nuts-foundation/nuts-node/vdr/didnuts/didstore" "github.com/nuts-foundation/nuts-node/vdr/log" + "github.com/nuts-foundation/nuts-node/vdr/resolver" ) // DIDDocumentType contains network transaction mime-type to identify a DID Document in the network. @@ -349,48 +346,6 @@ func (n *ambassador) resolveControllers(document did.Document, transaction dag.T return controllers, nil } -func sortHashes(input []hash.SHA256Hash) { - sort.Slice(input, func(i, j int) bool { - return bytes.Compare(input[i].Slice(), input[j].Slice()) < 0 - }) -} - -// missingTransactions does: current - incoming. Non conflicted updates will have an empty slice -func missingTransactions(current []hash.SHA256Hash, incoming []hash.SHA256Hash) []hash.SHA256Hash { - j := 0 - for _, h := range current { - found := false - for _, h2 := range incoming { - if h.Equals(h2) { - found = true - break - } - } - if !found { - current[j] = h - j++ - } - } - - return current[:j] -} - -// uniqueTransactions does: Set(current + incoming). -func uniqueTransactions(current []hash.SHA256Hash, incoming hash.SHA256Hash) []hash.SHA256Hash { - set := map[hash.SHA256Hash]bool{} - for _, h := range current { - set[h] = true - } - set[incoming] = true - - list := make([]hash.SHA256Hash, 0) - for k := range set { - list = append(list, k) - } - - return list -} - // checkTransactionIntegrity performs basic integrity checks on the Transaction fields // Some checks may look redundant because they are performed in the callers, this method has the sole // responsibility to ensure integrity, while the other may have not. diff --git a/vdr/didnuts/ambassador_test.go b/vdr/didnuts/ambassador_test.go index 056b575c44..bb9bff2698 100644 --- a/vdr/didnuts/ambassador_test.go +++ b/vdr/didnuts/ambassador_test.go @@ -608,15 +608,6 @@ func TestAmbassador_handleUpdateDIDDocument(t *testing.T) { }) } -func Test_sortHashes(t *testing.T) { - h0 := hash.SHA256Hash{} - h1 := hash.SHA256Hash{1} - h2 := hash.SHA256Hash{2} - input := []hash.SHA256Hash{h2, h0, h1} - sortHashes(input) - assert.Equal(t, []hash.SHA256Hash{h0, h1, h2}, input) -} - func Test_handleUpdateDIDDocument(t *testing.T) { t.Run("error - unable to resolve controllers", func(t *testing.T) { ctrl := gomock.NewController(t) @@ -731,69 +722,6 @@ func Test_checkTransactionIntegrity(t *testing.T) { } } -func Test_missingTransactions(t *testing.T) { - h1 := hash.SHA256Sum([]byte("hash1")) - h2 := hash.SHA256Sum([]byte("hash2")) - h3 := hash.SHA256Sum([]byte("hash3")) - - t.Run("non-conflicted updated as expected", func(t *testing.T) { - current := []hash.SHA256Hash{h1} - incoming := []hash.SHA256Hash{h1, h2} - - diff := missingTransactions(current, incoming) - - assert.Empty(t, diff) - }) - - t.Run("non-conflicted updated without ref", func(t *testing.T) { - current := []hash.SHA256Hash{h1} - incoming := []hash.SHA256Hash{h2} - - diff := missingTransactions(current, incoming) - - assert.Len(t, diff, 1) - assert.Equal(t, current, diff) - }) - - t.Run("conflicted resolved", func(t *testing.T) { - current := []hash.SHA256Hash{h1, h2} - incoming := []hash.SHA256Hash{h1, h2, h3} - - diff := missingTransactions(current, incoming) - - assert.Empty(t, diff) - }) -} - -func Test_uniqueTransactions(t *testing.T) { - h1 := hash.SHA256Sum([]byte("hash1")) - h2 := hash.SHA256Sum([]byte("hash2")) - - t.Run("ok - empty list", func(t *testing.T) { - current := []hash.SHA256Hash{} - - unique := uniqueTransactions(current, h1) - - assert.Len(t, unique, 1) - }) - - t.Run("ok - no overlap", func(t *testing.T) { - current := []hash.SHA256Hash{h2} - - unique := uniqueTransactions(current, h1) - - assert.Len(t, unique, 2) - }) - - t.Run("ok - duplicates", func(t *testing.T) { - current := []hash.SHA256Hash{h1, h2} - - unique := uniqueTransactions(current, h1) - - assert.Len(t, unique, 2) - }) -} - func newDidDocWithOptions(selfControl bool, controllers ...did.DID) (did.Document, jwk.Key, error) { kc := &mockKeyCreator{} docCreator := Creator{KeyStore: kc} diff --git a/vdr/didweb/manager.go b/vdr/didweb/manager.go index a6f824ec1c..47b7978217 100644 --- a/vdr/didweb/manager.go +++ b/vdr/didweb/manager.go @@ -240,11 +240,6 @@ func (m Manager) DeleteService(_ context.Context, subjectDID did.DID, serviceID } func buildDocument(subject did.DID, verificationMethods []did.VerificationMethod, services []did.Service) did.Document { - var vms []*did.VerificationMethod - for _, verificationMethod := range verificationMethods { - vms = append(vms, &verificationMethod) - } - document := did.Document{ Context: []interface{}{ ssi.MustParseURI(jsonld.Jws2020Context), diff --git a/vdr/vdr.go b/vdr/vdr.go index e966bb95f4..22d32ec9b5 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -150,8 +150,7 @@ func (r *Module) Configure(config core.ServerConfig) error { r.didResolver.Register(didkey.MethodName, didkey.NewResolver()) // Initiate the routines for auto-updating the data. - r.networkAmbassador.Configure() - return nil + return r.networkAmbassador.Configure() } func (r *Module) Start() error {