diff --git a/src/crypto-secret/class_methods.cr b/src/crypto-secret/class_methods.cr index 4eb723e..c168546 100644 --- a/src/crypto-secret/class_methods.cr +++ b/src/crypto-secret/class_methods.cr @@ -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 diff --git a/src/crypto-secret/lib.cr b/src/crypto-secret/lib.cr index 2d1de71..491cbe2 100644 --- a/src/crypto-secret/lib.cr +++ b/src/crypto-secret/lib.cr @@ -15,7 +15,7 @@ lib LibC end struct Slice(T) - def wipe + def wipe : Nil LibC.explicit_bzero to_unsafe, bytesize end end diff --git a/src/crypto-secret/secret.cr b/src/crypto-secret/secret.cr index 0b2c30c..08f0265 100644 --- a/src/crypto-secret/secret.cr +++ b/src/crypto-secret/secret.cr @@ -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 diff --git a/src/crypto-secret/stateful.cr b/src/crypto-secret/stateful.cr index ecdae6a..98535a3 100644 --- a/src/crypto-secret/stateful.cr +++ b/src/crypto-secret/stateful.cr @@ -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 diff --git a/src/crypto-secret/test.cr b/src/crypto-secret/test.cr index c2addbd..3a65c07 100644 --- a/src/crypto-secret/test.cr +++ b/src/crypto-secret/test.cr @@ -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