diff --git a/spec/crypto_secret_spec.cr b/spec/crypto_secret_spec.cr index 7a58ecf..310702f 100644 --- a/spec/crypto_secret_spec.cr +++ b/spec/crypto_secret_spec.cr @@ -1,10 +1,17 @@ require "./spec_helper" require "../src/crypto-secret/test" -require "../src/crypto-secret/not" -require "../src/crypto-secret/large" -require "../src/crypto-secret/key" +require "../src/crypto-secret" +#require "../src/crypto-secret/not" +#require "../src/crypto-secret/bidet" test_secret_class Crypto::Secret::Not test_secret_class Crypto::Secret::Bidet -test_secret_class Crypto::Secret::Large -test_secret_class Crypto::Secret::Key + +describe Crypto::Secret do + it ".for" do + [:kgk, :key, :data, :not].each do |sym| + secret = Crypto::Secret.for sym, 2 + secret.bytesize.should eq 2 + end + end +end diff --git a/src/crypto-secret/bidet.cr b/src/crypto-secret/bidet.cr index 92972fe..cd31558 100644 --- a/src/crypto-secret/bidet.cr +++ b/src/crypto-secret/bidet.cr @@ -3,6 +3,7 @@ require "./stateless" module Crypto::Secret # Leaves less sh** around if you forget to wipe. A safer default for large secrets that may stress mlock limits or low confidentiality secrets. # + # * Wipes on finalize but should not be relied on # * Not locked in memory # * Not access protected # * No guard pages diff --git a/src/crypto-secret/config.cr b/src/crypto-secret/config.cr new file mode 100644 index 0000000..b06893e --- /dev/null +++ b/src/crypto-secret/config.cr @@ -0,0 +1,42 @@ +{% if @type.has_constant?("Sodium") %} + CRYPTO_SECRET_KEY_CLASS = Sodium::SecureBuffer +{% else %} + CRYPTO_SECRET_KEY_CLASS = Crypto::Secret::Bidet +{% end %} + +module Crypto::Secret::Config + # :nodoc: + USES = Hash(Symbol, Secret.class).new + + enum SecurityLevel + Paranoid + Default + Lax + None + end + + def self.setup(how : SecurityLevel = SecurityLevel::Default) : Nil + case how + in SecurityLevel::Paranoid + register_use Bidet, :not + register_use CRYPTO_SECRET_KEY_CLASS, :kgk, :key, :data + in SecurityLevel::Default + register_use Not, :not + register_use Bidet, :data + register_use CRYPTO_SECRET_KEY_CLASS, :kgk, :key + in SecurityLevel::Lax + register_use Not, :not + register_use Bidet, :kgk, :key, :data + in SecurityLevel::None + register_use Not, :kgk, :key, :data, :not + end + end + + setup # Safe defaults + + def self.register_use(klass, *uses) : Nil + uses.each do |use| + USES[use] = klass + end + end +end diff --git a/src/crypto-secret/key.cr b/src/crypto-secret/key.cr deleted file mode 100644 index 59475ba..0000000 --- a/src/crypto-secret/key.cr +++ /dev/null @@ -1,16 +0,0 @@ -require "./bidet" - -# Use this class for holding small amounts of sensitive data such as crypto keys -# -# Underlying implentation subject to change -# -# Uses `Sodium::SecureBuffer` If "sodium" is required before "crypto-secret" -{% if @type.has_constant?("Sodium") %} - class Crypto::Secret::Key < ::Sodium::SecureBuffer - end -{% else %} - # TODO: mlock - # TODO: mprotect - class Crypto::Secret::Key < ::Crypto::Secret::Bidet - end -{% end %} diff --git a/src/crypto-secret/large.cr b/src/crypto-secret/large.cr deleted file mode 100644 index f92144d..0000000 --- a/src/crypto-secret/large.cr +++ /dev/null @@ -1,13 +0,0 @@ -require "./bidet" - -module Crypto::Secret - # Use this class as a default when holding possibly large amounts of data that may stress mlock limits - # - # Suitable uses: holding decrypted data - # - # no mlock - # - # Implementation subject to change - class Large < Bidet - end -end diff --git a/src/crypto-secret/secret.cr b/src/crypto-secret/secret.cr index d11e975..af5562c 100644 --- a/src/crypto-secret/secret.cr +++ b/src/crypto-secret/secret.cr @@ -33,39 +33,22 @@ module Crypto::Secret extend ClassMethods - abstract class Base + def self.new(size : Int32) + raise NotImplementedError.new("workaround for lack of `abstract def self.new`") end - REGISTERED = Hash(Symbol, Secret::Base.class).new -# REGISTERED_USES = {} of Nil => Nil - REGISTERED_USES = {} of Symbol => String -# REGISTERED_LOAD_PATHS = {} of Nil => Nil - REGISTERED_LOAD_PATHS = {} of String => String - - macro register(klass, *args) -p "{{ args.id}}" - {% for arg in args %} - {% REGISTERED_USES[arg] = klass %} - {% end %} + def self.for(secret : Crypto::Secret) : Crypto::Secret + secret end - macro register_class(klass, load_path, *uses) - {% REGISTERED_LOAD_PATHS[klass] = load_path %} - register *uses - end - - register_class "Crypto::Secret::Bidet", "./bidet", :ksk, :key, :data -# register_class "Crypto::Secret::Not", "./not" - register "Crypto::Secret::Bidet", :ksk, :key, :data - - def self.for(use : Symbol) : Crypto::Secret::Base.class - REGISTERED[use] - end - - def self.for(use : Symbol, size : Int32) + def self.for(use : Symbol, size : Int32) : Crypto::Secret for(use).new(size) end + def self.for(use : Symbol) : Crypto::Secret.class + Config::USES[use] + end + # For debugging. Leaks the secret # # Returned String **not** tracked or wiped @@ -207,3 +190,4 @@ puts "key=klass" {% end %} end +require "./config" diff --git a/src/crypto-secret/stateful.cr b/src/crypto-secret/stateful.cr index 2795fb6..0c31fb4 100644 --- a/src/crypto-secret/stateful.cr +++ b/src/crypto-secret/stateful.cr @@ -31,7 +31,7 @@ module Crypto::Secret # Marks a region allocated as readable and writable # WARNING: Not thread safe - def readwrite : self + def readwrite : Secret raise Error::KeyWiped.new if @state == State::Wiped readwrite_impl @state = State::Readwrite @@ -50,7 +50,7 @@ module Crypto::Secret # Marks a region allocated using sodium_malloc() or sodium_allocarray() as read-only. # WARNING: Not thread safe - def readonly : self + def readonly : Secret raise Error::KeyWiped.new if @state == State::Wiped readonly_impl @state = State::Readonly @@ -59,7 +59,7 @@ module Crypto::Secret # Makes a region inaccessible. It cannot be read or written, but the data are preserved. # WARNING: Not thread safe - def noaccess : self + def noaccess : Secret raise Error::KeyWiped.new if @state == State::Wiped noaccess_impl @state = State::Noaccess diff --git a/src/crypto-secret/stateless.cr b/src/crypto-secret/stateless.cr index a7054ba..3879d96 100644 --- a/src/crypto-secret/stateless.cr +++ b/src/crypto-secret/stateless.cr @@ -2,7 +2,7 @@ require "./secret" # Provides a 0 overhead implementation of [#readwrite, #readonly, #noaccess, #reset] with no protection # -# Data is still erased when out of scope +# Data is erased when #wipe is out of scope, manual #wipe, or finalized except for `Not` module Crypto::Secret::Stateless include Crypto::Secret @@ -11,7 +11,7 @@ module Crypto::Secret::Stateless end # Not thread safe - def readwrite : self + def readwrite : Secret self end @@ -27,7 +27,7 @@ module Crypto::Secret::Stateless end # Not thread safe - def readonly : self + def readonly : Secret self end @@ -42,12 +42,13 @@ module Crypto::Secret::Stateless end # Not thread safe - def noaccess : self + def noaccess : Secret self end # Not thread safe - def reset + def reset : Secret + self end def finalize