ch

package module
v0.0.0-...-eafe2ac Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2021 License: NCSA Imports: 13 Imported by: 3

Documentation

Overview

Crypto Helper: A friendly cryptographic helper for common use cases.

Crypto Helper is distributed under the NCSA license. This license should be inculded in the source distribution; if you've received a copy of this source and no associated license, please visit:

https://git.destrealm.org/go/cryptohelper

Index

Constants

View Source
const CryptoVersion = "0.1.0"

CryptoVersion declares the version used by the crypto API defined in this package and its associated data types. Semver is followed somewhat closely by this identifier: Patch levels indicate fixes to the crypto spec where published behavior and code behavior may not align, minor version bumps indicate new features or changes to the crypto API that may induce minor code breakage, and major version bumps indicate significant crypto API changes that are guaranteed to break downstream code.

Variables

View Source
var DefaultIVGenerator = func(size int) ([]byte, error) {
	if size == 0 {
		return []byte{}, ErrInvalidIVSize
	}

	buf := make([]byte, size)
	num, err := rand.Read(buf)
	if err != nil {
		return nil, err
	}
	if num < size {
		return nil, fmt.Errorf("wrote %d bytes out of %d required", num, size)
	}
	return buf, nil
}

DefaultIVGenerator is the default initialization vector generator function. This function doesn't do anything fancy: It just creates an initialization vector of size `size` containing cryptographically-secure randomly generated bytes.

View Source
var ErrCreatingCipher = errors.NewError("could not create new cipher")

ErrCreatingCipher is typically returned when initializing a cipher fails. As we only use AES in this package, this is most likely due to key length errors, which should be either 16 (AES-128), 24 (AES-192), or 32 (AES-256) bytes. See https://golang.org/pkg/crypto/aes/

View Source
var ErrCreatingEngine = errors.NewError("error creating engine")

ErrCreatingEngine is return when a crypto engine cannot be created. This error should typically not be returned through most use cases, but it is likely to appear if an implementer does not properly adhere to the interfaces defined in api.go and one of the related convenience New* methods fails to cast it to the appropriate interface.

View Source
var ErrCreatingIV = errors.NewError("could not create initialization vector")

ErrCreatingIV is typically returned when an encryption call cannot create the initialization vector. This may be due to incorrect block size or padding and may be implementation dependent. If this error is returned, it may be necessary to examine the type of block cipher in use (typically AES) as it may not be compliant with our use case.

View Source
var ErrCreatingKey = errors.NewError("could not create key or subkey")

ErrCreatingKey is returned whenever a new derived key or subkey cannot be generated. This may be due to problems bubbling up from the underlying OS and insufficient entropy provided to api.GenerateKeyData.

View Source
var ErrDecoding = errors.NewError("base64 decoding error")

ErrDecoding is returned when a base64 decoding error occurs. Drilling down into this will typically yield why the error occurred in the first place (see Error.OriginalError).

View Source
var ErrGeneratingKey = errors.NewError("error generating key")

ErrGeneratingKey is typically returned when an error occurs generating the key. This is usually the result of a failure during entropy collection but may have other pathologies.

View Source
var ErrInvalidIVSize = errors.NewError("invalid initialization vector size")

ErrInvalidIVSize is returned if a size of zero (0) is requested from IV generators. This is also returned by ImprovedByteSerializer if IV lengthst exceed serialization limits (currently 256 bytes or the maximum value of a uint8).

View Source
var ErrKeySizeMismatch = errors.NewError("key sizes are mismatched")

ErrKeySizeMismatch is returned when a key replacement is attempted with a key that does not match the length of the prior key.

View Source
var ErrSerializationFailed = errors.NewError("payload serialization failed")

ErrSerializationFailed is returned whenever a payload serialization is attempted but fails.

View Source
var MaxKeySize = 1024 * 1024 * 10

MaxKeySize defines the maximum key size for Crypto Helper. By default, this is 10MiB.

