README
¶
MemGuard
Secure software enclave for storage of sensitive information in memory.
This package attempts to reduce the likelihood of sensitive data being exposed. It supports all major operating systems and is written in pure Go.
Features
- Sensitive data is encrypted and authenticated in memory using xSalsa20 and Poly1305 respectively. The scheme also defends against cold-boot attacks.
- Memory allocation bypasses the language runtime by using system calls to query the kernel for resources directly. This avoids interference from the garbage-collector.
- Buffers that store plaintext data are fortified with guard pages and canary values to detect spurious accesses and overflows.
- Effort is taken to prevent sensitive data from touching the disk. This includes locking memory to prevent swapping and handling core dumps.
- Kernel-level immutability is implemented so that attempted modification of protected regions results in an access violation.
- Multiple endpoints provide session purging and safe termination capabilities as well as signal handling to prevent remnant data being left behind.
- Side-channel attacks are mitigated against by making sure that the copying and comparison of data is done in constant-time.
- Accidental memory leaks are mitigated against by harnessing the garbage-collector to automatically destroy containers that have become unreachable.
Some features were inspired by libsodium, so credits to them.
Full documentation and a complete overview of the API can be found here. Interesting and useful code samples can be found within the examples subpackage.
Installation
$ go get github.com/awnumar/memguard
API is experimental and may have unstable changes. You should pin a version. [modules]
Contributing
- Submitting program samples to
./examples
. - Reporting bugs, vulnerabilities, and any difficulties in using the API.
- Writing useful security and crypto libraries that utilise memguard.
- Implementing kernel-specific/cpu-specific protections.
- Submitting performance improvements.
Issues are for reporting bugs and for discussion on proposals. Pull requests should be made against master.
Documentation
¶
Overview ¶
Package memguard implements a secure software enclave for the storage of sensitive information in memory.
package main import ( "fmt" "os" "github.com/awnumar/memguard" ) func main() { // Safely terminate in case of an interrupt signal memguard.CatchInterrupt() // Purge the session when we return defer memguard.Purge() // Generate a key sealed inside an encrypted container key := memguard.NewEnclaveRandom(32) // Passing the key off to another function key = invert(key) // Decrypt the result returned from invert keyBuf, err := key.Open() if err != nil { fmt.Fprintln(os.Stderr, err) return } defer keyBuf.Destroy() // Um output it fmt.Println(keyBuf.Bytes()) } func invert(key *memguard.Enclave) *memguard.Enclave { // Decrypt the key into a local copy b, err := key.Open() if err != nil { memguard.SafePanic(err) } defer b.Destroy() // Destroy the copy when we return // Open returns the data in an immutable buffer, so make it mutable b.Melt() // Set every element to its complement for i := range b.Bytes() { b.Bytes()[i] = ^b.Bytes()[i] } // Return the new data in encrypted form return b.Seal() // <- sealing also destroys b }
There are two main container objects exposed in this API. Enclave objects encrypt data and store the ciphertext whereas LockedBuffers are more like guarded memory allocations. There is a limit on the maximum number of LockedBuffer objects that can exist at any one time, imposed by the system's mlock limits. There is no limit on Enclaves.
The general workflow is to store sensitive information in Enclaves when it is not immediately needed and decrypt it when and where it is. After use, the LockedBuffer should be destroyed.
If you need access to the data inside a LockedBuffer in a type not covered by any methods provided by this API, you can type-cast the allocation's memory to whatever type you want.
key := memguard.NewBuffer(32) keyArrayPtr := (*[32]byte)(unsafe.Pointer(&key.Bytes()[0])) // do not dereference
This is of course an unsafe operation and so care must be taken to ensure that the cast is valid and does not result in memory unsafety. Further examples of code and interesting use-cases can be found in the examples subpackage.
Several functions exist to make the mass purging of data very easy. It is recommended to make use of them when appropriate.
// Start an interrupt handler that will clean up memory before exiting memguard.CatchInterrupt() // Purge the session when returning from the main function of your program defer memguard.Purge() // Use the safe variants of exit functions provided in the stdlib memguard.SafeExit(1) memguard.SafePanic(err) // Destroy LockedBuffers as soon as possible after using them b, err := enclave.Open() if err != nil { memguard.SafePanic(err) } defer b.Destroy()
Core dumps are disabled by default. If you absolutely require them, you can enable them by using unix.Setrlimit to set RLIMIT_CORE to an appropriate value.
Index ¶
- func CatchInterrupt()
- func CatchSignal(f func(os.Signal), signals ...os.Signal)
- func Purge()
- func SafeExit(c int)
- func SafePanic(v interface{})
- func ScrambleBytes(buf []byte)
- func WipeBytes(buf []byte)
- type Enclave
- type LockedBuffer
- func NewBuffer(size int) *LockedBuffer
- func NewBufferFromBytes(src []byte) *LockedBuffer
- func NewBufferFromEntireReader(r io.Reader) (*LockedBuffer, error)
- func NewBufferFromReader(r io.Reader, size int) (*LockedBuffer, error)
- func NewBufferFromReaderUntil(r io.Reader, delim byte) (*LockedBuffer, error)
- func NewBufferRandom(size int) *LockedBuffer
- func (b *LockedBuffer) ByteArray16() *[16]byte
- func (b *LockedBuffer) ByteArray32() *[32]byte
- func (b *LockedBuffer) ByteArray64() *[64]byte
- func (b *LockedBuffer) ByteArray8() *[8]byte
- func (b *LockedBuffer) Bytes() []byte
- func (b *LockedBuffer) Copy(src []byte)
- func (b *LockedBuffer) CopyAt(offset int, src []byte)
- func (b *LockedBuffer) Destroy()
- func (b *LockedBuffer) EqualTo(buf []byte) bool
- func (b *LockedBuffer) Freeze()
- func (b *LockedBuffer) Int16() []int16
- func (b *LockedBuffer) Int32() []int32
- func (b *LockedBuffer) Int64() []int64
- func (b *LockedBuffer) Int8() []int8
- func (b *LockedBuffer) IsAlive() bool
- func (b *LockedBuffer) IsMutable() bool
- func (b *LockedBuffer) Melt()
- func (b *LockedBuffer) Move(src []byte)
- func (b *LockedBuffer) MoveAt(offset int, src []byte)
- func (b *LockedBuffer) Reader() *bytes.Reader
- func (b *LockedBuffer) Scramble()
- func (b *LockedBuffer) Seal() *Enclave
- func (b *LockedBuffer) Size() int
- func (b *LockedBuffer) String() string
- func (b *LockedBuffer) Uint16() []uint16
- func (b *LockedBuffer) Uint32() []uint32
- func (b *LockedBuffer) Uint64() []uint64
- func (b *LockedBuffer) Wipe()
- type Stream
Constants ¶
Variables ¶
Functions ¶
func CatchInterrupt ¶
func CatchInterrupt()
CatchInterrupt is a wrapper around CatchSignal that makes it easy to safely handle receiving interrupt signals. If an interrupt is received, the process will wipe sensitive data in memory before terminating.
A subsequent call to CatchSignal will override this call.
func CatchSignal ¶
CatchSignal assigns a given function to be run in the event of a signal being received by the process. If no signals are provided all signals will be caught.
1. Signal is caught by the process 2. Interrupt handler is executed 3. Secure session state is wiped 4. Process terminates with exit code 1
This function can be called multiple times with the effect that only the last call will have any effect.
func Purge ¶
func Purge()
Purge resets the session key to a fresh value and destroys all existing LockedBuffers. Existing Enclave objects will no longer be decryptable.
func SafeExit ¶
func SafeExit(c int)
SafeExit destroys everything sensitive before exiting with a specified status code.
func ScrambleBytes ¶
func ScrambleBytes(buf []byte)
ScrambleBytes overwrites an arbitrary buffer with cryptographically-secure random bytes.
Types ¶
type Enclave ¶
Enclave is a sealed and encrypted container for sensitive data.
func NewEnclave ¶
NewEnclave seals up some data into an encrypted enclave object. The buffer is wiped after the data is copied. If the length of the buffer is zero, the function will return nil.
A LockedBuffer may alternatively be converted into an Enclave object using its Seal method. This will also have the effect of destroying the LockedBuffer.
func NewEnclaveRandom ¶
NewEnclaveRandom generates and seals arbitrary amounts of cryptographically-secure random bytes into an encrypted enclave object. If size is not strictly positive the function will return nil.
func (*Enclave) Open ¶
func (e *Enclave) Open() (*LockedBuffer, error)
Open decrypts an Enclave object and places its contents into an immutable LockedBuffer. An error will be returned if decryption failed.
type LockedBuffer ¶
LockedBuffer is a structure that holds raw sensitive data.
The number of LockedBuffers that you are able to create is limited by how much memory your system's kernel allows each process to mlock/VirtualLock. Therefore you should call Destroy on LockedBuffers that you no longer need or defer a Destroy call after creating a new LockedBuffer.
func NewBuffer ¶
func NewBuffer(size int) *LockedBuffer
NewBuffer creates a mutable data container of the specified size.
func NewBufferFromBytes ¶
func NewBufferFromBytes(src []byte) *LockedBuffer
NewBufferFromBytes constructs an immutable buffer from a byte slice. The source buffer is wiped after the value has been copied over to the created container.
func NewBufferFromEntireReader ¶
func NewBufferFromEntireReader(r io.Reader) (*LockedBuffer, error)
NewBufferFromEntireReader reads from an io.Reader into an immutable buffer. It will continue reading until EOF.
A nil error is returned precisely when we managed to read all the way until EOF. Any data read is returned in either case.
func NewBufferFromReader ¶
func NewBufferFromReader(r io.Reader, size int) (*LockedBuffer, error)
NewBufferFromReader reads some number of bytes from an io.Reader into an immutable LockedBuffer.
An error is returned precisely when the number of bytes read is less than the requested amount. Any data read is returned in either case.
func NewBufferFromReaderUntil ¶
func NewBufferFromReaderUntil(r io.Reader, delim byte) (*LockedBuffer, error)
NewBufferFromReaderUntil constructs an immutable buffer containing data sourced from an io.Reader object.
If an error is encountered before the delimiter value, the error will be returned along with the data read up until that point.
func NewBufferRandom ¶
func NewBufferRandom(size int) *LockedBuffer
NewBufferRandom constructs an immutable buffer filled with cryptographically-secure random bytes.
func (*LockedBuffer) ByteArray16 ¶
func (b *LockedBuffer) ByteArray16() *[16]byte
ByteArray16 returns a pointer to some 16 byte array. Care must be taken not to dereference the pointer and instead pass it around as-is.
The length of the buffer must be at least 16 bytes in size and the LockedBuffer should not be destroyed. In either of these cases a nil value is returned.
func (*LockedBuffer) ByteArray32 ¶
func (b *LockedBuffer) ByteArray32() *[32]byte
ByteArray32 returns a pointer to some 32 byte array. Care must be taken not to dereference the pointer and instead pass it around as-is.
The length of the buffer must be at least 32 bytes in size and the LockedBuffer should not be destroyed. In either of these cases a nil value is returned.
func (*LockedBuffer) ByteArray64 ¶
func (b *LockedBuffer) ByteArray64() *[64]byte
ByteArray64 returns a pointer to some 64 byte array. Care must be taken not to dereference the pointer and instead pass it around as-is.
The length of the buffer must be at least 64 bytes in size and the LockedBuffer should not be destroyed. In either of these cases a nil value is returned.
func (*LockedBuffer) ByteArray8 ¶
func (b *LockedBuffer) ByteArray8() *[8]byte
ByteArray8 returns a pointer to some 8 byte array. Care must be taken not to dereference the pointer and instead pass it around as-is.
The length of the buffer must be at least 8 bytes in size and the LockedBuffer should not be destroyed. In either of these cases a nil value is returned.
func (*LockedBuffer) Bytes ¶
func (b *LockedBuffer) Bytes() []byte
Bytes returns a byte slice referencing the protected region of memory.
func (*LockedBuffer) Copy ¶
func (b *LockedBuffer) Copy(src []byte)
Copy performs a time-constant copy into a LockedBuffer. Move is preferred if the source is not also a LockedBuffer or if the source is no longer needed.
func (*LockedBuffer) CopyAt ¶
func (b *LockedBuffer) CopyAt(offset int, src []byte)
CopyAt performs a time-constant copy into a LockedBuffer at an offset. Move is preferred if the source is not also a LockedBuffer or if the source is no longer needed.
func (*LockedBuffer) Destroy ¶
func (b *LockedBuffer) Destroy()
Destroy wipes and frees the underlying memory of a LockedBuffer. The LockedBuffer will not be accessible or usable after this calls is made.
func (*LockedBuffer) EqualTo ¶
func (b *LockedBuffer) EqualTo(buf []byte) bool
EqualTo performs a time-constant comparison on the contents of a LockedBuffer with a given buffer. A destroyed LockedBuffer will always return false.
func (*LockedBuffer) Freeze ¶
func (b *LockedBuffer) Freeze()
Freeze makes a LockedBuffer's memory immutable. The call can be reversed with Melt.
func (*LockedBuffer) Int16 ¶
func (b *LockedBuffer) Int16() []int16
Int16 returns a slice pointing to the protected region of memory with the data represented as a sequence of signed 16 bit integers. Its length will be half that of the byte slice, excluding any remaining part that doesn't form a complete int16 value.
If called on a destroyed LockedBuffer, a nil slice will be returned.
func (*LockedBuffer) Int32 ¶
func (b *LockedBuffer) Int32() []int32
Int32 returns a slice pointing to the protected region of memory with the data represented as a sequence of signed 32 bit integers. Its length will be one quarter that of the byte slice, excluding any remaining part that doesn't form a complete int32 value.
If called on a destroyed LockedBuffer, a nil slice will be returned.
func (*LockedBuffer) Int64 ¶
func (b *LockedBuffer) Int64() []int64
Int64 returns a slice pointing to the protected region of memory with the data represented as a sequence of signed 64 bit integers. Its length will be one eighth that of the byte slice, excluding any remaining part that doesn't form a complete int64 value.
If called on a destroyed LockedBuffer, a nil slice will be returned.
func (*LockedBuffer) Int8 ¶
func (b *LockedBuffer) Int8() []int8
Int8 returns a slice pointing to the protected region of memory with the data represented as a sequence of signed 8 bit integers. If called on a destroyed LockedBuffer, a nil slice will be returned.
func (*LockedBuffer) IsAlive ¶
func (b *LockedBuffer) IsAlive() bool
IsAlive returns a boolean value indicating if a LockedBuffer is alive, i.e. that it has not been destroyed.
func (*LockedBuffer) IsMutable ¶
func (b *LockedBuffer) IsMutable() bool
IsMutable returns a boolean value indicating if a LockedBuffer is mutable.
func (*LockedBuffer) Melt ¶
func (b *LockedBuffer) Melt()
Melt makes a LockedBuffer's memory mutable. The call can be reversed with Freeze.
func (*LockedBuffer) Move ¶
func (b *LockedBuffer) Move(src []byte)
Move performs a time-constant move into a LockedBuffer. The source is wiped after the bytes are copied.
func (*LockedBuffer) MoveAt ¶
func (b *LockedBuffer) MoveAt(offset int, src []byte)
MoveAt performs a time-constant move into a LockedBuffer at an offset. The source is wiped after the bytes are copied.
func (*LockedBuffer) Reader ¶
func (b *LockedBuffer) Reader() *bytes.Reader
Reader returns a Reader object referencing the protected region of memory.
func (*LockedBuffer) Scramble ¶
func (b *LockedBuffer) Scramble()
Scramble attempts to overwrite the data with cryptographically-secure random bytes.
func (*LockedBuffer) Seal ¶
func (b *LockedBuffer) Seal() *Enclave
Seal takes a LockedBuffer object and returns its contents encrypted inside a sealed Enclave object. The LockedBuffer is subsequently destroyed and its contents wiped.
If Seal is called on a destroyed buffer, a nil enclave is returned.
func (*LockedBuffer) Size ¶
func (b *LockedBuffer) Size() int
Size gives you the length of a given LockedBuffer's data segment. A destroyed LockedBuffer will have a size of zero.
func (*LockedBuffer) String ¶
func (b *LockedBuffer) String() string
String returns a string representation of the protected region of memory.
func (*LockedBuffer) Uint16 ¶
func (b *LockedBuffer) Uint16() []uint16
Uint16 returns a slice pointing to the protected region of memory with the data represented as a sequence of unsigned 16 bit integers. Its length will be half that of the byte slice, excluding any remaining part that doesn't form a complete uint16 value.
If called on a destroyed LockedBuffer, a nil slice will be returned.
func (*LockedBuffer) Uint32 ¶
func (b *LockedBuffer) Uint32() []uint32
Uint32 returns a slice pointing to the protected region of memory with the data represented as a sequence of unsigned 32 bit integers. Its length will be one quarter that of the byte slice, excluding any remaining part that doesn't form a complete uint32 value.
If called on a destroyed LockedBuffer, a nil slice will be returned.
func (*LockedBuffer) Uint64 ¶
func (b *LockedBuffer) Uint64() []uint64
Uint64 returns a slice pointing to the protected region of memory with the data represented as a sequence of unsigned 64 bit integers. Its length will be one eighth that of the byte slice, excluding any remaining part that doesn't form a complete uint64 value.
If called on a destroyed LockedBuffer, a nil slice will be returned.
func (*LockedBuffer) Wipe ¶
func (b *LockedBuffer) Wipe()
Wipe attempts to overwrite the data with zeros.
type Stream ¶
Stream is an in-memory encrypted container implementing the reader and writer interfaces.
It is most useful when you need to store lots of data in memory and are able to work on it in chunks.
func (*Stream) Flush ¶
func (s *Stream) Flush() (*LockedBuffer, error)
Flush reads all of the data from a Stream and returns it inside a LockedBuffer. If an error is encountered before all the data could be read, it is returned along with any data read up until that point.
func (*Stream) Next ¶
func (s *Stream) Next() (*LockedBuffer, error)
Next grabs the next chunk of data from the Stream and returns it decrypted inside a LockedBuffer. Any error from the stream is forwarded.
func (*Stream) Read ¶
Read decrypts and places some data from a Stream object into a provided buffer.
If there is no data, the call will return an io.EOF error. If the caller provides a buffer that is too small to hold the next chunk of data, the remaining bytes are re-encrypted and added to the front of the queue to be returned in the next call.