Sodium::SecureBuffer
Add State and transitions. New Exceptions. Raise instead of crashing when attempting to access buffer. Allow wiping more than once. Add specs.master
parent
428907318b
commit
8cdb4cbf42
|
@ -0,0 +1,84 @@
|
||||||
|
require "../spec_helper"
|
||||||
|
require "../../src/sodium/secure_buffer"
|
||||||
|
|
||||||
|
describe Sodium::SecureBuffer do
|
||||||
|
it "allocates empty" do
|
||||||
|
buf = Sodium::SecureBuffer.new 5
|
||||||
|
buf.to_slice.each do |b|
|
||||||
|
b.should eq 0xdb_u8
|
||||||
|
end
|
||||||
|
|
||||||
|
buf.noaccess
|
||||||
|
buf.readonly
|
||||||
|
buf.readwrite
|
||||||
|
end
|
||||||
|
|
||||||
|
it "allocates random" do
|
||||||
|
buf = Sodium::SecureBuffer.random 5
|
||||||
|
buf.to_slice.bytesize.should eq 5
|
||||||
|
buf.wipe
|
||||||
|
end
|
||||||
|
|
||||||
|
it "copies and erases" do
|
||||||
|
bytes = Bytes.new(5) { 1_u8 }
|
||||||
|
|
||||||
|
buf = Sodium::SecureBuffer.new bytes, erase: true
|
||||||
|
buf.to_slice.bytesize.should eq 5
|
||||||
|
buf.to_slice.each do |b|
|
||||||
|
b.should eq 1_u8
|
||||||
|
end
|
||||||
|
|
||||||
|
bytes.to_slice.each do |b|
|
||||||
|
b.should eq 0_u8
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dups without crashing" do
|
||||||
|
buf = Sodium::SecureBuffer.new 5
|
||||||
|
buf.readwrite
|
||||||
|
|
||||||
|
buf2 = buf.dup
|
||||||
|
buf2.readonly
|
||||||
|
|
||||||
|
buf[0] = 0_u8
|
||||||
|
end
|
||||||
|
|
||||||
|
it "transitions correctly" do
|
||||||
|
buf = Sodium::SecureBuffer.new 5
|
||||||
|
|
||||||
|
buf.noaccess
|
||||||
|
buf.@state.should eq Sodium::SecureBuffer::State::Noaccess
|
||||||
|
buf.readonly { }
|
||||||
|
buf.@state.should eq Sodium::SecureBuffer::State::Noaccess
|
||||||
|
|
||||||
|
buf.readonly
|
||||||
|
buf.@state.should eq Sodium::SecureBuffer::State::Readonly
|
||||||
|
buf.readwrite { }
|
||||||
|
buf.@state.should eq Sodium::SecureBuffer::State::Readonly
|
||||||
|
|
||||||
|
buf.readwrite
|
||||||
|
buf.@state.should eq Sodium::SecureBuffer::State::Readwrite
|
||||||
|
|
||||||
|
buf.wipe
|
||||||
|
buf.@state.should eq Sodium::SecureBuffer::State::Wiped
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can wipe more than once" do
|
||||||
|
buf = Sodium::SecureBuffer.new 5
|
||||||
|
3.times { buf.wipe }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can't transition from wiped" do
|
||||||
|
buf = Sodium::SecureBuffer.new 5
|
||||||
|
buf.wipe
|
||||||
|
expect_raises Sodium::SecureBuffer::Error::KeyWiped do
|
||||||
|
buf.readwrite
|
||||||
|
end
|
||||||
|
expect_raises Sodium::SecureBuffer::Error::KeyWiped do
|
||||||
|
buf.readonly
|
||||||
|
end
|
||||||
|
expect_raises Sodium::SecureBuffer::Error::KeyWiped do
|
||||||
|
buf.noaccess
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,6 +4,23 @@ require "./wipe"
|
||||||
module Sodium
|
module Sodium
|
||||||
# Allocate guarded memory using [sodium_malloc](https://libsodium.gitbook.io/doc/memory_management)
|
# Allocate guarded memory using [sodium_malloc](https://libsodium.gitbook.io/doc/memory_management)
|
||||||
class SecureBuffer
|
class SecureBuffer
|
||||||
|
class Error < Sodium::Error
|
||||||
|
class KeyWiped < Error
|
||||||
|
end
|
||||||
|
|
||||||
|
class InvalidStateTransition < Error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
enum State
|
||||||
|
Readwrite
|
||||||
|
Readonly
|
||||||
|
Noaccess
|
||||||
|
Wiped
|
||||||
|
end
|
||||||
|
|
||||||
|
@state = State::Readwrite
|
||||||
|
|
||||||
getter bytesize
|
getter bytesize
|
||||||
|
|
||||||
delegate :+, :[], :[]=, to: to_slice
|
delegate :+, :[], :[]=, to: to_slice
|
||||||
|
@ -37,8 +54,11 @@ module Sodium
|
||||||
end
|
end
|
||||||
|
|
||||||
def wipe
|
def wipe
|
||||||
|
return if @state == State::Wiped
|
||||||
readwrite
|
readwrite
|
||||||
Sodium.memzero self.to_slice
|
Sodium.memzero self.to_slice
|
||||||
|
@state = State::Wiped
|
||||||
|
noaccess!
|
||||||
end
|
end
|
||||||
|
|
||||||
def finalize
|
def finalize
|
||||||
|
@ -46,7 +66,9 @@ module Sodium
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns key
|
# Returns key
|
||||||
|
# May permanently set key to readonly depending on class usage.
|
||||||
def to_slice
|
def to_slice
|
||||||
|
readonly if @state == State::Noaccess
|
||||||
Slice(UInt8).new @ptr, @bytesize
|
Slice(UInt8).new @ptr, @bytesize
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -58,8 +80,30 @@ module Sodium
|
||||||
self.class.new self
|
self.class.new self
|
||||||
end
|
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.
|
# Makes a region allocated using sodium_malloc() or sodium_allocarray() inaccessible. It cannot be read or written, but the data are preserved.
|
||||||
def noaccess
|
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
|
if LibSodium.sodium_mprotect_noaccess(@ptr) != 0
|
||||||
raise "sodium_mprotect_noaccess"
|
raise "sodium_mprotect_noaccess"
|
||||||
end
|
end
|
||||||
|
@ -68,17 +112,21 @@ module Sodium
|
||||||
|
|
||||||
# Marks a region allocated using sodium_malloc() or sodium_allocarray() as read-only.
|
# Marks a region allocated using sodium_malloc() or sodium_allocarray() as read-only.
|
||||||
def readonly
|
def readonly
|
||||||
|
raise Error::KeyWiped.new if @state == State::Wiped
|
||||||
if LibSodium.sodium_mprotect_readonly(@ptr) != 0
|
if LibSodium.sodium_mprotect_readonly(@ptr) != 0
|
||||||
raise "sodium_mprotect_readonly"
|
raise "sodium_mprotect_readonly"
|
||||||
end
|
end
|
||||||
|
@state = State::Readonly
|
||||||
self
|
self
|
||||||
end
|
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().
|
# 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
|
def readwrite
|
||||||
|
raise Error::KeyWiped.new if @state == State::Wiped
|
||||||
if LibSodium.sodium_mprotect_readwrite(@ptr) != 0
|
if LibSodium.sodium_mprotect_readwrite(@ptr) != 0
|
||||||
raise "sodium_mprotect_readwrite"
|
raise "sodium_mprotect_readwrite"
|
||||||
end
|
end
|
||||||
|
@state = State::Readwrite
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -91,5 +139,26 @@ module Sodium
|
||||||
def ==(other : Bytes)
|
def ==(other : Bytes)
|
||||||
Sodium.memcmp self.to_slice, other
|
Sodium.memcmp self.to_slice, other
|
||||||
end
|
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
|
||||||
|
set_state new_state
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
set_state old_state if old_state
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue