token

package
v0.0.0-...-5f133c6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 11, 2024 License: BSD-3-Clause, BSD-3-Clause Imports: 15 Imported by: 0

Documentation

Overview

Package token provides primitives to create and decode cryptographic tokens.

The library is built around the concept of Encoders: objects capable of turning a byte array into another, by, for example, adding a cryptographic signature created with an asymmetric key, encrypting the data, adding an expiry time, or by chaining multiple encoders together.

Additionally, the library provides a few higher level adapters that allow to serialize golang structs into an array of bytes, or to turn an array of bytes into a string.

For example, by using something like:

be, err := token.NewSymmetricEncoder(...)
if err ...

encoder := token.NewTypeEncoder(token.NewChainedEncoder(
    token.NewTimeEncoder(nil, time.Second * 10), be, token.NewBase64URLEncoder())

you will get an encoder that when used like:

uData := struct {
  Username, Lang string
}{"myname", "english"}

b64string, err := encoder.Encode(uData)

will convert a struct into a byte array, add the time the serialization happened, encrypt all with a symmetric key, and then convert to base64.

On Decode(), the original array will be returned after applying all the necessary transformations and verifications. For example, Decode() will error out if the data is older than 10 seconds, the maximum lifetime supplied to NewTimeEncoder.

Index

Constants

View Source
const AsymmetricKeyLength = 32
View Source
const AsymmetricNonceLength = 24

Variables

View Source
var ExpiredError = fmt.Errorf("signature expired")

ExpiredError is returned if the data is considered expired.

View Source
var ExpiresTimeKey = contextKey("expire")

ExpiresTimeKey allows to access the time the data is expected to expire.

It can be accessed and used just like explained for IssuedTimeKey.

View Source
var IssuedTimeKey = contextKey("issued")

IssuedTimeKey allows to access the time encoded by TimeEncoder.Encode.

During Deocde() the context supplied is annotated with the time extracted while decoding the data.

Example:

te := NewTimeEncoder(...)
...
ctx, data, err := te.Decode(context.Background(), original)
...
etime, ok := ctx.Value(token.IssuedTimeKey).(time.Time)
if !ok {
  ...
}
View Source
var MaxTimeKey = contextKey("max")

MaxTimeKey allows to access the maximum validity of the data.

MaxTimeKey can be accessed and used just like explained for IssuedTimeKey.

Functions

func GenerateAsymmetricKeys

func GenerateAsymmetricKeys(rng *rand.Rand) (*AsymmetricKey, *AsymmetricKey, error)

GenerateAsymmetricKeys generates a public key and private key.

func GenerateSigningKey

func GenerateSigningKey(rng *rand.Rand) (*VerifyingKey, *SigningKey, error)

func GenerateSymmetricKey

func GenerateSymmetricKey(rng *rand.Rand, size int) ([]byte, error)

GenerateSymmetricKey generates a new symmetric key.

size is the size of the key to generate in bits. If 0, defaults to 256. The only valid values are those accepted by the underlying AES cipher: 128, 192 or 256.

Types

type Allocator

type Allocator func([]byte, BinaryEncoder, []BinaryEncoder) []byte

Allocator is a function capable of creating a buffer to encrypt the data in.

This can be used to either store the data in a specific place (eg, mmap) or to avoid multiple allocations in the course of filling the buffer.

var DefaultAllocator Allocator = func(buffer []byte, cipher BinaryEncoder, keyciphers []BinaryEncoder) []byte {

	return make([]byte, 0, len(buffer)+64+96*len(keyciphers))
}

type AsymmetricEncoder

type AsymmetricEncoder struct {
	// contains filtered or unexported fields
}

AsymmetricEncoder is a BinaryEncoder capable of encrypting data with a public key and decoding it using a public and private key pair.

AsymmetricEncoder is based on the naccl library, it is pretty much an interface adapter around the OpenAnonymous and SealAnonymous functions.

func NewAsymmetricEncoder

func NewAsymmetricEncoder(rng *rand.Rand, setters ...AsymmetricSetter) (*AsymmetricEncoder, error)

func (*AsymmetricEncoder) Decode

func (t *AsymmetricEncoder) Decode(ctx context.Context, ciphertext []byte) (context.Context, []byte, error)

func (*AsymmetricEncoder) Encode

func (t *AsymmetricEncoder) Encode(data []byte) ([]byte, error)

func (*AsymmetricEncoder) PrivateKey

func (t *AsymmetricEncoder) PrivateKey() *AsymmetricKey

func (*AsymmetricEncoder) PublicKey

func (t *AsymmetricEncoder) PublicKey() *AsymmetricKey

type AsymmetricKey

type AsymmetricKey [AsymmetricKeyLength]byte

func AsymmetricKeyFromHex

func AsymmetricKeyFromHex(key string) (*AsymmetricKey, error)

func AsymmetricKeyFromSlice

func AsymmetricKeyFromSlice(key []byte) (*AsymmetricKey, error)

func AsymmetricKeyFromString

func AsymmetricKeyFromString(key string) (*AsymmetricKey, error)

func (*AsymmetricKey) ToByte

func (k *AsymmetricKey) ToByte() *[AsymmetricKeyLength]byte

type AsymmetricNonce

type AsymmetricNonce [AsymmetricNonceLength]byte

func AsymmetricNonceFromSlice

func AsymmetricNonceFromSlice(nonce []byte) (*AsymmetricNonce, error)

func (*AsymmetricNonce) ToByte

type AsymmetricSetter

type AsymmetricSetter func(*AsymmetricEncoder) error

func UseKeyPair

func UseKeyPair(pub, priv *AsymmetricKey) AsymmetricSetter

func UsePrivateKey

func UsePrivateKey(key *AsymmetricKey) AsymmetricSetter

func UsePublicKey

func UsePublicKey(key *AsymmetricKey) AsymmetricSetter

func WithGeneratedAsymmetricKey

func WithGeneratedAsymmetricKey() AsymmetricSetter

Creates a new random public and private key, or return error.

type Base64Encoder

type Base64Encoder struct {
	// contains filtered or unexported fields
}

func NewBase64UrlEncoder

func NewBase64UrlEncoder() *Base64Encoder

func (*Base64Encoder) Decode

func (e *Base64Encoder) Decode(ctx context.Context, data []byte) (context.Context, []byte, error)

func (*Base64Encoder) Encode

func (e *Base64Encoder) Encode(data []byte) ([]byte, error)

type BinaryEncoder

type BinaryEncoder interface {
	// Encode will transform the input array of bytes into the returned one.
	Encode([]byte) ([]byte, error)

	// Decode will return the original array of bytes after decoding it.
	//
	// The context can be used to access additional metadata.
	// See examples below.
	Decode(context.Context, []byte) (context.Context, []byte, error)
}

BinaryEncoders convert an array of bytes into another by applying binary transformations.

For example: they can encrypt the data, compress it, sign it, augment it with metadata (like an expiration time), and so on.

type ChainedEncoder

type ChainedEncoder []BinaryEncoder

ChainedEncoder is a set of BinaryEncoders to be applied in sequence.

This allows, for example, to add additional signatures to data after encrypting it, or to add an expiration time.

func NewChainedEncoder

func NewChainedEncoder(enc ...BinaryEncoder) *ChainedEncoder

func (*ChainedEncoder) Decode

func (ce *ChainedEncoder) Decode(ctx context.Context, data []byte) (context.Context, []byte, error)

func (*ChainedEncoder) Encode

func (ce *ChainedEncoder) Encode(data []byte) ([]byte, error)

type CryptoFactory

type CryptoFactory func(rng *rand.Rand, key []byte) (BinaryEncoder, []byte, error)

CryptoFactory is a function capable of creating a new BinaryEncoder.

If the supplied key parameter is nil, then a random key should be generated. Returns a BinaryEncoder - used to perform encryption and decryption - the key configured for the encoder (either supplied as a parameter to the function, or generated randomly). In case of error, returns an error.

Look at SymmetricCreator for an example. More details in the definition of MultiKeyCryptoEncoder.

var SymmetricCreator CryptoFactory = func(rng *rand.Rand, key []byte) (BinaryEncoder, []byte, error) {
	if key != nil {
		be, err := NewSymmetricEncoder(rng, UseSymmetricKey(key))
		return be, key, err
	}

	key, err := GenerateSymmetricKey(rng, 0)
	if err != nil {
		return nil, nil, err
	}

	be, err := NewSymmetricEncoder(rng, UseSymmetricKey(key))
	return be, key, err
}

SymmetricCreator is a CryptoFactory suitable for use with NewMultiKeyCryptoEncoder().

type ExpireEncoder

type ExpireEncoder struct {
	// contains filtered or unexported fields
}

ExpireEncoder is an encoder that saves the time the data expires.

On Decode, it checks with the supplied time source, and fails validation if the data is considered expired.

This means that the Encode()r of the data is in control of when the clients using Decode() will consider it expired, as they will generally enforce the stored expiry time.

Expiry information is encoded in the token by whoever created the data.

func NewExpireEncoder

func NewExpireEncoder(source TimeSource, validity time.Duration) *ExpireEncoder

NewExpireEncoder creates a new ExpireEncoder.

source is a source of time, TimeSource. validity is the dessired lifetime of the data. It is used during encode to store a desired expire time alongisde the data.

func (*ExpireEncoder) Decode

func (t *ExpireEncoder) Decode(ctx context.Context, data []byte) (context.Context, []byte, error)

Decode decodes ExpireEncoder encoded data.

It returns ExpiredError if the time supplied by the passed TimeSource is past the ExpiresTime carried alongside the data. It returns a generic error if the data is considered corrupted or invalid for any other reason.

Decode always tries to return as much data as possible, together with ExpiresTime information in the context, even if the data is expired.

This allows, for example, to write code to override/ignore the ExpiredError, or to print user friendly messages indicating when the data was expired.

func (*ExpireEncoder) Encode

func (t *ExpireEncoder) Encode(data []byte) ([]byte, error)

type MultiKeyCryptoEncoder

type MultiKeyCryptoEncoder struct {
	// contains filtered or unexported fields
}

MultiKeyCryptoEncoder provides an encoder capable of encrypting data with multiple keys so it can be decrypted by any one of those keys.

In literature, this is referred to as a Multi-Recipient Encryption Scheme 1, that can be used to implement arbitrary naive/simple hybrid ciphers 2.

Just like GPG or other puplar software, It works by:

  1. Generating a random key and encrypting data with this random key. This is the Data Encryption Key, or DEK.
  1. Encrypting this random key multiple times, once per recipient. Each recipient provides a Key Encryption Key, or KEK.

In the implementation here:

  • data is encrypted with an arbitrary BinaryEncoder, which is instantiated through a CryptFactory, in charge of generating the key and initializing the cipher to use. CyrptoFactory generates the DEK.
  • the DEK is encrypted through one or more keyholders. Keyholders are just BinaryEncoders, symmetric or asymmetric, capable of encrypting the DEK with their own KEK.

The use of a MultiKeyCryptoEncoder is very very simple. See the test for more examples, but a basic use to encrypt data can look like:

keyholder1, err := NewAsymmetricEncoder(rng, UsePublicKey(recipient1))
...
keyholder2, err := NewAsymmetricEncoder(rng, UsePublickKey(recipient2))
...

mke, err := NewMultiKeyCryptoEncoder(
               rng, SymmetricCreator, WithKeyHolder(keyholder1, keyholder2))
...
encoded, err := mke.Encode(data)

While to decrypt the data, a single recipient could use something like:

mke, err := NewMultiKeyCryptoEncoder(
               rng, SymmetricCreator, WithKeyHolder(keyholder1))
...
_, decoded, err := mke.Decode(context.Background(), encoded)

Now:

  • SymmetricCreator generates a random 256 bit key, and configures an AES256-GCM cipher to encrypt/decrypt the data. You can create your own factory to use any other algorithm.

  • Keyholders are just other BinaryEncoders. You can use an AsymmetricEncoder, a Symmetric one, mix them, or even just store the DEK in cleartext if you really want to.

  • MultiKeyCryptoEncoder is really agnostic to the encoder returned by the CryptoFactory, or used as keyholder.

  • Each call to Encode() results in a new random key (and random nonces) being computed, and in all keyholders being invoked in turn to encrypt that key and store the result as part of the Encode()d message.

  • If you use a MultiKeyCryptoEncoder to encrypt the data, you MUST use a MultiKeyCryptoEncoder to decrypt it.

  • A MultiKeyCryptoEncoder will be able to decrypt the message as long as it has at least one keyholder capable of decrypting one of the encrypted keys.

  • Authenticated encryption for all (keyholder and data encryption) is strongly recommended.

    The MultiKeyCryptoEncoder stores very little metadata alongside each key (just the key length). When decrypting, it will try each key in turn until it finds one that (a) can be decrypted without errors, and (b) can decrypt the entire message without errors.

    If neither the keyholder nor the data encryption uses authenticated encryption (or is chained with NewChainedEncoder with some form of MAC/hashing/checksumming), it is likely that a Decode() will result in garbage, as the operation will succeed even in the presence of invalid keys (same that would happen with the wrong key and a non-authenticated scheme).

  • The data returned by a MultiKeyCryptoEncoder is neither signed nor authenticated. A receiver or MITM could modify the data and make undetectable changes to the layout by, for example, removing or adding keys, or corrupting the framing that indicates the lenght of each stored copy of the key.

    If this is undesireable, you can chain the encoder with another signing or encrypting encoder. But assuming both KEK and DEK are used with an authenticated encryption scheme risk should be minimal if any (extra keys will be rejected, corruption in key or data will be detected).

https://www.cc.gatech.edu/~aboldyre/papers/bbks.pdf

func NewMultiKeyCryptoEncoder

func NewMultiKeyCryptoEncoder(rng *rand.Rand, creator CryptoFactory, setters ...MultiKeyCryptoSetter) (*MultiKeyCryptoEncoder, error)

NewMultiKeyCryptoEncoder creates a new MultiKeyCryptoEncoder.

creator is a function capable of generating a random key and a BinaryEncoder to use to encode the data.

With settters, at least one keyholder must be specified.

func (*MultiKeyCryptoEncoder) Decode

func (mke *MultiKeyCryptoEncoder) Decode(ctx context.Context, buffer []byte) (context.Context, []byte, error)

Decode decrypts a message created with Encode.

Given that the MultiKeyCryptoEncoder used to Decode the message is expected to be initialized with a single key or a small subset of the keys used to encode the message, Decode will simply try to decode each key with each keyholder configured.

If it finds one key that can be decoded successfully by one of its keyholders, and this key can decrypt the data without errors, Decode() will return success with the result of the operation.

func (*MultiKeyCryptoEncoder) Encode

func (mke *MultiKeyCryptoEncoder) Encode(data []byte) ([]byte, error)

Encode encrypts the data so that it can be decrypted by any one keyholder.

Encode invokes the configured CryptoFactory to generate a random key and a BinaryEncoder to encrypt the data. It then invokes each keyholder in turn to encrypt this random key alongside the encrypted message.

The returned byte array has the format:

[varint: length of ciphertext][ciphertext]
[varint: length of key encrypted with keyholder[0]][key encrypted with keyholder[0]]
[varint: length of key encrypted with keyholder[1]][key encrypted with keyholder[1]]
[...]

type MultiKeyCryptoSetter

type MultiKeyCryptoSetter func(*MultiKeyCryptoEncoder) error

func WithAllocator

func WithAllocator(allocator Allocator) MultiKeyCryptoSetter

func WithKeyHolder

func WithKeyHolder(be ...BinaryEncoder) MultiKeyCryptoSetter

type SigningEncoder

type SigningEncoder struct {
	// contains filtered or unexported fields
}

SigningEncoder is an encoder that adds a cryptographically strong signature to the data.

Data will fail to decode if the signature is invalid.

func NewSigningEncoder

func NewSigningEncoder(rng *rand.Rand, setters ...SigningSetter) (*SigningEncoder, error)

func (*SigningEncoder) Decode

func (t *SigningEncoder) Decode(ctx context.Context, value []byte) (context.Context, []byte, error)

func (*SigningEncoder) Encode

func (t *SigningEncoder) Encode(data []byte) ([]byte, error)

type SigningKey

type SigningKey [64]byte

func SigningKeyFromSlice

func SigningKeyFromSlice(slice []byte) (*SigningKey, error)

func (*SigningKey) ToBytes

func (pk *SigningKey) ToBytes() *[64]byte

type SigningSetter

type SigningSetter func(*SigningEncoder) error

func UseSigningKey

func UseSigningKey(signing *SigningKey) SigningSetter

func UseVerifyingKey

func UseVerifyingKey(verify *VerifyingKey) SigningSetter

type StringEncoder

type StringEncoder interface {
	Encode([]byte) (string, error)
	Decode(context.Context, string) (context.Context, []byte, error)
}

StringEncoders convert an array of bytes into a string safe for specific applications.

For example: mime64 encoding, url encoding, hex, ...

type SymmetricEncoder

type SymmetricEncoder struct {
	// contains filtered or unexported fields
}

func NewSymmetricEncoder

func NewSymmetricEncoder(rng *rand.Rand, setters ...SymmetricSetter) (*SymmetricEncoder, error)

NewSymmetricEncoder creates a new encoder using AES in GCM mode as a symmetric cipher.

A typical way to use the encoder would be:

rng := rand.New(srand.Source)  // using github.com/enkit/lib/srand library.
...
be, err := NewSymmetricEncoder(rng, ReadOrGenerateSymmetricKey("/etc/keys/connect.key", 0))
if err != nil ...

Or:

key, err := GenerateSymmetricKey(rng, 0)
if err != nil ...

be, err := NewSymmetricEncoder(rng, UseSymmetricKey(key))
...

followed by calls to Encode() and Decode().

func (*SymmetricEncoder) Decode

func (t *SymmetricEncoder) Decode(ctx context.Context, ciphertext []byte) (context.Context, []byte, error)

func (*SymmetricEncoder) Encode

func (t *SymmetricEncoder) Encode(data []byte) ([]byte, error)

type SymmetricSetter

type SymmetricSetter func(*SymmetricEncoder) error

func ReadOrGenerateSymmetricKey

func ReadOrGenerateSymmetricKey(path string, size int) SymmetricSetter

Reads a key from a file, or creates a new one and stores it in a file. Returns error if it can't succeed in generating or storing a new key.

size is the size in bits of the desired key. If left to 0, defaults to 256 bits.

The generated (or read) file is just the raw content of the key. For example, for a key of 256 bits, it will generate a file of exactly 32 bytes, containing the binary encoded key.

func UseSymmetricKey

func UseSymmetricKey(key []byte) SymmetricSetter

UseSymmetricKey uses a key supplied as an array of bytes.

The key can be any array of bytes long enough for the block cipher selected. Use GenerateSymmetricKey to create one.

func WithGeneratedSymmetricKey

func WithGeneratedSymmetricKey(size int) SymmetricSetter

Creates a new random key and stores it in settings, or return error.

size represents the key size in bits. If size is 0, uses a default key size of 256 bits.

type TimeEncoder

type TimeEncoder struct {
	// contains filtered or unexported fields
}

TimeEncoder is an encoder that saves the time the data was encoded.

On Decode, it checks with the supplied validity and time source, and fails validation if the data is considered expired.

If data is expired is determined solely by the consumer of the data, based on the time the data was created.

Expiry information is not encoded in the resulting byte array.

func NewTimeEncoder

func NewTimeEncoder(source TimeSource, validity time.Duration) *TimeEncoder

NewTimeEncoder creates a new TimeEncoder.

source is a TimeSource to read the time from. validity is used on decode together with the issued time carried with the data to determine if the data is to be considered expired or not.

func (*TimeEncoder) Decode

func (t *TimeEncoder) Decode(ctx context.Context, data []byte) (context.Context, []byte, error)

Decode decodes TimeEncoder encoded data.

It returns ExpiredError if the data was issued before the validity time supplied to NewTimeEncoder. It returns a generic error if the data is considered corrupted or invalid for any other reason.

Decode always tries to return as much data as possible, together with IssuedTime and MaxTime information in the context, even if the data is expired. This allows, for example, to write code to override/ignore the ExpiredError, or to print user friendly messages indicating when the data was expired.

func (*TimeEncoder) Encode

func (t *TimeEncoder) Encode(data []byte) ([]byte, error)

type TimeSource

type TimeSource func() time.Time

TimeSource is a function that returns the current time.

type TypeEncoder

type TypeEncoder struct {
	// contains filtered or unexported fields
}

func NewTypeEncoder

func NewTypeEncoder(be BinaryEncoder, setter ...TypeEncoderSetter) *TypeEncoder

func (*TypeEncoder) Decode

func (t *TypeEncoder) Decode(ctx context.Context, data []byte, output interface{}) (context.Context, error)

func (*TypeEncoder) Encode

func (t *TypeEncoder) Encode(data interface{}) ([]byte, error)

type TypeEncoderSetter

type TypeEncoderSetter func(*TypeEncoder)

func WithMarshaller

func WithMarshaller(ma marshal.Marshaller) TypeEncoderSetter

WithMarshaller selects a specific marshaller to use with NewTypeEncoder.

If none is specified, by default NewTypeEncoder will use a gob encoder. Note that different marshaller may impose different constraints.

type VerifyingKey

type VerifyingKey [32]byte

func VerifyingKeyFromSlice

func VerifyingKeyFromSlice(slice []byte) (*VerifyingKey, error)

func (*VerifyingKey) ToBytes

func (pk *VerifyingKey) ToBytes() *[32]byte

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL