diff --git a/internal/oidc/dynamiccodec/codec.go b/internal/oidc/dynamiccodec/codec.go new file mode 100644 index 00000000..0517e11c --- /dev/null +++ b/internal/oidc/dynamiccodec/codec.go @@ -0,0 +1,42 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Package dynamiccodec provides a type that can encode information using a just-in-time signing and +// (optionally) encryption secret. +package dynamiccodec + +import ( + "github.com/gorilla/securecookie" + + "go.pinniped.dev/internal/oidc" +) + +var _ oidc.Codec = &Codec{} + +// KeyFunc returns 2 keys: a required signing key, and an optional encryption key. +type KeyFunc func() ([]byte, []byte) + +// Codec can dynamically encode and decode information by using a KeyFunc to get its keys +// just-in-time. +type Codec struct { + keyFunc KeyFunc +} + +// New creates a new Codec that will use the provided keyFunc for its key source. +func New(keyFunc KeyFunc) *Codec { + return &Codec{ + keyFunc: keyFunc, + } +} + +// Encode implements oidc.Encode(). +func (c *Codec) Encode(name string, value interface{}) (string, error) { + signingKey, encryptionKey := c.keyFunc() + return securecookie.New(signingKey, encryptionKey).Encode(name, value) +} + +// Decode implements oidc.Decode(). +func (c *Codec) Decode(name string, value string, into interface{}) error { + signingKey, encryptionKey := c.keyFunc() + return securecookie.New(signingKey, encryptionKey).Decode(name, value, into) +} diff --git a/internal/oidc/dynamiccodec/codec_test.go b/internal/oidc/dynamiccodec/codec_test.go new file mode 100644 index 00000000..7513954a --- /dev/null +++ b/internal/oidc/dynamiccodec/codec_test.go @@ -0,0 +1,110 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package dynamiccodec + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCodec(t *testing.T) { + tests := []struct { + name string + keys func(encoderSigningKey, encoderEncryptionKey, decoderSigningKey, decoderEncryptionKey *[]byte) + wantEncoderError string + wantDecoderError string + }{ + { + name: "good signing and encryption keys", + }, + { + name: "good signing keys and no encryption key", + keys: func(encoderSigningKey, encoderEncryptionKey, decoderSigningKey, decoderEncryptionKey *[]byte) { + *encoderEncryptionKey = nil + *decoderEncryptionKey = nil + }, + }, + { + name: "good signing keys and bad encoding encryption key", + keys: func(encoderSigningKey, encoderEncryptionKey, decoderSigningKey, decoderEncryptionKey *[]byte) { + *encoderEncryptionKey = []byte("this-secret-is-not-16-bytes") + }, + wantEncoderError: "securecookie: error - caused by: crypto/aes: invalid key size 27", + }, + { + name: "good signing keys and bad decoding encryption key", + keys: func(encoderSigningKey, encoderEncryptionKey, decoderSigningKey, decoderEncryptionKey *[]byte) { + *decoderEncryptionKey = []byte("this-secret-is-not-16-bytes") + }, + wantDecoderError: "securecookie: error - caused by: crypto/aes: invalid key size 27", + }, + { + name: "bad encoder signing key", + keys: func(encoderSigningKey, encoderEncryptionKey, decoderSigningKey, decoderEncryptionKey *[]byte) { + *encoderSigningKey = nil + }, + wantEncoderError: "securecookie: hash key is not set", + }, + { + name: "bad decoder signing key", + keys: func(encoderSigningKey, encoderEncryptionKey, decoderSigningKey, decoderEncryptionKey *[]byte) { + *decoderSigningKey = nil + }, + wantDecoderError: "securecookie: hash key is not set", + }, + { + name: "signing key mismatch", + keys: func(encoderSigningKey, encoderEncryptionKey, decoderSigningKey, decoderEncryptionKey *[]byte) { + *encoderSigningKey = []byte("this key does not match the decoder key") + }, + wantDecoderError: "securecookie: the value is not valid", + }, + { + name: "encryption key mismatch", + keys: func(encoderSigningKey, encoderEncryptionKey, decoderSigningKey, decoderEncryptionKey *[]byte) { + *encoderEncryptionKey = []byte("16-byte-no-match") + }, + wantDecoderError: "securecookie: error - caused by: securecookie: error - caused by: gob: encoded unsigned integer out of range", + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + var ( + encoderSigningKey = []byte("some-signing-key") + encoderEncryptionKey = []byte("16-byte-encr-key") + decoderSigningKey = []byte("some-signing-key") + decoderEncryptionKey = []byte("16-byte-encr-key") + ) + if test.keys != nil { + test.keys(&encoderSigningKey, &encoderEncryptionKey, &decoderSigningKey, &decoderEncryptionKey) + } + encoder := New(func() ([]byte, []byte) { + return encoderSigningKey, encoderEncryptionKey + }) + + encoded, err := encoder.Encode("some-name", "some-message") + if test.wantEncoderError != "" { + require.EqualError(t, err, test.wantEncoderError) + return + } + require.NoError(t, err) + + decoder := New(func() ([]byte, []byte) { + return decoderSigningKey, decoderEncryptionKey + }) + + var decoded string + err = decoder.Decode("some-name", encoded, &decoded) + if test.wantDecoderError != "" { + require.EqualError(t, err, test.wantDecoderError) + return + } + require.NoError(t, err) + + require.Equal(t, "some-message", decoded) + }) + } +}