Implement OIDC4VCI Credential Endpoint#369
Implement OIDC4VCI Credential Endpoint#369andresuribe87 wants to merge 36 commits intoTBD54566975:mainfrom
Conversation
Codecov Report
@@ Coverage Diff @@
## main #369 +/- ##
==========================================
- Coverage 20.23% 20.23% -0.01%
==========================================
Files 42 50 +8
Lines 4675 4928 +253
==========================================
+ Hits 946 997 +51
- Misses 3597 3790 +193
- Partials 132 141 +9
... and 6 files with indirect coverage changes Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
|
|
||
| // Introspect extracts a token from the `Authorization` header, and determines whether it's active by using the | ||
| // Endpoint configured. A `nil` error represents an active token. | ||
| func (s introspecter) introspect(ctx context.Context, req *http.Request) error { |
There was a problem hiding this comment.
nit: i introspector ?
| err := body.Close() | ||
| if err != nil { | ||
| logrus.WithError(err).Warn("closing body") | ||
| } |
There was a problem hiding this comment.
| err := body.Close() | |
| if err != nil { | |
| logrus.WithError(err).Warn("closing body") | |
| } | |
| if err := body.Close(); err != nil { | |
| logrus.WithError(err).Warn("closing body") | |
| } |
| @@ -0,0 +1,122 @@ | |||
| package middleware | |||
There was a problem hiding this comment.
I am wondering if it may be confusing to co-locate server code for both binaries. what do you tink about having a duplicate file structure for oidc stuff? or, alliteratively a set of oidc packages within existing packages?
| return nil | ||
| } | ||
|
|
||
| func simpleOauthTokenServer() *httptest.Server { |
There was a problem hiding this comment.
| func simpleOauthTokenServer() *httptest.Server { | |
| func simpleOAuthTokenServer() *httptest.Server { |
| ClientSecret: "", | ||
| TokenURL: mockTokenServer.URL, | ||
| Scopes: []string{"notsurewhatscope"}, | ||
| EndpointParams: nil, |
There was a problem hiding this comment.
can remove this and client secret?
| } | ||
|
|
||
| // CredentialError implements https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-response | ||
| type CredentialError struct { |
There was a problem hiding this comment.
OIDCCredentialError?
| oidcRouter, err := router.NewOIDCCredentialRouter(oidc.NewOIDCService( | ||
| didService.GetResolver(), | ||
| credService, | ||
| [32]byte{ |
There was a problem hiding this comment.
where's this data coming from?
| err = oidcRouter.IssueCredential(newRequestContext(), w, req) | ||
| require.NoError(t, err) | ||
|
|
||
| var errResp2 map[string]any |
There was a problem hiding this comment.
nit: favor make for maps
| require.Equal(t, 120, credentialResponse.CNonceExpiresIn) | ||
|
|
||
| // And do it again, but after the expiration time. We should now expect an error | ||
| <-time.After(time.Duration(int64(errorResp["c_nonce_expires_in"].(float64)) * int64(time.Second))) |
| var subject string | ||
| var err error | ||
| switch credRequest.Proof.ProofType { | ||
| case "jwt": |
There was a problem hiding this comment.
can we use a known type for this. think we have one in the sdk
| Secret: serviceKey[:], | ||
| }) | ||
| if err != nil { | ||
| panic(err) |
There was a problem hiding this comment.
this should return (*Service, error)
| key, err := totp.Generate(totp.GenerateOpts{ | ||
| Issuer: "ssi-service", |
There was a problem hiding this comment.
should these values come from config?
|
|
||
| func (s Service) CredentialEndpoint(ctx context.Context, credRequest *model.CredentialRequest) (*model.CredentialResponse, error) { | ||
| if credRequest.Format == "" { | ||
| return nil, framework.NewRequestErrorMsg("invalid_request", http.StatusBadRequest) |
There was a problem hiding this comment.
would consider creating a const block of errors ...
const ( InvalidRequest OurErrorType = "invalid_request" ... )
| return nil, errors.New("proof_type not recognized") | ||
| } | ||
|
|
||
| serviceResp, err := s.credService.GetCredentialsBySubject(ctx, credential.GetCredentialBySubjectRequest{Subject: subject}) |
There was a problem hiding this comment.
this logic wasn't too straightforward for me to understand...we're getting all credentials for a subject, and then comparing the object model to see if it matches the passed in cred?
I think we can consider different paths for credential equality. if the entire object needs to match, can we simplify to the id? What about a triple of id, schema, issuer or issuanceDate? id is probably sufficient...
|
|
||
| serviceResp, err := s.credService.GetCredentialsBySubject(ctx, credential.GetCredentialBySubjectRequest{Subject: subject}) | ||
| if err != nil { | ||
| return nil, err |
| return nil, errors.New("no credential found") | ||
| } | ||
|
|
||
| func sameElements(arr1, arr2 []string) bool { |
There was a problem hiding this comment.
does reflect.DeepEquals work as a replacement for this?
| if len(message.Signatures()) != 1 { | ||
| return "", errors.New("jwt expected to have exactly one signature") | ||
| } | ||
| headers := message.Signatures()[0].ProtectedHeaders() |
There was a problem hiding this comment.
careful to avoid NPE if no signatures
| headers := message.Signatures()[0].ProtectedHeaders() | ||
|
|
||
| // - typ: REQUIRED. MUST be openid4vci-proof+jwt, which explicitly types the proof JWT as recommended in Section 3.11 of [RFC8725]. | ||
| const openID4VCIType = "openid4vci-proof+jwt" |
There was a problem hiding this comment.
could this const be pulled out of the method defn?
| func (s Service) isNonceValid(nonce string) bool { | ||
| valid, err := totp.ValidateCustom(nonce, s.key.Secret(), time.Now(), s.validateOpts()) | ||
| if err != nil { | ||
| logrus.WithError(err).Error("Problem validating") |
There was a problem hiding this comment.
if there's an err should we return false?
what if valid == true and err == something?
| } | ||
| } | ||
|
|
||
| // Introspect extracts a token from the `Authorization` header, and determines whether it's active by using the |
There was a problem hiding this comment.
There is no plug in libraries for this?
| func (s *SSIServer) OIDCAPI(service svcframework.Service) error { | ||
| r, err := router.NewOIDCCredentialRouter(service) | ||
| if err != nil { | ||
| return err |
There was a problem hiding this comment.
| return err | |
| return util.LoggingErrorMsg(err, "could not create OIDC router") | |
nitro-neal
left a comment
There was a problem hiding this comment.
There seems to be a lot low level code parsing url requests and stuff,
Are there any existing libraries we can use to do some of this heavy lifting?
My friend Chad gave me some examples:
Golang OAuth2 Library: https://pkg.go.dev/golang.org/x/oauth2
osin: https://github.com/RangelReale/osin
oauth2_proxy: https://github.com/oauth2-proxy/oauth2-proxy
Go-Guardian: https://github.com/shaj13/go-guardian
Overview
This change implements https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-endpoint
Description
In order to support this, some prerequisites were implemented:
introspectthat can be set on a per handler basis. It's responsible for gating access to the endpoint defined by the handler.The implementation itself includes:
How Has This Been Tested?
Multiple unit tests.
Checklist
Before submitting this PR, please make sure:
References
https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-endpoint