diff --git a/consent/handler.go b/consent/handler.go index 2eea50bc8d8..48167e7380f 100644 --- a/consent/handler.go +++ b/consent/handler.go @@ -4,6 +4,7 @@ package consent import ( + "context" "encoding/json" "net/http" "net/url" @@ -476,11 +477,12 @@ func (h *Handler) acceptOAuth2LoginRequest(w http.ResponseWriter, r *http.Reques } handledLoginRequest.RequestedAt = loginRequest.RequestedAt - f, err := flowctx.Decode[flow.Flow](ctx, h.r.FlowCipher(), challenge, flowctx.AsLoginChallenge) + f, err := h.decodeFlowWithClient(ctx, challenge, flowctx.AsLoginChallenge) if err != nil { h.r.Writer().WriteError(w, r, err) return } + request, err := h.r.ConsentManager().HandleLoginRequest(ctx, f, challenge, &handledLoginRequest) if err != nil { h.r.Writer().WriteError(w, r, errorsx.WithStack(err)) @@ -575,7 +577,7 @@ func (h *Handler) rejectOAuth2LoginRequest(w http.ResponseWriter, r *http.Reques return } - f, err := flowctx.Decode[flow.Flow](ctx, h.r.FlowCipher(), challenge, flowctx.AsLoginChallenge) + f, err := h.decodeFlowWithClient(ctx, challenge, flowctx.AsLoginChallenge) if err != nil { h.r.Writer().WriteError(w, r, err) return @@ -761,7 +763,7 @@ func (h *Handler) acceptOAuth2ConsentRequest(w http.ResponseWriter, r *http.Requ p.RequestedAt = cr.RequestedAt p.HandledAt = sqlxx.NullTime(time.Now().UTC()) - f, err := flowctx.Decode[flow.Flow](ctx, h.r.FlowCipher(), challenge, flowctx.AsConsentChallenge) + f, err := h.decodeFlowWithClient(ctx, challenge, flowctx.AsConsentChallenge) if err != nil { h.r.Writer().WriteError(w, r, err) return @@ -868,7 +870,7 @@ func (h *Handler) rejectOAuth2ConsentRequest(w http.ResponseWriter, r *http.Requ return } - f, err := flowctx.Decode[flow.Flow](ctx, h.r.FlowCipher(), challenge, flowctx.AsConsentChallenge) + f, err := h.decodeFlowWithClient(ctx, challenge, flowctx.AsConsentChallenge) if err != nil { h.r.Writer().WriteError(w, r, err) return @@ -1044,3 +1046,12 @@ func (h *Handler) getOAuth2LogoutRequest(w http.ResponseWriter, r *http.Request, h.r.Writer().Write(w, r, request) } + +func (h *Handler) decodeFlowWithClient(ctx context.Context, challenge string, opts ...flowctx.CodecOption) (*flow.Flow, error) { + f, err := flowctx.Decode[flow.Flow](ctx, h.r.FlowCipher(), challenge, opts...) + if err != nil { + return nil, err + } + + return f, nil +} diff --git a/consent/strategy_default.go b/consent/strategy_default.go index 395742fc47c..6d7f8c67e95 100644 --- a/consent/strategy_default.go +++ b/consent/strategy_default.go @@ -1170,13 +1170,3 @@ func (s *DefaultStrategy) ObfuscateSubjectIdentifier(ctx context.Context, cl fos } return subject, nil } - -func (s *DefaultStrategy) loginSessionFromCookie(r *http.Request) *flow.LoginSession { - clientID := r.URL.Query().Get("client_id") - if clientID == "" { - return nil - } - ls, _ := flowctx.FromCookie[flow.LoginSession](r.Context(), r, s.r.FlowCipher(), flowctx.LoginSessionCookie(flowctx.SuffixFromStatic(clientID))) - - return ls -} diff --git a/flow/flow.go b/flow/flow.go index b926ce5db96..b6a3b9b857f 100644 --- a/flow/flow.go +++ b/flow/flow.go @@ -84,18 +84,18 @@ type Flow struct { // identify the session. // // required: true - ID string `db:"login_challenge"` - NID uuid.UUID `db:"nid"` + ID string `db:"login_challenge" json:"i"` + NID uuid.UUID `db:"nid" json:"n"` // RequestedScope contains the OAuth 2.0 Scope requested by the OAuth 2.0 Client. // // required: true - RequestedScope sqlxx.StringSliceJSONFormat `db:"requested_scope"` + RequestedScope sqlxx.StringSliceJSONFormat `db:"requested_scope" json:"rs,omitempty"` // RequestedAudience contains the access token audience as requested by the OAuth 2.0 Client. // // required: true - RequestedAudience sqlxx.StringSliceJSONFormat `db:"requested_at_audience"` + RequestedAudience sqlxx.StringSliceJSONFormat `db:"requested_at_audience" json:"ra,omitempty"` // LoginSkip, if true, implies that the client has requested the same scopes from the same user previously. // If true, you can skip asking the user to grant the requested scopes, and simply forward the user to the redirect URL. @@ -103,73 +103,72 @@ type Flow struct { // This feature allows you to update / set session information. // // required: true - LoginSkip bool `db:"login_skip"` + LoginSkip bool `db:"login_skip" json:"ls,omitempty"` // Subject is the user ID of the end-user that authenticated. Now, that end user needs to grant or deny the scope // requested by the OAuth 2.0 client. If this value is set and `skip` is true, you MUST include this subject type // when accepting the login request, or the request will fail. // // required: true - Subject string `db:"subject"` + Subject string `db:"subject" json:"s,omitempty"` // OpenIDConnectContext provides context for the (potential) OpenID Connect context. Implementation of these // values in your app are optional but can be useful if you want to be fully compliant with the OpenID Connect spec. - OpenIDConnectContext *OAuth2ConsentRequestOpenIDConnectContext `db:"oidc_context"` + OpenIDConnectContext *OAuth2ConsentRequestOpenIDConnectContext `db:"oidc_context" json:"oc"` // Client is the OAuth 2.0 Client that initiated the request. // // required: true - Client *client.Client `db:"-"` - - ClientID string `db:"client_id"` + Client *client.Client `db:"c" json:"client,omitempty"` + ClientID string `db:"client_id" json:"ci,omitempty"` // RequestURL is the original OAuth 2.0 Authorization URL requested by the OAuth 2.0 client. It is the URL which // initiates the OAuth 2.0 Authorization Code or OAuth 2.0 Implicit flow. This URL is typically not needed, but // might come in handy if you want to deal with additional request parameters. // // required: true - RequestURL string `db:"request_url"` + RequestURL string `db:"request_url" json:"r,omitempty"` // SessionID is the login session ID. If the user-agent reuses a login session (via cookie / remember flag) // this ID will remain the same. If the user-agent did not have an existing authentication session (e.g. remember is false) // this will be a new random value. This value is used as the "sid" parameter in the ID Token and in OIDC Front-/Back- // channel logout. Its value can generally be used to associate consecutive login requests by a certain user. - SessionID sqlxx.NullString `db:"login_session_id"` + SessionID sqlxx.NullString `db:"login_session_id" json:"si,omitempty"` // IdentityProviderSessionID is the session ID of the end-user that authenticated. // If specified, we will use this value to propagate the logout. - IdentityProviderSessionID sqlxx.NullString `db:"identity_provider_session_id"` + IdentityProviderSessionID sqlxx.NullString `db:"identity_provider_session_id" json:"is,omitempty"` - LoginVerifier string `db:"login_verifier"` - LoginCSRF string `db:"login_csrf"` + LoginVerifier string `db:"login_verifier" json:"lv,omitempty"` + LoginCSRF string `db:"login_csrf" json:"lc,omitempty"` - LoginInitializedAt sqlxx.NullTime `db:"login_initialized_at"` - RequestedAt time.Time `db:"requested_at"` + LoginInitializedAt sqlxx.NullTime `db:"login_initialized_at" json:"li,omitempty"` + RequestedAt time.Time `db:"requested_at" json:"ia,omitempty"` - State int16 `db:"state"` + State int16 `db:"state" json:"q,omitempty"` // LoginRemember, if set to true, tells ORY Hydra to remember this user by telling the user agent (browser) to store // a cookie with authentication data. If the same user performs another OAuth 2.0 Authorization Request, he/she // will not be asked to log in again. - LoginRemember bool `db:"login_remember"` + LoginRemember bool `db:"login_remember" json:"lr,omitempty"` // LoginRememberFor sets how long the authentication should be remembered for in seconds. If set to `0`, the // authorization will be remembered for the duration of the browser session (using a session cookie). - LoginRememberFor int `db:"login_remember_for"` + LoginRememberFor int `db:"login_remember_for" json:"lf,omitempty"` // LoginExtendSessionLifespan, if set to true, session cookie expiry time will be updated when session is // refreshed (login skip=true). - LoginExtendSessionLifespan bool `db:"login_extend_session_lifespan"` + LoginExtendSessionLifespan bool `db:"login_extend_session_lifespan" json:"ll,omitempty"` // ACR sets the Authentication AuthorizationContext Class Reference value for this authentication session. You can use it // to express that, for example, a user authenticated using two factor authentication. - ACR string `db:"acr"` + ACR string `db:"acr" json:"a,omitempty"` // AMR sets the Authentication Methods References value for this // authentication session. You can use it to specify the method a user used to // authenticate. For example, if the acr indicates a user used two factor // authentication, the amr can express they used a software-secured key. - AMR sqlxx.StringSliceJSONFormat `db:"amr"` + AMR sqlxx.StringSliceJSONFormat `db:"amr" json:"am,omitempty"` // ForceSubjectIdentifier forces the "pairwise" user ID of the end-user that authenticated. The "pairwise" user ID refers to the // (Pairwise Identifier Algorithm)[http://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg] of the OpenID @@ -188,58 +187,58 @@ type Flow struct { // other unique value). // // If you fail to compute the proper value, then authentication processes which have id_token_hint set might fail. - ForceSubjectIdentifier string `db:"forced_subject_identifier"` + ForceSubjectIdentifier string `db:"forced_subject_identifier" json:"fs,omitempty"` // Context is an optional object which can hold arbitrary data. The data will be made available when fetching the // consent request under the "context" field. This is useful in scenarios where login and consent endpoints share // data. - Context sqlxx.JSONRawMessage `db:"context"` + Context sqlxx.JSONRawMessage `db:"context" json:"ct"` // LoginWasUsed set to true means that the login request was already handled. // This can happen on form double-submit or other errors. If this is set we // recommend redirecting the user to `request_url` to re-initiate the flow. - LoginWasUsed bool `db:"login_was_used"` + LoginWasUsed bool `db:"login_was_used" json:"lu,omitempty"` - LoginError *RequestDeniedError `db:"login_error"` - LoginAuthenticatedAt sqlxx.NullTime `db:"login_authenticated_at"` + LoginError *RequestDeniedError `db:"login_error" json:"le,omitempty"` + LoginAuthenticatedAt sqlxx.NullTime `db:"login_authenticated_at" json:"la,omitempty"` // ConsentChallengeID is the identifier ("authorization challenge") of the consent authorization request. It is used to // identify the session. // // required: true - ConsentChallengeID sqlxx.NullString `db:"consent_challenge_id"` + ConsentChallengeID sqlxx.NullString `db:"consent_challenge_id" json:"cc,omitempty"` // ConsentSkip, if true, implies that the client has requested the same scopes from the same user previously. // If true, you must not ask the user to grant the requested scopes. You must however either allow or deny the // consent request using the usual API call. - ConsentSkip bool `db:"consent_skip"` - ConsentVerifier sqlxx.NullString `db:"consent_verifier"` - ConsentCSRF sqlxx.NullString `db:"consent_csrf"` + ConsentSkip bool `db:"consent_skip" json:"cs,omitempty"` + ConsentVerifier sqlxx.NullString `db:"consent_verifier" json:"cv,omitempty"` + ConsentCSRF sqlxx.NullString `db:"consent_csrf" json:"cr,omitempty"` // GrantedScope sets the scope the user authorized the client to use. Should be a subset of `requested_scope`. - GrantedScope sqlxx.StringSliceJSONFormat `db:"granted_scope"` + GrantedScope sqlxx.StringSliceJSONFormat `db:"granted_scope" json:"gs,omitempty"` // GrantedAudience sets the audience the user authorized the client to use. Should be a subset of `requested_access_token_audience`. - GrantedAudience sqlxx.StringSliceJSONFormat `db:"granted_at_audience"` + GrantedAudience sqlxx.StringSliceJSONFormat `db:"granted_at_audience" json:"ga,omitempty"` // ConsentRemember, if set to true, tells ORY Hydra to remember this consent authorization and reuse it if the same // client asks the same user for the same, or a subset of, scope. - ConsentRemember bool `db:"consent_remember"` + ConsentRemember bool `db:"consent_remember" json:"ce,omitempty"` // ConsentRememberFor sets how long the consent authorization should be remembered for in seconds. If set to `0`, the // authorization will be remembered indefinitely. - ConsentRememberFor *int `db:"consent_remember_for"` + ConsentRememberFor *int `db:"consent_remember_for" json:"cf"` // ConsentHandledAt contains the timestamp the consent request was handled. - ConsentHandledAt sqlxx.NullTime `db:"consent_handled_at"` + ConsentHandledAt sqlxx.NullTime `db:"consent_handled_at" json:"ch,omitempty"` // ConsentWasHandled set to true means that the request was already handled. // This can happen on form double-submit or other errors. If this is set we // recommend redirecting the user to `request_url` to re-initiate the flow. - ConsentWasHandled bool `db:"consent_was_used"` - ConsentError *RequestDeniedError `db:"consent_error"` - SessionIDToken sqlxx.MapStringInterface `db:"session_id_token" faker:"-"` - SessionAccessToken sqlxx.MapStringInterface `db:"session_access_token" faker:"-"` + ConsentWasHandled bool `db:"consent_was_used" json:"cw,omitempty"` + ConsentError *RequestDeniedError `db:"consent_error" json:"cx"` + SessionIDToken sqlxx.MapStringInterface `db:"session_id_token" faker:"-" json:"st"` + SessionAccessToken sqlxx.MapStringInterface `db:"session_access_token" faker:"-" json:"sa"` } func NewFlow(r *LoginRequest) *Flow { @@ -511,21 +510,33 @@ type CipherProvider interface { } // ToLoginChallenge converts the flow into a login challenge. -func (f *Flow) ToLoginChallenge(ctx context.Context, cipherProvider CipherProvider) (string, error) { +func (f Flow) ToLoginChallenge(ctx context.Context, cipherProvider CipherProvider) (string, error) { + if f.Client != nil { + f.ClientID = f.Client.GetID() + } return flowctx.Encode(ctx, cipherProvider.FlowCipher(), f, flowctx.AsLoginChallenge) } // ToLoginVerifier converts the flow into a login verifier. -func (f *Flow) ToLoginVerifier(ctx context.Context, cipherProvider CipherProvider) (string, error) { +func (f Flow) ToLoginVerifier(ctx context.Context, cipherProvider CipherProvider) (string, error) { + if f.Client != nil { + f.ClientID = f.Client.GetID() + } return flowctx.Encode(ctx, cipherProvider.FlowCipher(), f, flowctx.AsLoginVerifier) } // ToConsentChallenge converts the flow into a consent challenge. -func (f *Flow) ToConsentChallenge(ctx context.Context, cipherProvider CipherProvider) (string, error) { +func (f Flow) ToConsentChallenge(ctx context.Context, cipherProvider CipherProvider) (string, error) { + if f.Client != nil { + f.ClientID = f.Client.GetID() + } return flowctx.Encode(ctx, cipherProvider.FlowCipher(), f, flowctx.AsConsentChallenge) } // ToConsentVerifier converts the flow into a consent verifier. -func (f *Flow) ToConsentVerifier(ctx context.Context, cipherProvider CipherProvider) (string, error) { +func (f Flow) ToConsentVerifier(ctx context.Context, cipherProvider CipherProvider) (string, error) { + if f.Client != nil { + f.ClientID = f.Client.GetID() + } return flowctx.Encode(ctx, cipherProvider.FlowCipher(), f, flowctx.AsConsentVerifier) } diff --git a/oauth2/flowctx/cookies.go b/oauth2/flowctx/cookies.go deleted file mode 100644 index 42609d50019..00000000000 --- a/oauth2/flowctx/cookies.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2023 Ory Corp -// SPDX-License-Identifier: Apache-2.0 - -package flowctx - -import "github.com/ory/hydra/v2/client" - -type ( - CookieSuffixer interface { - CookieSuffix() string - } - - StaticSuffix string - clientID string -) - -func (s StaticSuffix) CookieSuffix() string { return string(s) } -func (s clientID) GetID() string { return string(s) } - -const ( - loginSessionCookie = "ory_hydra_loginsession" -) - -func LoginSessionCookie(suffix CookieSuffixer) string { - return loginSessionCookie + "_" + suffix.CookieSuffix() -} - -func SuffixForClient(c client.IDer) StaticSuffix { - return StaticSuffix(client.CookieSuffix(c)) -} - -func SuffixFromStatic(id string) StaticSuffix { - return SuffixForClient(clientID(id)) -} diff --git a/oauth2/flowctx/encoding.go b/oauth2/flowctx/encoding.go index 67d9d51d2a6..8a1f8cbf270 100644 --- a/oauth2/flowctx/encoding.go +++ b/oauth2/flowctx/encoding.go @@ -8,13 +8,10 @@ import ( "compress/gzip" "context" "encoding/json" - "net/http" "github.com/pkg/errors" - "github.com/ory/fosite" "github.com/ory/hydra/v2/aead" - "github.com/ory/hydra/v2/driver/config" ) type ( @@ -84,50 +81,21 @@ func Encode(ctx context.Context, cipher aead.Cipher, val any, opts ...CodecOptio // Steps: // 1. Encode to JSON // 2. GZIP - // 3. Encrypt with AEAD (AES-GCM) + Base64 URL-encode + // 3. Encrypt with AEAD (XChaCha20-Poly1305) + Base64 URL-encode var b bytes.Buffer - gz := gzip.NewWriter(&b) + gz, err := gzip.NewWriterLevel(&b, gzip.BestCompression) + if err != nil { + return "", err + } if err = json.NewEncoder(gz).Encode(val); err != nil { return "", err } + if err = gz.Close(); err != nil { return "", err } return cipher.Encrypt(ctx, b.Bytes(), additionalDataFromOpts(opts...)) } - -// SetCookie encrypts the given value and sets it in a cookie. -func SetCookie(ctx context.Context, w http.ResponseWriter, reg interface { - FlowCipher() *aead.XChaCha20Poly1305 - config.Provider -}, cookieName string, value any, opts ...CodecOption) error { - cipher := reg.FlowCipher() - cookie, err := Encode(ctx, cipher, value, opts...) - if err != nil { - return err - } - - http.SetCookie(w, &http.Cookie{ - Name: cookieName, - Value: cookie, - HttpOnly: true, - Domain: reg.Config().CookieDomain(ctx), - Secure: reg.Config().CookieSecure(ctx), - SameSite: reg.Config().CookieSameSiteMode(ctx), - }) - - return nil -} - -// FromCookie looks up the value stored in the cookie and decodes it. -func FromCookie[T any](ctx context.Context, r *http.Request, cipher aead.Cipher, cookieName string, opts ...CodecOption) (*T, error) { - cookie, err := r.Cookie(cookieName) - if err != nil { - return nil, errors.WithStack(fosite.ErrInvalidClient.WithHint("No cookie found for this request. Please initiate a new flow and retry.")) - } - - return Decode[T](ctx, cipher, cookie.Value, opts...) -} diff --git a/oauth2/flowctx/encoding_test.go b/oauth2/flowctx/encoding_test.go new file mode 100644 index 00000000000..4d2ee89e62a --- /dev/null +++ b/oauth2/flowctx/encoding_test.go @@ -0,0 +1,134 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package flowctx_test + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/gofrs/uuid" + "github.com/stretchr/testify/require" + + "github.com/ory/hydra/v2/aead" + "github.com/ory/hydra/v2/client" + "github.com/ory/hydra/v2/flow" + "github.com/ory/hydra/v2/oauth2/flowctx" + "github.com/ory/x/pointerx" + "github.com/ory/x/sqlxx" +) + +func TestEncoding(t *testing.T) { + f := flow.Flow{ + ID: uuid.Must(uuid.NewV4()).String(), + NID: uuid.Must(uuid.NewV4()), + RequestedScope: []string{"scope1", "scope2"}, + RequestedAudience: []string{"https://api.example.org/v1", "https://api.example.org/v2"}, + LoginSkip: false, + Subject: "some-subject@some-idp-somewhere.com", + OpenIDConnectContext: &flow.OAuth2ConsentRequestOpenIDConnectContext{ + ACRValues: []string{"acr1", "acr2"}, + UILocales: []string{"en-US", "en-GB"}, + Display: "page", + IDTokenHintClaims: map[string]interface{}{"claim1": "value1", "claim2": "value2"}, + LoginHint: "some-login-hint", + }, + Client: &client.Client{ + ID: uuid.Must(uuid.NewV4()).String(), + NID: uuid.Must(uuid.NewV4()), + Name: "some-client-name", + Secret: "some-supersafe-secret", + RedirectURIs: []string{ + "https://redirect1.example.org/callback", + "https://redirect2.example.org/callback", + }, + GrantTypes: []string{"authorization_code", "refresh_token"}, + ResponseTypes: []string{"code"}, + Scope: "scope1 scope2", + Audience: sqlxx.StringSliceJSONFormat{"https://api.example.org/v1 https://api.example.org/v2"}, + Owner: "some-owner", + TermsOfServiceURI: "https://tos.example.org", + PolicyURI: "https://policy.example.org", + ClientURI: "https://client.example.org", + LogoURI: "https://logo.example.org", + Contacts: []string{"contact1", "contact2"}, + SubjectType: "public", + JSONWebKeysURI: "https://jwks.example.org", + JSONWebKeys: nil, // TODO? + TokenEndpointAuthMethod: "client_secret_basic", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + AllowedCORSOrigins: []string{"https://cors1.example.org", "https://cors2.example.org"}, + Metadata: sqlxx.JSONRawMessage(`{"client-metadata-key1": "val1"}`), + AccessTokenStrategy: "jwt", + SkipConsent: true, + }, + RequestURL: "https://auth.hydra.local/oauth2/auth?client_id=some-client-id&response_type=code&scope=scope1+scope2&redirect_uri=https%3A%2F%2Fredirect1.example.org%2Fcallback&state=some-state&nonce=some-nonce", + SessionID: sqlxx.NullString("some-session-id"), + LoginCSRF: uuid.Must(uuid.NewV4()).String(), + LoginInitializedAt: sqlxx.NullTime(time.Now()), + RequestedAt: time.Now(), + State: 1, + LoginRemember: true, + LoginRememberFor: 3600, + Context: sqlxx.JSONRawMessage(`{"context-key1": "val1"}`), + GrantedScope: []string{"scope1", "scope2"}, + GrantedAudience: []string{"https://api.example.org/v1", "https://api.example.org/v2"}, + ConsentRemember: true, + ConsentRememberFor: pointerx.Int(3600), + ConsentHandledAt: sqlxx.NullTime(time.Now()), + SessionIDToken: sqlxx.MapStringInterface{ + "session-id-token-key1": "val1", + "session-id-token-key2": "val2", + uuid.Must(uuid.NewV4()).String(): "val3", + uuid.Must(uuid.NewV4()).String(): "val4", + uuid.Must(uuid.NewV4()).String(): "val5", + }, + SessionAccessToken: sqlxx.MapStringInterface{ + "session-access-token-key1": "val1", + "session-access-token-key2": "val2", + uuid.Must(uuid.NewV4()).String(): "val3", + uuid.Must(uuid.NewV4()).String(): "val4", + uuid.Must(uuid.NewV4()).String(): "val5", + }, + } + + ctx := context.Background() + + t.Run("with client", func(t *testing.T) { + j, err := json.Marshal(f) + require.NoError(t, err) + t.Logf("Length (JSON): %d", len(j)) + cp := new(cipherProvider) + consentVerifier, err := flowctx.Encode(ctx, cp.FlowCipher(), f, flowctx.AsConsentVerifier) + require.NoError(t, err) + t.Logf("Length (JSON+GZIP+AEAD): %d", len(consentVerifier)) + }) + t.Run("without client", func(t *testing.T) { + f := f + f.Client = nil + j, err := json.Marshal(f) + require.NoError(t, err) + t.Logf("Length (JSON): %d", len(j)) + cp := new(cipherProvider) + consentVerifier, err := f.ToConsentVerifier(ctx, cp) + require.NoError(t, err) + t.Logf("Length (JSON+GZIP+AEAD): %d", len(consentVerifier)) + }) +} + +type cipherProvider struct{} + +func (c *cipherProvider) FlowCipher() *aead.XChaCha20Poly1305 { + return aead.NewXChaCha20Poly1305(c) +} + +func (c *cipherProvider) GetGlobalSecret(context.Context) ([]byte, error) { + return []byte("supersecret123456789123456789012"), nil +} + +func (c *cipherProvider) GetRotatedGlobalSecrets(ctx context.Context) ([][]byte, error) { + return nil, nil +}