View Source
var TimeBasedIVGenerator = func(size int) ([]byte, error) {
	if size == 0 {
		return []byte{}, ErrInvalidIVSize
	}

	writer := &bytes.Buffer{}
	buf := make([]byte, size-8)
	now := time.Now().UTC()

	err := binary.Write(writer, binary.LittleEndian, now.UnixNano())
	if err != nil {
		return nil, err
	}

	num, err := rand.Read(buf)
	if err == nil && num == size-8 {
		writer.Write(buf)
	} else {
		if err != nil {
			return nil, err
		}

		return nil, fmt.Errorf("wrote %d bytes out of %d required", num, size-8)
	}

	return writer.Bytes(), nil
}

TimeBasedIVGenerator is an initialization vector generator that creates vectors based in part on the current timestamp. This is ideal for initialization vectors 16 bytes or larger in size (the algorithms supported here require 16-byte IVs). This works by dividing the initialization vector space into two 8-byte chunks: The first chunk is devoted to containing a byte-encoded float64 timestamp (second and nanosecond components), and the second contains 8 cryptographically-secure generated bytes. These are then concatenated into a single 16-byte IV.

Since initialization vectors are transmitted in the clear and are part of our signed payload, we can validate tokens that use time-based IVs by decoding the timestamp component and comparing it to our configured expiration time. If the timestamp of the IV exceeds the expiration, then we know the payload is no longer valid, and we need not decrypt the payload or store additional timestamp data therein.

Functions

func DecodeTimeBasedIV

func DecodeTimeBasedIV(iv []byte) (time.Time, []byte, error)

DecodeTimeBasedIV decodes initialization vectors created by the TimeBasedIVGenerator. This performs the generation task in reverse: We extract the first 8 bytes of the IV, read them into a float64, and create a timestamp instance from the data. The decoding function doesn't validate the timestamp--that's up to the caller.

Be aware that Crypto Helper always signs its payloads, which includes the encrypted data plus the initialization vector. Time-based IVs are never decoded before they are signature-validated.

func Dumphex

func Dumphex(buf []byte) string

func GenerateKey

func GenerateKey(length int) ([]byte, error)

GenerateKeyData generates a cryptographically-secure byte slice for use as a key of size `length`.

func GenerateKeyString

func GenerateKeyString(length int) (string, error)

GenerateKeyString returns a base64 implementation of a random series of bytes as generated by GenerateKey.

Types

type Base64Serializer

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

Base64Serializer encodes serialized payloads first as a slice of bytes and then as base64 output.

func (*Base64Serializer) Serialize

func (s *Base64Serializer) Serialize(payload *Payload) ([]byte, error)

func (*Base64Serializer) SerializeForSigning

func (s *Base64Serializer) SerializeForSigning(payload *Payload) ([]byte, error)

func (*Base64Serializer) Unserialize

func (s *Base64Serializer) Unserialize(src []byte) *Payload

type Base64SizedSerializer

type Base64SizedSerializer struct {
	*Base64Serializer
	// contains filtered or unexported fields
}

func (*Base64SizedSerializer) SetBlockSize

func (s *Base64SizedSerializer) SetBlockSize(i int)

func (*Base64SizedSerializer) SetDigestSize

func (s *Base64SizedSerializer) SetDigestSize(i int)

type ByteSerializer

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

func (*ByteSerializer) Serialize

func (b *ByteSerializer) Serialize(payload *Payload) ([]byte, error)

func (*ByteSerializer) SerializeForSigning

func (b *ByteSerializer) SerializeForSigning(payload *Payload) ([]byte, error)

func (*ByteSerializer) SetBlockSize

func (b *ByteSerializer) SetBlockSize(s int)

func (*ByteSerializer) SetDigestSize

func (b *ByteSerializer) SetDigestSize(s int)

func (*ByteSerializer) Unserialize

func (b *ByteSerializer) Unserialize(data []byte) *Payload

type CompositeKey

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

func NewCompositeKey

func NewCompositeKey(hmac, cipher Keyer) *CompositeKey

func (*CompositeKey) Cipher

func (c *CompositeKey) Cipher() Keyer

func (*CompositeKey) HMAC

func (c *CompositeKey) HMAC() Keyer

type CompositeKeyer

type CompositeKeyer interface {
	HMAC() Keyer
	Cipher() Keyer
}

CompositeKeyer defines a basic interface type for crypto calls that require both a cipher and an HMAC key.

type Encrypter

type Encrypter interface {
	Encrypt([]byte) (*Payload, error)
	Decrypt(*Payload) []byte
	SetKey(key []byte) error
	SetIVGenerator(generator IVGenerator)
	SetSerializer(SerializerFunc)
}

type EngineConfiguration

type EngineConfiguration struct {
	// Sets the block cipher to use. This should typically be AES.
	BlockCipher func([]byte) (cipher.Block, error)

	// Sets the hash algorithm for use in message signing.
	Hasher func() hash.Hash

	// Enables MAC truncation. If set, the full signature will not be concatenated
	// to the ciphertext. How these behave depends on the HMAC algorithm's output
	// size (hash.Hash.Size()) and the MaxTruncatedLength.
	TruncatedMAC bool

	// If TruncatedMAC is enabled, this value controls the maximum truncated
	// length. This value cannot be less than 16 and should be 32 or greater. If
	// this value is 8 or less, it is interpreted as a fractional denominator,
	// e.g. a value of 2 will divide the length of the HMAC in half, 4 will divide
	// it into a quarter, and so forth. Fractional denominators will likewise
	// generate output no less than 16 bytes.
	MaxTruncatedLength uint16

	// Composite key, optionally used for both encryption and signing.
	CompositeKey CompositeKeyer

	// Cipher key used for encryption.
	CipherKey []byte

	// HMAC key used for signing.
	HMACKey []byte

	// Configures the serializer to use for managing cipher text, signature, and
	// initialization vector output. This value may be nil which implicitly sets
	// the default serializer to ByteSerializer.
	Serializer SerializerFunc

	// Configures the initialization vector generator to use. This value may be
	// nil which implicitly sets the default IVGenerator to DefaultIVGenerator.
	IVGenerator IVGenerator
}

EngineConfiguration sets engine attributes and behaviors. Refer to individual fields for documentation.

type IVGenerator

type IVGenerator func(int) ([]byte, error)

type ImprovedByteSerializer

type ImprovedByteSerializer struct{}

ImprovedByteSerializer need not know the block or digest size ahead of time; instead, it encodes the length of the cipher text and initialization vector at the start of the byte stream. These values are used to deduce the length of the payload's signature, once encoded.

Limitation: Initialization vectors cannot be longer than 256 bytes in length.

func (*ImprovedByteSerializer) Serialize

func (ser *ImprovedByteSerializer) Serialize(payload *Payload) ([]byte, error)

func (*ImprovedByteSerializer) SerializeForSigning

func (ser *ImprovedByteSerializer) SerializeForSigning(payload *Payload) ([]byte, error)

func (*ImprovedByteSerializer) Unserialize

func (ser *ImprovedByteSerializer) Unserialize(data []byte) *Payload

type JSONSerializer

type JSONSerializer struct{}

func (*JSONSerializer) Serialize

func (s *JSONSerializer) Serialize(payload *Payload) ([]byte, error)

func (*JSONSerializer) SerializeForSigning

func (s *JSONSerializer) SerializeForSigning(payload *Payload) ([]byte, error)

func (*JSONSerializer) Unserialize

func (s *JSONSerializer) Unserialize(data []byte) *Payload

type Key

type Key struct {
	Encoded string
	Key     []byte
}

func (*Key) Bytes

func (k *Key) Bytes() ([]byte, error)

func (*Key) MustGetBytes

func (k *Key) MustGetBytes() []byte

MustGetBytes returns the decoded byte array from the specified key. If the decoding process fails, it panics.

