More documentation

Implement #reset & #random with specs
This commit is contained in:
Didactic Drunk 2021-06-17 10:19:38 -07:00
parent 363800bf1a
commit 8977222b44
5 changed files with 57 additions and 14 deletions

View File

@ -1,5 +1,6 @@
module Crypto::Secret::ClassMethods
# Copies `data` to the new Secret and **erases data**
#
# Returns a **readonly** Secret
def move_from(data : Bytes)
copy_from data
@ -8,6 +9,7 @@ module Crypto::Secret::ClassMethods
end
# Copies `data` to the new Secret
#
# Returns a **readonly** Secret
def copy_from(data : Bytes)
new(data.bytesize).tap do |obj|
@ -20,9 +22,6 @@ module Crypto::Secret::ClassMethods
# Returns a **readonly** random Secret
def random(size)
buf = new(size)
buf.readwrite do |slice|
Random::Secure.random_bytes slice
end
buf.readonly
buf.random.readonly
end
end

View File

@ -15,7 +15,7 @@ lib LibC
end
struct Slice(T)
def wipe
def wipe : Nil
LibC.explicit_bzero to_unsafe, bytesize
end
end

View File

@ -35,12 +35,24 @@ module Crypto::Secret
Readwrite
end
extend ClassMethods
# For debugging.
# Returned String **not** tracked or wiped
def hexstring : String
readonly &.hexstring
end
def random : self
readwrite do |slice|
Random::Secure.random_bytes slice
end
self
end
# Zeroes data
#
# Secret is unavailable (readonly/readwrite may fail) until reset
def wipe
readwrite do |slice|
wipe_impl slice
@ -55,6 +67,7 @@ module Crypto::Secret
end
def reset
wipe
end
def finalize
@ -93,9 +106,13 @@ module Crypto::Secret
end
end
abstract def readwrite
abstract def readonly
abstract def noaccess
# Marks a region allocated using as read & write depending on implementation.
abstract def readwrite : self
# Marks a region allocated using as read-only depending on implementation.
abstract def readonly : self
# Makes a region allocated inaccessible depending on implementation. It cannot be read or written, but the data are preserved.
abstract def noaccess : self
protected abstract def to_slice(& : Bytes -> Nil)
abstract def bytesize : Int32

View File

@ -17,6 +17,7 @@ module Crypto::Secret
end
@state = State::Readwrite
@pre_wipe_state = State::Readwrite
# Temporarily make buffer readwrite within the block returning to the prior state on exit.
# WARNING: Not thread safe unless this object is **readwrite**
@ -65,6 +66,14 @@ module Crypto::Secret
self
end
def reset
case @state
when State::Wiped; set_state @pre_wipe_state
else
wipe_impl
end
end
# WARNING: Not thread safe
# Kept public for .dup
# :nodoc:
@ -72,12 +81,10 @@ module Crypto::Secret
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}"
in State::Readwrite; readwrite
in State::Readonly ; readonly
in State::Noaccess ; noaccess
in State::Wiped ; raise Error::KeyWiped.new
end
end
@ -100,6 +107,7 @@ module Crypto::Secret
# WARNING: Not thread safe
def wipe
return if @state == State::Wiped
@pre_wipe_state = @state
readwrite do |slice|
wipe_impl slice
end

View File

@ -76,5 +76,24 @@ macro test_secret_class(to sclass)
secret.inspect.should match /\(\*\*\*SECRET\*\*\*\)$/
end
if sclass.is_a?(Crypto::Secret::Stateful)
it "can't transition after #wipe except with #reset" do
secret = sclass.new 1
secret.wipe
expect_raises Crypto::Secret::Error::KeyWiped do
secret.readwrite
end
expect_raises Crypto::Secret::Error::KeyWiped do
secret.readonly
end
expect_raises Crypto::Secret::Error::KeyWiped do
secret.readnoaccess
end
secret.reset
secret.readonly
end
end
end
end