Skip to content

Commit

Permalink
feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
reinkrul committed Dec 6, 2023
1 parent 89fc933 commit 7aa6824
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 25 deletions.
54 changes: 32 additions & 22 deletions auth/api/iam/s2s_vptoken.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,13 @@ import (
// TODO: Might want to make this configurable at some point
const accessTokenValidity = 15 * time.Minute

// maxPresentationValidity defines the maximum validity of a presentation.
const maxPresentationValidity = 10 * time.Second
// s2sMaxPresentationValidity defines the maximum validity of a presentation.
// This is to prevent replay attacks. The value is specified by Nuts RFC021, and excludes max. clock skew.
const s2sMaxPresentationValidity = 5 * time.Second

// s2sMaxClockSkew defines the maximum clock skew between nodes.
// The value is specified by Nuts RFC021.
const s2sMaxClockSkew = 5 * time.Second

// handleS2SAccessTokenRequest handles the /token request with vp_token bearer grant type, intended for service-to-service exchanges.
// It performs cheap checks first (parameter presence and validity, matching VCs to the presentation definition), then the more expensive ones (checking signatures).
Expand All @@ -62,7 +67,7 @@ func (r *Wrapper) handleS2SAccessTokenRequest(issuer did.DID, scope string, subm
}

for _, presentation := range pexEnvelope.Presentations {
if err := validatePresentationValidity(presentation); err != nil {
if err := validateS2SPresentationMaxValidity(presentation); err != nil {
return nil, err
}
if err := validatePresentationSigner(presentation); err != nil {
Expand All @@ -77,7 +82,7 @@ func (r *Wrapper) handleS2SAccessTokenRequest(issuer did.DID, scope string, subm
return nil, err
}
for _, presentation := range pexEnvelope.Presentations {
if err := r.validatePresentationNonce(presentation); err != nil {
if err := r.validateS2SPresentationNonce(presentation); err != nil {
return nil, err
}
}
Expand All @@ -95,7 +100,7 @@ func (r *Wrapper) handleS2SAccessTokenRequest(issuer did.DID, scope string, subm
}

// All OK, allow access
response, err := r.createAccessToken(issuer, time.Now(), pexEnvelope.Presentations, *submission, *definition, scope)
response, err := r.createS2SAccessToken(issuer, time.Now(), pexEnvelope.Presentations, *submission, *definition, scope)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -141,7 +146,7 @@ func (r *Wrapper) RequestAccessToken(ctx context.Context, request RequestAccessT
return RequestAccessToken200JSONResponse(*tokenResult), nil
}

func (r *Wrapper) createAccessToken(issuer did.DID, issueTime time.Time, presentations []vc.VerifiablePresentation,
func (r *Wrapper) createS2SAccessToken(issuer did.DID, issueTime time.Time, presentations []vc.VerifiablePresentation,
submission pe.PresentationSubmission, definition PresentationDefinition, scope string) (*oauth.TokenResponse, error) {
accessToken := AccessToken{
Token: crypto.GenerateNonce(),
Expand Down Expand Up @@ -191,8 +196,8 @@ func (r Wrapper) validatePresentationSubmission(scope string, submission *pe.Pre
return definition, err
}

// validatePresentationValidity checks that the presentation is valid for a reasonable amount of time.
func validatePresentationValidity(presentation vc.VerifiablePresentation) error {
// validateS2SPresentationMaxValidity checks that the presentation is valid for a reasonable amount of time.
func validateS2SPresentationMaxValidity(presentation vc.VerifiablePresentation) error {
created := credential.PresentationIssuanceDate(presentation)
expires := credential.PresentationExpirationDate(presentation)
if created == nil || expires == nil {
Expand All @@ -201,10 +206,10 @@ func validatePresentationValidity(presentation vc.VerifiablePresentation) error
Description: "presentation is missing creation or expiration date",
}
}
if expires.Sub(*created) > maxPresentationValidity {
if expires.Sub(*created) > s2sMaxPresentationValidity {
return oauth.OAuth2Error{
Code: oauth.InvalidRequest,
Description: fmt.Sprintf("presentation is valid for too long (max %s)", maxPresentationValidity),
Description: fmt.Sprintf("presentation is valid for too long (max %s)", s2sMaxPresentationValidity),
}
}
return nil
Expand All @@ -228,8 +233,8 @@ func validatePresentationSigner(presentation vc.VerifiablePresentation) error {
return nil
}

// validatePresentationNonce checks if the nonce has been used before; 'nonce' claim for JWTs or LDProof's 'nonce' for JSON-LD.
func (r *Wrapper) validatePresentationNonce(presentation vc.VerifiablePresentation) error {
// validateS2SPresentationNonce checks if the nonce has been used before; 'nonce' claim for JWTs or LDProof's 'nonce' for JSON-LD.
func (r *Wrapper) validateS2SPresentationNonce(presentation vc.VerifiablePresentation) error {
var nonce string
switch presentation.Format() {
case vc.JWTPresentationProofFormat:
Expand Down Expand Up @@ -259,24 +264,29 @@ func (r *Wrapper) validatePresentationNonce(presentation vc.VerifiablePresentati
nonce = *proof.Nonce
}

nonceStore := r.storageEngine.GetSessionDatabase().GetStore(maxPresentationValidity, "s2s", "nonce")
err := nonceStore.Get(nonce, new(bool))
if !errors.Is(err, storage.ErrNotFound) {
if err != nil {
// unable to check nonce
return err
}
return oauth.OAuth2Error{
nonceStore := r.storageEngine.GetSessionDatabase().GetStore(s2sMaxPresentationValidity+s2sMaxClockSkew, "s2s", "nonce")
nonceError := nonceStore.Get(nonce, new(bool))
if nonceError != nil && errors.Is(nonceError, storage.ErrNotFound) {
// this is OK, nonce has not been used before
nonceError = nil
} else if nonceError != nil {
// other error occurred. Keep error to report after storing nonce
} else {
// no store error: value was retrieved from store, meaning the nonce has been used before
nonceError = oauth.OAuth2Error{
Code: oauth.InvalidRequest,
Description: "presentation nonce has already been used",
}
}
// Regardless the result of the nonce checking, the nonce of the VP must not be used again.
// So always store the nonce.
if err := nonceStore.Put(nonce, true); err != nil {
return fmt.Errorf("unable to store nonce: %w", err)
nonceError = errors.Join(fmt.Errorf("unable to store nonce: %w", err), nonceError)
}
return nil
return nonceError
}

// validatePresentationAudience checks if the presentation audience (aud claim for JWTs, domain property for JSON-LD proofs) contains the issuer DID.
func (r *Wrapper) validatePresentationAudience(presentation vc.VerifiablePresentation, issuer did.DID) error {
var audience []string
switch presentation.Format() {
Expand Down
4 changes: 2 additions & 2 deletions auth/api/iam/s2s_vptoken_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) {

_, err := ctx.client.handleS2SAccessTokenRequest(issuerDID, requestedScope, submissionJSON, presentation.Raw())

require.EqualError(t, err, "invalid_request - presentation is valid for too long (max 10s)")
require.EqualError(t, err, "invalid_request - presentation is valid for too long (max 5s)")
})
t.Run("JWT VP", func(t *testing.T) {
ctx := newTestClient(t)
Expand Down Expand Up @@ -343,7 +343,7 @@ func TestWrapper_createAccessToken(t *testing.T) {
t.Run("ok", func(t *testing.T) {
ctx := newTestClient(t)

accessToken, err := ctx.client.createAccessToken(issuerDID, time.Now(), []VerifiablePresentation{presentation}, submission, definition, "everything")
accessToken, err := ctx.client.createS2SAccessToken(issuerDID, time.Now(), []VerifiablePresentation{presentation}, submission, definition, "everything")

require.NoError(t, err)
assert.NotEmpty(t, accessToken.AccessToken)
Expand Down
2 changes: 1 addition & 1 deletion vcr/verifier/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ func (v *verifier) validateJWTPresentation(presentation vc.VerifiablePresentatio
return time.Now()
}
return *at
})))
})), jwt.WithAcceptableSkew(maxSkew))
if err != nil {
return fmt.Errorf("unable to validate JWT credential: %w", err)
}
Expand Down

0 comments on commit 7aa6824

Please sign in to comment.