type Keyer

type Keyer interface {
	// Bytes returns the byte array representation of the key or an error if the key could not be decoded.
	Bytes() ([]byte, error)

	// MustGetBytes returns the decoded byte representation of the key or panics if decoding fails.
	// Details of this method are up to the implementer. The Key type, as provided by this library, strictly wraps a []byte array.
	MustGetBytes() []byte
}

Keyer provides methods for implementations to offer decoding methods for keys. Since decoding could be an expensive operation, it can be conducted on demand by using this interface to define key types.

type Payload

type Payload struct {
	CipherText []byte `json:"c"`
	IV         []byte `json:"i"`
	Signature  []byte `json:"s"`
}

func NewPayload

func NewPayload(cipherText []byte, iv []byte, signature []byte) *Payload

func (*Payload) Empty

func (p *Payload) Empty() bool

func (*Payload) Merge

func (p *Payload) Merge(payload *Payload) *Payload

func (*Payload) String

func (p *Payload) String() string

type PayloadValidator

type PayloadValidator func(*Payload, []byte) bool

PayloadValidator can be used to validate a payload plus plaintext data. The first argument is the Payload; the second is the decrypted plaintext. This is useful for validating time-based initialization vectors.

func TimeBasedIVValidator

func TimeBasedIVValidator(maxage time.Duration) PayloadValidator

TimeBasedIVValidator is a closure that accepts a maximum token age (maxage) and returns a validator for use with Sealer's OpenValidator() function.

type Sealer

type Sealer interface {
	Open([]byte) []byte
	OpenValidator([]byte, PayloadValidator) []byte
	Seal([]byte) []byte
	SealAsPayload([]byte) *Payload
	SetSerializer(SerializerFunc)
}

func NewSealer

func NewSealer(config *EngineConfiguration) (Sealer, error)

NewSealer creates and returns an implementation returning the Sealer interface based on the configuration provided.

type Serializer

type Serializer interface {
	Serialize(*Payload) ([]byte, error)
	SerializeForSigning(*Payload) ([]byte, error)
	Unserialize([]byte) *Payload
}

func NewBase64Serializer

func NewBase64Serializer() Serializer

func NewByteSerializer

func NewByteSerializer() Serializer

func NewImprovedByteSerializer

func NewImprovedByteSerializer() Serializer

func NewJSONSerializer

func NewJSONSerializer() Serializer

type SerializerFunc

type SerializerFunc func() Serializer

SerializerFunc is a convenience type that describes the signature of serializer factory function that returns a pre-configured Serializer instance.

func NewBase64SerializerWith

func NewBase64SerializerWith(serializer Serializer) SerializerFunc

type Signer

type Signer interface {
	Sign(message []byte) []byte
	SignPayload(*Payload) (*Payload, error)
	Verify(signature, message []byte) bool
	VerifyBytes(message []byte) bool
	VerifyPayload(payload *Payload) (bool, error)
	SetHMAC(hmac []byte)
	SetSerializer(serializer SerializerFunc)
}

func NewSigner

func NewSigner(config *EngineConfiguration) (Signer, error)

NewSigner creates an implementation returning the Signer interface based on the configuration provided.

type SigningEncrypter

type SigningEncrypter interface {
	Encrypt([]byte) (*Payload, error)
	Decrypt(*Payload) []byte
	Sign([]byte) []byte
	SignPayload(*Payload) (*Payload, error)
	Verify([]byte, []byte) bool
	VerifyBytes([]byte) bool
	VerifyPayload(*Payload) (bool, error)
	SetHMAC(hmac []byte)
	SetIVGenerator(generator IVGenerator)
	SetKey(key []byte) error
	SetSerializer(SerializerFunc)
}

func NewSigningEncrypterEngine

func NewSigningEncrypterEngine(config *EngineConfiguration) (SigningEncrypter, error)

NewSigningEncrypterEngine creates an implementation returning the SigningEncrypter interface based on the configuration provided.

