180 lines
4.2 KiB
Crystal
180 lines
4.2 KiB
Crystal
require "./lib_sodium"
|
|
require "./wipe"
|
|
|
|
module Sodium
|
|
# Allocate guarded memory using [sodium_malloc](https://libsodium.gitbook.io/doc/memory_management)
|
|
class SecureBuffer
|
|
class Error < Sodium::Error
|
|
class KeyWiped < Error
|
|
end
|
|
|
|
class InvalidStateTransition < Error
|
|
end
|
|
end
|
|
|
|
enum State
|
|
Wiped
|
|
Noaccess
|
|
Readonly
|
|
Readwrite
|
|
end
|
|
|
|
@state = State::Readwrite
|
|
|
|
getter bytesize
|
|
|
|
delegate :+, :[], :[]=, to: to_slice
|
|
|
|
def initialize(@bytesize : Int32)
|
|
@ptr = LibSodium.sodium_malloc @bytesize
|
|
end
|
|
|
|
# Returns a **readonly** random SecureBuffer.
|
|
def self.random(size)
|
|
buf = new(size)
|
|
Random::Secure.random_bytes buf.to_slice
|
|
buf.readonly
|
|
end
|
|
|
|
# Copies bytes to a **readonly** SecureBuffer.
|
|
# Optionally erases bytes after copying if erase is set
|
|
def initialize(bytes : Bytes, erase = false)
|
|
initialize bytes.bytesize
|
|
bytes.copy_to self.to_slice
|
|
Sodium.memzero(bytes) if erase
|
|
readonly
|
|
end
|
|
|
|
# :nodoc:
|
|
# For .dup
|
|
def initialize(sbuf : self)
|
|
initialize sbuf.bytesize
|
|
# Maybe not thread safe
|
|
sbuf.readonly do
|
|
sbuf.to_slice.copy_to self.to_slice
|
|
end
|
|
readonly
|
|
end
|
|
|
|
def wipe
|
|
return if @state == State::Wiped
|
|
readwrite
|
|
Sodium.memzero self.to_slice
|
|
@state = State::Wiped
|
|
noaccess!
|
|
end
|
|
|
|
def finalize
|
|
LibSodium.sodium_free @ptr
|
|
end
|
|
|
|
# Returns key
|
|
# May permanently set key to readonly depending on class usage.
|
|
def to_slice : Bytes
|
|
case @state
|
|
when State::Noaccess, State::Wiped
|
|
readonly
|
|
else
|
|
# Ok
|
|
end
|
|
Slice(UInt8).new @ptr, @bytesize
|
|
end
|
|
|
|
def to_unsafe
|
|
@ptr
|
|
end
|
|
|
|
def dup
|
|
self.class.new self
|
|
end
|
|
|
|
# Temporarily make buffer readonly within the block returning to the prior state on exit.
|
|
def readonly
|
|
with_state State::Readonly do
|
|
yield
|
|
end
|
|
end
|
|
|
|
# Temporarily make buffer readonly within the block returning to the prior state on exit.
|
|
def readwrite
|
|
with_state State::Readwrite do
|
|
yield
|
|
end
|
|
end
|
|
|
|
# Makes a region allocated using sodium_malloc() or sodium_allocarray() inaccessible. It cannot be read or written, but the data are preserved.
|
|
def noaccess
|
|
raise Error::KeyWiped.new if @state == State::Wiped
|
|
noaccess!
|
|
@state = State::Noaccess
|
|
self
|
|
end
|
|
|
|
# Also used by #wipe
|
|
private def noaccess!
|
|
if LibSodium.sodium_mprotect_noaccess(@ptr) != 0
|
|
raise "sodium_mprotect_noaccess"
|
|
end
|
|
self
|
|
end
|
|
|
|
# Marks a region allocated using sodium_malloc() or sodium_allocarray() as read-only.
|
|
def readonly
|
|
raise Error::KeyWiped.new if @state == State::Wiped
|
|
if LibSodium.sodium_mprotect_readonly(@ptr) != 0
|
|
raise "sodium_mprotect_readonly"
|
|
end
|
|
@state = State::Readonly
|
|
self
|
|
end
|
|
|
|
# Marks a region allocated using sodium_malloc() or sodium_allocarray() as readable and writable, after having been protected using sodium_mprotect_readonly() or sodium_mprotect_noaccess().
|
|
def readwrite
|
|
raise Error::KeyWiped.new if @state == State::Wiped
|
|
if LibSodium.sodium_mprotect_readwrite(@ptr) != 0
|
|
raise "sodium_mprotect_readwrite"
|
|
end
|
|
@state = State::Readwrite
|
|
self
|
|
end
|
|
|
|
# Timing safe memory compare.
|
|
def ==(other : self)
|
|
Sodium.memcmp self.to_slice, other.to_slice
|
|
end
|
|
|
|
# Timing safe memory compare.
|
|
def ==(other : Bytes)
|
|
Sodium.memcmp self.to_slice, other
|
|
end
|
|
|
|
private def set_state(new_state : State)
|
|
return if @state == new_state
|
|
|
|
case new_state
|
|
when State::Readwrite; readwrite
|
|
when State::Readonly ; readonly
|
|
when State::Noaccess ; noaccess
|
|
when State::Wiped ; raise Error::InvalidStateTransition.new
|
|
else
|
|
raise "unknown state #{new_state}"
|
|
end
|
|
end
|
|
|
|
private def with_state(new_state : State)
|
|
old_state = @state
|
|
# Only change when new_state needs more access than @state.
|
|
if old_state >= new_state
|
|
yield
|
|
else
|
|
begin
|
|
set_state new_state
|
|
yield
|
|
ensure
|
|
set_state old_state
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|