Documentation
¶
Overview ¶
Package memguard is designed to allow you to easily handle sensitive values in memory.
The general working cycle is easy to follow:
// Declare a protected slice and move bytes into it. encryptionKey := memguard.New(32) encryptionKey.Move(generateRandomBytes(32)) // Use the buffer wherever you need it. Encrypt(encryptionKey.Buffer, plaintext) // Destroy it after you're done. encryptionKey.Destroy()
As you'll have noted, the example above does not append or assign the key to the buffer, but rather it uses the built-in API function `Move()`.
b := memguard.New(32)
b.Move([]byte("...")) // Correct.
b.Copy([]byte("...")) // Less correct; original buffer isn't wiped.
b.Buffer = []byte("...") // WRONG
b.Buffer = append(b.Buffer, []byte("...")) // WRONG
If a function that you're using requires an array, you can cast the Buffer to an array and then pass around a pointer. Make sure that you do not dereference the pointer and pass around the resulting value, as this will leave copies all over the place.
key := memguard.NewFromBytes([]byte("yellow submarine"))
// Make sure that the size of the array matches the size of the Buffer.
keyArrayPtr := (*[16]byte)(unsafe.Pointer(&key.Buffer[0]))
The MemGuard API is thread-safe. You can extend this thread-safety to outside of the API functions by using the Mutex that each LockedBuffer exposes. Do not use the mutex when calling a function that is part of the MemGuard API. For example:
b := New(4)
b.Lock()
copy(b.Buffer, []byte("test"))
b.Unlock()
c := New(4)
c.Lock()
c.Copy([]byte("test")) // This will deadlock.
c.Unlock()
When terminating your application, care should be taken to securely cleanup everything.
// Start a listener that will wait for interrupt signals and catch them.
memguard.CatchInterrupt(func() {
// Over here put anything you want executing before program exit.
fmt.Println("Interrupt signal received. Exiting...")
})
// Defer a DestroyAll() in your main() function.
defer memguard.DestroyAll()
// Use memguard.SafeExit() instead of os.Exit().
memguard.SafeExit(0) // 0 is the status code.
Index ¶
- Variables
- func CatchInterrupt(f func())
- func DestroyAll()
- func DisableUnixCoreDumps()
- func Equal(a, b *LockedBuffer) (bool, error)
- func SafeExit(c int)
- func Split(b *LockedBuffer, offset int) (*LockedBuffer, *LockedBuffer, error)
- func WipeBytes(buf []byte)
- type LockedBuffer
- func Duplicate(b *LockedBuffer) (*LockedBuffer, error)
- func LockedBuffers() []*LockedBuffer
- func New(length int, readOnly bool) (*LockedBuffer, error)
- func NewFromBytes(buf []byte, readOnly bool) (*LockedBuffer, error)
- func NewRandom(length int, readOnly bool) (*LockedBuffer, error)
- func Trim(b *LockedBuffer, offset, size int) (*LockedBuffer, error)
- func (b *LockedBuffer) Copy(buf []byte) error
- func (b *LockedBuffer) CopyAt(buf []byte, offset int) error
- func (b *LockedBuffer) Destroy()
- func (b *LockedBuffer) EqualTo(buf []byte) (bool, error)
- func (b *LockedBuffer) MarkAsReadOnly() error
- func (b *LockedBuffer) MarkAsReadWrite() error
- func (b *LockedBuffer) Move(buf []byte) error
- func (b *LockedBuffer) MoveAt(buf []byte, offset int) error
Constants ¶
This section is empty.
Variables ¶
var ErrDestroyed = errors.New("memguard.ErrDestroyed: buffer is destroyed")
ErrDestroyed is returned when a function is called on a destroyed LockedBuffer.
var ErrInvalidLength = errors.New("memguard.ErrInvalidLength: length of buffer must be greater than zero")
ErrInvalidLength is returned when a LockedBuffer of smaller than one byte is requested.
var ErrReadOnly = errors.New("memguard.ErrReadOnly: buffer is marked read-only")
ErrReadOnly is returned when a function that needs to modify a LockedBuffer is given a LockedBuffer that is marked as being read-only.
Functions ¶
func CatchInterrupt ¶
func CatchInterrupt(f func())
CatchInterrupt starts a goroutine that monitors for interrupt signals. It accepts a function of type func() and executes that before calling SafeExit(0).
memguard.CatchInterrupt(func() {
fmt.Println("Interrupt signal received. Exiting...")
})
If CatchInterrupt is called multiple times, only the first call is executed and all subsequent calls are ignored.
func DestroyAll ¶
func DestroyAll()
DestroyAll calls Destroy on all LockedBuffers that have not already been destroyed.
CatchInterrupt and SafeExit both call DestroyAll before exiting.
func DisableUnixCoreDumps ¶ added in v0.5.0
func DisableUnixCoreDumps()
DisableUnixCoreDumps disables core-dumps on Unix systems. Since core-dumps are only relevant on Unix systems, if DisableUnixCoreDumps is called on any other system it will do nothing and return immediately.
func Equal ¶ added in v0.4.0
func Equal(a, b *LockedBuffer) (bool, error)
Equal compares the contents of two LockedBuffers in constant time.
func SafeExit ¶
func SafeExit(c int)
SafeExit exits the program with a specified exit-code, but calls DestroyAll first.
func Split ¶ added in v0.4.0
func Split(b *LockedBuffer, offset int) (*LockedBuffer, *LockedBuffer, error)
Split takes a LockedBuffer, splits it at a specified offset, and then returns the two newly created LockedBuffers. The permissions of the original are preserved and the original LockedBuffer is not destroyed.
Types ¶
type LockedBuffer ¶
LockedBuffer is a structure that holds secure values. It exposes a Mutex, various metadata flags, and a slice that maps to the protected memory.
The entire memguard API handles and passes around pointers to LockedBuffers, and so, for both security and convenience, you should refrain from dereferencing a LockedBuffer.
if LockedBuffer.ReadOnly == true {
// Editing this buffer will crash the process.
}
if LockedBuffer.Destroyed == true {
// This buffer has been cleaned up and destroyed.
}
If an API function that needs to edit a LockedBuffer is given one marked as read-only, the call will return an ErrReadOnly. Similarly, if a function is given a LockedBuffer that has been destroyed, the call will return an ErrDestroyed.
For obvious reasons, you should never edit these metadata values yourself. Doing so will result in undefined behaviour.
func Duplicate ¶ added in v0.4.0
func Duplicate(b *LockedBuffer) (*LockedBuffer, error)
Duplicate takes a LockedBuffer and creates a new one with the same contents and permissions as the original.
func LockedBuffers ¶ added in v0.5.0
func LockedBuffers() []*LockedBuffer
LockedBuffers returns a slice containing a pointer to each LockedBuffer that has not been destroyed.
func New ¶
func New(length int, readOnly bool) (*LockedBuffer, error)
New creates a new LockedBuffer of a specified length and permissions.
If the given length is less than one, the call will return an ErrInvalidLength.
func NewFromBytes ¶
func NewFromBytes(buf []byte, readOnly bool) (*LockedBuffer, error)
NewFromBytes creates a new LockedBuffer containing the contents of a given slice. The slice is wiped after the values have been copied over.
If the size of the slice is zero, the call will return an ErrInvalidLength.
func NewRandom ¶ added in v0.5.0
func NewRandom(length int, readOnly bool) (*LockedBuffer, error)
NewRandom creates a new LockedBuffer of a given length.
The distinction is that while a LockedBuffer created with New would be filled with zeroes, NewRandom creates LockedBuffers full of cryptographically-secure pseudo-random bytes instead. Therefore, a LockedBuffer attained by a call to NewRandom can be safely used as an encryption key.
func Trim ¶ added in v0.4.0
func Trim(b *LockedBuffer, offset, size int) (*LockedBuffer, error)
Trim shortens a LockedBuffer according to the given specifications. The permissions of the original are preserved and the original LockedBuffer is not destroyed.
Trim takes an offset and a size as arguments. The resulting LockedBuffer starts at index [offset] and ends at index [offset+size].
func (*LockedBuffer) Copy ¶
func (b *LockedBuffer) Copy(buf []byte) error
Copy copies bytes from a byte slice into a LockedBuffer in constant-time. Just like Golang's built-in copy function, Copy only copies up to the smallest of the two buffers.
It does not wipe the original slice so using Copy is less secure than using Move. Therefore Move should be favoured unless you have a good reason.
You should aim to call WipeBytes on the original slice as soon as possible.
If the LockedBuffer is marked as read-only, the call will fail and return an ErrReadOnly.
func (*LockedBuffer) CopyAt ¶ added in v0.4.0
func (b *LockedBuffer) CopyAt(buf []byte, offset int) error
CopyAt is identical to Copy but it copies into the LockedBuffer at a specified offset.
func (*LockedBuffer) Destroy ¶
func (b *LockedBuffer) Destroy()
Destroy verifies that no buffer underflows occurred and then wipes, unlocks, and frees all related memory. If a buffer underflow is detected, the process panics.
This function must be called on all LockedBuffers before exiting. DestroyAll is designed for this purpose, as is CatchInterrupt and SafeExit. We recommend using all of them together.
If the LockedBuffer has already been destroyed then the call makes no changes.
func (*LockedBuffer) EqualTo ¶ added in v0.4.0
func (b *LockedBuffer) EqualTo(buf []byte) (bool, error)
EqualTo compares a LockedBuffer to a byte slice in constant time.
func (*LockedBuffer) MarkAsReadOnly ¶ added in v0.4.0
func (b *LockedBuffer) MarkAsReadOnly() error
MarkAsReadOnly asks the kernel to mark the LockedBuffer's memory as read-only. Any subsequent attempts to write to this memory will result in the process crashing with a SIGSEGV memory violation.
Calling MarkAsReadOnly on a LockedBuffer that is already read-only will result in no changes being made.
To make the memory writable again, MarkAsReadWrite is called.
func (*LockedBuffer) MarkAsReadWrite ¶ added in v0.4.0
func (b *LockedBuffer) MarkAsReadWrite() error
MarkAsReadWrite asks the kernel to mark the LockedBuffer's memory as readable and writable.
Calling MarkAsReadWrite on a LockedBuffer that is already readable and writable will result in no changes being made.
This method is the counterpart of MarkAsReadOnly.
func (*LockedBuffer) Move ¶
func (b *LockedBuffer) Move(buf []byte) error
Move moves bytes from a byte slice into a LockedBuffer in constant-time. Just like Golang's built-in copy function, Move only moves up to the smallest of the two buffers.
Unlike Copy, Move wipes the entire original slice after copying the appropriate number of bytes over, and so it should be favoured unless you have a good reason.
If the LockedBuffer is marked as read-only, the call will fail and return an ErrReadOnly.