type SigningEncryptionEngine

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

SigningEncryptionEngine defines an encryption engine that implements the interfaces Signer, Encrypter, and Sealer.

func (*SigningEncryptionEngine) Decrypt

func (engine *SigningEncryptionEngine) Decrypt(payload *Payload) []byte

Decrypt the provided payload and return a byte slice of the plain text component. The returned plain text may need to be further decoded or processed.

func (*SigningEncryptionEngine) Encrypt

func (engine *SigningEncryptionEngine) Encrypt(data []byte) (*Payload, error)

Encrypt the byte slice input returning a processed Payload containing the ciphertext, signature, and initialization vector.

func (*SigningEncryptionEngine) NewSubkey

func (engine *SigningEncryptionEngine) NewSubkey() ([]byte, []byte, error)

NewSubkey creates a new subkey from the configured cipher and HMAC keys for this engine. Both keys inherit the length of their parents and cannot be changed. Key validation is performed by using the parent HMAC key to sign the subkey's randomly-generated cipher key; this hash is then used with a key derivation formula (PBKDF2) to create a signing key of the appropriate length. The KDF salt is generated randomly and prepended to the signing key.

Signing keys will always be digest_size * 2 with the first 16 bytes reserved for the random salt.

func (*SigningEncryptionEngine) Open

func (engine *SigningEncryptionEngine) Open(message []byte) []byte

Open the provided message, validating its signature, decrypting it, and return the resulting plain text.

Contrasted with the Decrypt() and Verify() methods, this one does not return an error. Instead, if an error condition is encountered during the verification or decryption processes, an empty byte slice is returned to prevent information leakage.

Most applications that require encrypt-then-MAC behavior should use this function instead of either the Signer or Encrypter interfaces.

func (*SigningEncryptionEngine) OpenValidator

func (engine *SigningEncryptionEngine) OpenValidator(message []byte, validator PayloadValidator) []byte

OpenValidator is like Open() but accepts a validation function that itself accepts a payload plus the decrypted ciphertext against which is may validate further. As with Open(), this will return an empty byte slice if the validation fails.

func (*SigningEncryptionEngine) Seal

func (engine *SigningEncryptionEngine) Seal(ciphertext []byte) (buf []byte)

Seal the provided ciphertext. This will generate a payload internally, serializing it using the serializer configured for this engine, and return the resulting byte array.

Contracted with the Encrypt() and Sign() methods, this does not return an error. Instead, if an error condition occurs during the signing or decryption processes, an empty byte array is returned to prevent information leakage.

Most applications requiring encrypt-then-MAC should use this function instead of either the Signer or Encrypter interface.

func (*SigningEncryptionEngine) SealAsPayload

func (engine *SigningEncryptionEngine) SealAsPayload(ciphertext []byte) *Payload

SealAsPayload seals the provided payload, updating it with the generated ciphertext, signature, and initialization vector. This function is mostly useful for callers who wish to serialize the payload on their own, independently from the one configured for this engine.

func (*SigningEncryptionEngine) Serialize

func (engine *SigningEncryptionEngine) Serialize(payload *Payload) ([]byte, error)

Serialize the provided payload returning a byte slice defined by the serializer type. Most configurations will likely use the ByteSerializer which concatenates the payload using cipher and HMAC lengths to unserialize.

Other serializers include the JSON serializer which returns the payload as a JSON-formatted string represented by a byte slice.

func (*SigningEncryptionEngine) SerializeForSigning

func (engine *SigningEncryptionEngine) SerializeForSigning(payload *Payload) ([]byte, error)

SerializeForSigning serializes the payload for signing. This differs from Serialize() in that it partially concatenates the payload (ciphertext and initalization vector) and returns the result.

func (*SigningEncryptionEngine) SetHMAC

func (engine *SigningEncryptionEngine) SetHMAC(key []byte)

SetHMAC configures the HMAC key to use for signing. Be aware that this should typically not be used on engines that are intended to persist for long periods of time.

