diff --git a/README.md b/README.md index 578c1b7..267cb2a 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Updated Crystal bindings for the [libsodium API](https://libsodium.gitbook.io/do - [x] ChaCha20 - [ ] One time auth - [ ] Padding + - [?] Semi-automatic memory wiping. ☑ Indicate specs are compared against test vectors from another source. @@ -111,7 +112,7 @@ public_key.verify_detached message, signature ### Secret Key Encryption ```crystal -key = Cox::SecretKey.random +key = Cox::SecretKey.new message = "foobar" encrypted, nonce = key.encrypt_easy message diff --git a/spec/cox/secret_key_spec.cr b/spec/cox/secret_box_spec.cr similarity index 85% rename from spec/cox/secret_key_spec.cr rename to spec/cox/secret_box_spec.cr index 666dfb9..52203e6 100644 --- a/spec/cox/secret_key_spec.cr +++ b/spec/cox/secret_box_spec.cr @@ -1,8 +1,8 @@ require "../spec_helper" -describe Cox::SecretKey do +describe Cox::SecretBox do it "encrypts/decrypts" do - key = Cox::SecretKey.random + key = Cox::SecretBox.new message = "foobar" encrypted, nonce = key.encrypt_easy message diff --git a/src/cox.cr b/src/cox.cr index 5c85e89..42f8fec 100644 --- a/src/cox.cr +++ b/src/cox.cr @@ -8,37 +8,14 @@ module Cox class DecryptionFailed < Error end end + + def self.memzero(bytes : Bytes) + LibSodium.sodium_memzero bytes, bytes.bytesize + end end require "./cox/**" -module Cox - def self.encrypt(data, nonce : Nonce, recipient_public_key : CryptoBox::PublicKey, sender_secret_key : CryptoBox::SecretKey) - data_buffer = data.to_slice - data_size = data_buffer.bytesize - output_buffer = Bytes.new(data_buffer.bytesize + LibSodium::MAC_SIZE) - if LibSodium.crypto_box_easy(output_buffer.to_slice, data_buffer, data_size, nonce.to_slice, recipient_public_key.to_slice, sender_secret_key.to_slice) != 0 - raise Error.new("crypto_box_easy") - end - output_buffer - end - - def self.encrypt(data, recipient_public_key : CryptoBox::PublicKey, sender_secret_key : CryptoBox::SecretKey) - nonce = Nonce.new - {nonce, encrypt(data, nonce, recipient_public_key, sender_secret_key)} - end - - def self.decrypt(data, nonce : Nonce, sender_public_key : CryptoBox::PublicKey, recipient_secret_key : CryptoBox::SecretKey) - data_buffer = data.to_slice - data_size = data_buffer.bytesize - output_buffer = Bytes.new(data_buffer.bytesize - LibSodium::MAC_SIZE) - if LibSodium.crypto_box_open_easy(output_buffer.to_slice, data_buffer.to_slice, data_size, nonce.to_slice, sender_public_key.to_slice, recipient_secret_key.to_slice) != 0 - raise Error::DecryptionFailed.new("crypto_box_open_easy") - end - output_buffer - end -end - if Cox::LibSodium.sodium_init == -1 abort "Failed to init libsodium" end diff --git a/src/cox/crypto_box/pair.cr b/src/cox/crypto_box/pair.cr index 213e761..18567d8 100644 --- a/src/cox/crypto_box/pair.cr +++ b/src/cox/crypto_box/pair.cr @@ -2,8 +2,13 @@ require "../lib_sodium" module Cox::CryptoBox class Pair - # TODO: precompute using crypto_box_beforenm + include Wipe + + # BUG: precompute size + @bytes = Bytes.new(1) + def initialize(@secret_key : SecretKey, @public_key : PublicKey) + # TODO: precompute using crypto_box_beforenm end def encrypt_easy(src) @@ -25,8 +30,5 @@ module Cox::CryptoBox end # TODO detached - def close - # TODO: wipe state - end end end diff --git a/src/cox/crypto_box/public_key.cr b/src/cox/crypto_box/public_key.cr index 783b043..41af091 100644 --- a/src/cox/crypto_box/public_key.cr +++ b/src/cox/crypto_box/public_key.cr @@ -2,6 +2,7 @@ require "../lib_sodium" module Cox::CryptoBox class PublicKey < Key + include Wipe KEY_SIZE = LibSodium::PUBLIC_KEY_SIZE getter bytes : Bytes diff --git a/src/cox/crypto_box/secret_key.cr b/src/cox/crypto_box/secret_key.cr index 7f3e826..599c3f4 100644 --- a/src/cox/crypto_box/secret_key.cr +++ b/src/cox/crypto_box/secret_key.cr @@ -2,6 +2,7 @@ require "../lib_sodium" module Cox::CryptoBox class SecretKey < Key + include Wipe KEY_SIZE = LibSodium::SECRET_KEY_SIZE MAC_SIZE = LibSodium::MAC_SIZE @@ -24,11 +25,12 @@ module Cox::CryptoBox @public_key = PublicKey.new pkey end - def pair(public_key) + # Return a Pair containing a precomputed shared secret for use with encryption/decryption. + def pair(public_key) : Pair Pair.new self, public_key end - # Create a new pair and automatically close when exiting the block. + # Create a new pair and automatically close when the block exits. def pair(public_key) pa = pair public_key begin diff --git a/src/cox/lib_sodium.cr b/src/cox/lib_sodium.cr index 1e3bc0c..7d75c03 100644 --- a/src/cox/lib_sodium.cr +++ b/src/cox/lib_sodium.cr @@ -35,6 +35,7 @@ module Cox fun crypto_generichash_blake2b_keybytes_max : LibC::SizeT fun crypto_generichash_blake2b_saltbytes : LibC::SizeT fun crypto_generichash_blake2b_personalbytes : LibC::SizeT + fun sodium_memzero(Pointer(LibC::UChar), LibC::SizeT) : Nil PUBLIC_KEY_SIZE = crypto_box_publickeybytes() SECRET_KEY_SIZE = crypto_box_secretkeybytes() diff --git a/src/cox/secret_key.cr b/src/cox/secret_box.cr similarity index 78% rename from src/cox/secret_key.cr rename to src/cox/secret_box.cr index 6d491d6..59ebf55 100644 --- a/src/cox/secret_key.cr +++ b/src/cox/secret_box.cr @@ -1,22 +1,35 @@ require "./lib_sodium" module Cox - class SecretKey < Key - property bytes : Bytes - + # [https://libsodium.gitbook.io/doc/secret-key_cryptography](https://libsodium.gitbook.io/doc/secret-key_cryptography) + # + # ```crystal + # key = Cox::SecretKey.new + # message = "foobar" + # encrypted, nonce = key.encrypt_easy message + # + # # On the other side. + # key = Cox::SecretKey.new key + # message = key.decrypt_easy encrypted, nonce + # ``` + class SecretBox < Key KEY_SIZE = LibSodium::SECRET_KEY_SIZE MAC_SIZE = LibSodium::MAC_SIZE + property bytes : Bytes + + # Generate a new random key. + def initialize + @bytes = Random::Secure.random_bytes(KEY_SIZE) + end + + # Use an existing key from bytes. def initialize(@bytes : Bytes) if bytes.bytesize != KEY_SIZE raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}") end end - def self.random - new Random::Secure.random_bytes(KEY_SIZE) - end - def encrypt_easy(data) encrypt_easy data.to_slice end diff --git a/src/cox/sign/public_key.cr b/src/cox/sign/public_key.cr index 02d5c31..b4220cf 100644 --- a/src/cox/sign/public_key.cr +++ b/src/cox/sign/public_key.cr @@ -2,6 +2,7 @@ require "../lib_sodium" module Cox class Sign::PublicKey < Key + include Wipe KEY_SIZE = LibSodium::PUBLIC_SIGN_SIZE getter bytes : Bytes diff --git a/src/cox/sign/secret_key.cr b/src/cox/sign/secret_key.cr index 705bedc..2e10001 100644 --- a/src/cox/sign/secret_key.cr +++ b/src/cox/sign/secret_key.cr @@ -2,6 +2,7 @@ require "../lib_sodium" module Cox class Sign::SecretKey < Cox::Key + include Wipe KEY_SIZE = LibSodium::SECRET_SIGN_SIZE getter bytes : Bytes diff --git a/src/cox/wipe.cr b/src/cox/wipe.cr new file mode 100644 index 0000000..8073907 --- /dev/null +++ b/src/cox/wipe.cr @@ -0,0 +1,18 @@ +module Cox::Wipe + @closed = false + + def close + return if @closed + wipe + @closed = true + end + + protected def wipe + return if @closed + Cox.memzero @bytes + end + + def finalize + wipe # Don't call close. May be overridden with calls unsafe within finalize. + end +end