Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions resource/nonce.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ func generateDPoPNonce(random io.Reader) (string, error) {
return base64.RawURLEncoding.EncodeToString(buf), nil
}

// IssueDPoPNonce issues a fresh DPoP nonce this Verifier will accept, for
// a resource server to hand out ahead of a request rather than in answer
// to one: an OpenID4VCI 1.0 §7.2 Nonce Response's DPoP-Nonce header,
// say, which the Wallet then uses at the Credential Endpoint this
// Verifier guards. It returns "" and no error when Dependencies.Nonces is
// nil: the Verifier doesn't use DPoP nonces, so there is none to give.
//
// Each call stores a nonce until Config.Limits.DPoPNonceLifetime has
// passed. Called from an endpoint anyone may call (§7.1's Nonce Endpoint
// is), it lets anyone add records, as that endpoint's own c_nonce does:
// rate-limit it the same way.
func (v *Verifier) IssueDPoPNonce(ctx context.Context) (string, error) {
if v.deps.Nonces == nil {
return "", nil
}
return v.issueDPoPNonce(ctx, v.deps.Clock.Now())
}

// issueDPoPNonce generates and persists a fresh nonce, valid from now
// for v.cfg.Limits.DPoPNonceLifetime. Only ever called once v.deps.Nonces
// is known to be non-nil.
Expand Down
22 changes: 22 additions & 0 deletions resource/nonce_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,25 @@ func TestInsufficientScopeCarriesNextNonce(t *testing.T) {
t.Fatalf("status = %d, want 403", rec.Code)
}
}

// TestIssueDPoPNonce: a nonce IssueDPoPNonce hands out ahead of a
// request (as an OpenID4VCI Nonce Response does) is accepted by Verify,
// once; a Verifier without Dependencies.Nonces has none to give.
func TestIssueDPoPNonce(t *testing.T) {
ctx := context.Background()
first := newNonceFixture(t, "", nil)
issued, err := first.verifier.IssueDPoPNonce(ctx)
if err != nil || issued == "" {
t.Fatalf("IssueDPoPNonce = %q, %v; want a nonce", issued, err)
}
if _, err := newNonceFixture(t, issued, first.nonces).verify(t); err != nil {
t.Fatalf("Verify(issued nonce) = %v, want accepted", err)
}
if _, err := newNonceFixture(t, issued, first.nonces).verify(t); err == nil {
t.Fatal("Verify(issued nonce again) = nil error, want it refused as spent")
}

if nonce, err := newFixture(t).verifier.IssueDPoPNonce(ctx); nonce != "" || err != nil {
t.Errorf("IssueDPoPNonce without Nonces = %q, %v; want none and no error", nonce, err)
}
}
Loading