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
- Variables
- func GenerateAsymmetricKeys(rng *rand.Rand) (*AsymmetricKey, *AsymmetricKey, error)
- func GenerateSigningKey(rng *rand.Rand) (*VerifyingKey, *SigningKey, error)
- func GenerateSymmetricKey(rng *rand.Rand, size int) ([]byte, error)
- type Allocator
- type AsymmetricEncoder
- type AsymmetricKey
- type AsymmetricNonce
- type AsymmetricSetter
- type Base64Encoder
- type BinaryEncoder
- type ChainedEncoder
- type CryptoFactory
- type ExpireEncoder
- type MultiKeyCryptoEncoder
- type MultiKeyCryptoSetter
- type SigningEncoder
- type SigningKey
- type SigningSetter
- type StringEncoder
- type SymmetricEncoder
- type SymmetricSetter
- type TimeEncoder
- type TimeSource
- type TypeEncoder
- type TypeEncoderSetter
- type VerifyingKey
Constants ¶
const AsymmetricKeyLength = 32
const AsymmetricNonceLength = 24
Variables ¶
var ExpiredError = fmt.Errorf("signature expired")
ExpiredError is returned if the data is considered expired.
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.
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 { ... }
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 ¶
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) 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 ¶
func (n *AsymmetricNonce) ToByte() *[AsymmetricNonceLength]byte
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
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
type CryptoFactory ¶
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 ¶
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.
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:
- Generating a random key and encrypting data with this random key. This is the Data Encryption Key, or DEK.
- 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)
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().
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 ¶
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.
type TimeSource ¶
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) 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