2019-07-04 02:56:02 +02:00
require " ./lib_sodium "
require " ./wipe "
module Sodium
2019-08-06 23:27:19 +02:00
# Allocate guarded memory using [sodium_malloc](https://libsodium.gitbook.io/doc/memory_management)
2019-07-04 02:56:02 +02:00
class SecureBuffer
2019-09-01 11:51:48 +02:00
class Error < Sodium :: Error
class KeyWiped < Error
end
class InvalidStateTransition < Error
end
end
enum State
Wiped
2019-09-01 19:31:36 +02:00
Noaccess
Readonly
Readwrite
2019-09-01 11:51:48 +02:00
end
@state = State :: Readwrite
2019-07-04 02:56:02 +02:00
getter bytesize
2019-07-27 09:35:27 +02:00
delegate :+ , :[] , :[]= , to : to_slice
2019-07-08 22:24:25 +02:00
2019-07-04 02:56:02 +02:00
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
2019-07-27 09:35:27 +02:00
# :nodoc:
# For .dup
def initialize ( sbuf : self )
initialize sbuf . bytesize
sbuf . to_slice . copy_to self . to_slice
readonly
end
2019-07-04 02:56:02 +02:00
def wipe
2019-09-01 11:51:48 +02:00
return if @state == State :: Wiped
2019-07-04 02:56:02 +02:00
readwrite
Sodium . memzero self . to_slice
2019-09-01 11:51:48 +02:00
@state = State :: Wiped
noaccess!
2019-07-04 02:56:02 +02:00
end
def finalize
LibSodium . sodium_free @ptr
end
2019-08-07 01:45:20 +02:00
# Returns key
2019-09-01 11:51:48 +02:00
# May permanently set key to readonly depending on class usage.
2019-07-04 02:56:02 +02:00
def to_slice
2019-09-14 06:33:21 +02:00
case @state
when State :: Noaccess , State :: Wiped
readonly
else
# Ok
end
2019-07-04 02:56:02 +02:00
Slice ( UInt8 ) . new @ptr , @bytesize
end
def to_unsafe
@ptr
end
2019-07-27 09:35:27 +02:00
def dup
self . class . new self
end
2019-09-01 11:51:48 +02:00
# 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
2019-07-04 02:56:02 +02:00
# Makes a region allocated using sodium_malloc() or sodium_allocarray() inaccessible. It cannot be read or written, but the data are preserved.
def noaccess
2019-09-01 11:51:48 +02:00
raise Error :: KeyWiped . new if @state == State :: Wiped
noaccess!
@state = State :: Noaccess
self
end
# Also used by #wipe
private def noaccess!
2019-07-04 02:56:02 +02:00
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
2019-09-01 11:51:48 +02:00
raise Error :: KeyWiped . new if @state == State :: Wiped
2019-07-04 02:56:02 +02:00
if LibSodium . sodium_mprotect_readonly ( @ptr ) != 0
raise " sodium_mprotect_readonly "
end
2019-09-01 11:51:48 +02:00
@state = State :: Readonly
2019-07-04 02:56:02 +02:00
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
2019-09-01 11:51:48 +02:00
raise Error :: KeyWiped . new if @state == State :: Wiped
2019-07-04 02:56:02 +02:00
if LibSodium . sodium_mprotect_readwrite ( @ptr ) != 0
raise " sodium_mprotect_readwrite "
end
2019-09-01 11:51:48 +02:00
@state = State :: Readwrite
2019-07-04 02:56:02 +02:00
self
end
2019-08-24 23:15:37 +02:00
# Timing safe memory compare.
2019-07-04 02:56:02 +02:00
def == ( other : self )
2019-08-06 03:49:17 +02:00
Sodium . memcmp self . to_slice , other . to_slice
2019-07-04 02:56:02 +02:00
end
2019-08-24 23:15:37 +02:00
# Timing safe memory compare.
2019-07-04 02:56:02 +02:00
def == ( other : Bytes )
2019-08-06 03:49:17 +02:00
Sodium . memcmp self . to_slice , other
2019-07-04 02:56:02 +02:00
end
2019-09-01 11:51:48 +02:00
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 )
2019-09-14 06:33:21 +02:00
old_state = @state
2019-09-01 19:31:36 +02:00
# Only change when new_state needs more access than @state.
2019-09-14 06:33:21 +02:00
if old_state >= new_state
2019-09-01 19:31:36 +02:00
yield
else
begin
set_state new_state
yield
ensure
2019-09-14 06:33:21 +02:00
set_state old_state
2019-09-01 19:31:36 +02:00
end
end
2019-09-01 11:51:48 +02:00
end
2019-07-04 02:56:02 +02:00
end
end