This method may be useful for keys that are on an expiration rotation and allows callers to cycle through multiple key versions before encountering one that works.

func (*SigningEncryptionEngine) SetIVGenerator

func (engine *SigningEncryptionEngine) SetIVGenerator(generator IVGenerator)

SetIVGenerator changes the generator used to create initialization vectors. This is not typically needed and is not recommended.

func (*SigningEncryptionEngine) SetKey

func (engine *SigningEncryptionEngine) SetKey(key []byte) error

SetKey changes the configured cipher key and reinitializes associated data structures. New keys must be exactly the same length as the key they're replacing.

This method may be useful for keys that are on an expiration rotation and allows callers to cycle through multiple key versions before encountering one that works.

func (*SigningEncryptionEngine) SetSerializer

func (engine *SigningEncryptionEngine) SetSerializer(serializer SerializerFunc)

SetSerializer changes the configured serializer. This accepts a serializer function which is then passed the cipher blocksize and the HMAC algorithm's output size. This function must return the Serializer interface and is defined in api.go.

func (*SigningEncryptionEngine) Sign

func (engine *SigningEncryptionEngine) Sign(ciphertext []byte) []byte

Sign the provided ciphertext using the configured HMAC hash algorithm.

func (*SigningEncryptionEngine) SignPayload

func (engine *SigningEncryptionEngine) SignPayload(payload *Payload) (*Payload, error)

SignPayload is similar to Sign() with the exception that it accepts a Payload argument rather than a pre-assembled byte slice. This uses the configured serializer to convert the payload to an array of bytes.

Unlike Sign(), this method may return an error if the serialization process fails.

func (*SigningEncryptionEngine) Unserialize

func (engine *SigningEncryptionEngine) Unserialize(message []byte) *Payload

Unserialize parses the payload using the configured cipher and HMAC, calculating the expected lengths of each (including the initialization vector), splitting apart the slice and returning a generated Payload.

func (*SigningEncryptionEngine) ValidateSubkey

func (engine *SigningEncryptionEngine) ValidateSubkey(cipherkey, hmackey []byte) bool

ValidateSubkey accepts two byte slices, one for the subkey's cipher and one for the subkey's HMAC. Referring to NewSubkey()'s documentation, we know that the HMAC key is derived from signing the subkey's cipher with the master HMAC key and passing it through a key derivation formula (PBKDF2). To validate the key pair, we simply need to re-sign the cipher key, run it through the KDF, and compare the hashes.

Note that the first 16 bytes of the HMAC key will always be the salt used by the KDF.

func (*SigningEncryptionEngine) Verify

func (engine *SigningEncryptionEngine) Verify(signature, message []byte) bool

Verify the signature and the provided message. This uses hmac.Equal for constant-time comparison of the provided signature versus the HMAC we generate for validation.

func (*SigningEncryptionEngine) VerifyBytes

func (engine *SigningEncryptionEngine) VerifyBytes(message []byte) bool

VerifyBytes is like Verify() but unserializes the byte array first. The message argument is expected to contain the entire signed payload (this includes the initialization vector).

func (*SigningEncryptionEngine) VerifyPayload

func (engine *SigningEncryptionEngine) VerifyPayload(payload *Payload) (bool, error)

VerifyPayload is similar to Verify() with the exception that it accepts a Payload argument rather than a pre-assembled byte slice. This users the configured serializer to convert the payload to an array of bytes.

Unlike Verify(), this method may return an error if the serialization process fails. Clients that don't wish to leak information about the verification process even in the event of a serialize failure can simply return the boolean value as is.

type SizedSerializer

type SizedSerializer interface {
	SetBlockSize(int)
	SetDigestSize(int)
}

type SubkeySigner

type SubkeySigner interface {
	NewSubkey() ([]byte, []byte, error)
	ValidateSubkey([]byte, []byte) bool
}

Jump to

Keyboard shortcuts

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