API changed all Key classes .bytes to .to_slice
Switched most custom Wipe implementation to libsodium guarded memory.
This commit is contained in:
parent
769e02e4c7
commit
d1c8829fcf
33
README.md
33
README.md
@ -49,9 +49,9 @@ Crystal bindings for the [libsodium API](https://libsodium.gitbook.io/doc/)
|
||||
- [ ] [One time auth](https://libsodium.gitbook.io/doc/advanced/poly1305)
|
||||
- [ ] Padding
|
||||
- Library features
|
||||
- Faster builds by requiring what you need (`require "sodium/secret_box"`)
|
||||
- Controlled memory wiping (by calling `.close`)
|
||||
- Semi-automatic memory wiping (on GC).
|
||||
- [x] Faster builds by requiring what you need (`require "sodium/secret_box"`)
|
||||
- [x] All SecretKey's held in libsodium guarded memory.
|
||||
- [ ] Controlled memory wiping (by calling `.close`)
|
||||
|
||||
☑ Indicate specs are compared against test vectors from another source.
|
||||
|
||||
@ -96,38 +96,17 @@ dependencies:
|
||||
|
||||
## Usage
|
||||
|
||||
The `specs` provide the best examples of how to use or misuse this shard.
|
||||
See `examples` for help on using these classes in a complete application.
|
||||
|
||||
### Warning
|
||||
The `specs` provide the best examples of how to use or misuse individual classes.
|
||||
|
||||
This library uses automatic memory wiping. **Make sure you write your keys to disk or send them over a network
|
||||
before losing references to objects that contain keys.** You may also call `.dup`.
|
||||
|
||||
```crystal
|
||||
# Returns Bytes representation and loses reference to SecretKey
|
||||
def new_key
|
||||
secret_key = Sodium::CryptoBox::SecretKey.new
|
||||
secret_key.bytes
|
||||
end
|
||||
|
||||
saved_key = new_key
|
||||
|
||||
p saved_key[0, 8]
|
||||
GC.collect
|
||||
p saved_key[0, 8]
|
||||
```
|
||||
|
||||
```
|
||||
# Before GC
|
||||
Bytes[175, 134, 134, 159, 149, 208, 171, 251]
|
||||
# After GC
|
||||
Bytes[0, 0, 0, 0, 0, 0, 0, 0]
|
||||
```
|
||||
|
||||
You may call `.close` on any object that retains keying material to wipe it's key(s) earlier.
|
||||
Objects with a `.close` method also respond to `Class.open` and wipe when the block returns.
|
||||
|
||||
```crystal
|
||||
# TODO
|
||||
Sodium::CryptoBox::SecretKey.open(sec_key, pub_key) do |secret_key|
|
||||
... Do crypto operations ...
|
||||
end
|
||||
|
@ -2,30 +2,30 @@ require "../../spec_helper"
|
||||
require "../../../src/sodium/crypto_box/secret_key"
|
||||
|
||||
private def new_key_bytes
|
||||
Sodium::CryptoBox::SecretKey.new.bytes
|
||||
Sodium::CryptoBox::SecretKey.new.to_slice
|
||||
end
|
||||
|
||||
describe Sodium::CryptoBox::SecretKey do
|
||||
it "loads keys" do
|
||||
key1 = Sodium::CryptoBox::SecretKey.new
|
||||
key2 = Sodium::CryptoBox::SecretKey.new key1.bytes, key1.public_key.bytes
|
||||
key1.bytes.should eq key2.bytes
|
||||
key1.public_key.bytes.should eq key2.public_key.bytes
|
||||
key2 = Sodium::CryptoBox::SecretKey.new key1.to_slice, key1.public_key.to_slice
|
||||
key1.to_slice.should eq key2.to_slice
|
||||
key1.public_key.to_slice.should eq key2.public_key.to_slice
|
||||
end
|
||||
|
||||
it "recomputes the public_key" do
|
||||
key1 = Sodium::CryptoBox::SecretKey.new
|
||||
key2 = Sodium::CryptoBox::SecretKey.new key1.bytes
|
||||
key1.bytes.should eq key2.bytes
|
||||
key1.public_key.bytes.should eq key2.public_key.bytes
|
||||
key2 = Sodium::CryptoBox::SecretKey.new key1.to_slice
|
||||
key1.to_slice.should eq key2.to_slice
|
||||
key1.public_key.to_slice.should eq key2.public_key.to_slice
|
||||
end
|
||||
|
||||
it "seed keys" do
|
||||
seed = Bytes.new Sodium::CryptoBox::SecretKey::SEED_SIZE
|
||||
key1 = Sodium::CryptoBox::SecretKey.new seed: seed
|
||||
key2 = Sodium::CryptoBox::SecretKey.new seed: seed
|
||||
key1.bytes.should eq key2.bytes
|
||||
key1.public_key.bytes.should eq key2.public_key.bytes
|
||||
key1.to_slice.should eq key2.to_slice
|
||||
key1.public_key.to_slice.should eq key2.public_key.to_slice
|
||||
end
|
||||
|
||||
it "authenticated easy encrypt/decrypt" do
|
||||
@ -65,8 +65,4 @@ describe Sodium::CryptoBox::SecretKey do
|
||||
|
||||
String.new(decrypted).should eq(data)
|
||||
end
|
||||
|
||||
it "wipes keys" do
|
||||
check_wiped new_key_bytes
|
||||
end
|
||||
end
|
||||
|
@ -8,7 +8,7 @@ describe Sodium::Kdf do
|
||||
kdf1 = Sodium::Kdf.new
|
||||
|
||||
# verify loading saved key
|
||||
kdf2 = Sodium::Kdf.new kdf1.bytes
|
||||
kdf2 = Sodium::Kdf.new kdf1.to_slice.dup
|
||||
|
||||
# verify generated subkey's are the same after loading
|
||||
key1_s1 = kdf1.derive CONTEXT, 0, 16
|
||||
|
@ -71,18 +71,18 @@ describe Sodium::Pwhash do
|
||||
it "key_derive fails without a mode" do
|
||||
pwhash = pw_min
|
||||
expect_raises(ArgumentError) do
|
||||
pwhash.key_derive pwhash.salt, "foo", 16
|
||||
pwhash.key_derive pwhash.random_salt, "foo", 16
|
||||
end
|
||||
end
|
||||
|
||||
it "derives a key from a password" do
|
||||
pwhash = pw_min
|
||||
pwhash.mode = Sodium::Pwhash::Mode::Argon2id13
|
||||
salt = pwhash.salt
|
||||
salt = pwhash.random_salt
|
||||
key1 = pwhash.key_derive salt, "foo", 16
|
||||
key2 = pwhash.key_derive salt, "foo", 16
|
||||
key3 = pwhash.key_derive salt, "bar", 16
|
||||
key4 = pwhash.key_derive pwhash.salt, "foo", 16
|
||||
key4 = pwhash.key_derive pwhash.random_salt, "foo", 16
|
||||
|
||||
key1.bytesize.should eq 16
|
||||
key1.should eq key2
|
||||
|
@ -1,31 +1,31 @@
|
||||
require "../../spec_helper"
|
||||
require "../../../src/sodium/sign/secret_key"
|
||||
|
||||
private def new_sign_key_bytes
|
||||
Sodium::Sign::SecretKey.new.bytes
|
||||
private def new_sign_key_to_slice
|
||||
Sodium::Sign::SecretKey.new.to_slice
|
||||
end
|
||||
|
||||
describe Sodium::Sign::SecretKey do
|
||||
it "loads keys" do
|
||||
key1 = Sodium::Sign::SecretKey.new
|
||||
key2 = Sodium::Sign::SecretKey.new key1.bytes, key1.public_key.bytes
|
||||
key1.bytes.should eq key2.bytes
|
||||
key1.public_key.bytes.should eq key2.public_key.bytes
|
||||
key2 = Sodium::Sign::SecretKey.new key1.to_slice, key1.public_key.to_slice
|
||||
key1.to_slice.should eq key2.to_slice
|
||||
key1.public_key.to_slice.should eq key2.public_key.to_slice
|
||||
end
|
||||
|
||||
it "recomputes the public key" do
|
||||
key1 = Sodium::Sign::SecretKey.new
|
||||
key2 = Sodium::Sign::SecretKey.new key1.bytes
|
||||
key1.bytes.should eq key2.bytes
|
||||
key1.public_key.bytes.should eq key2.public_key.bytes
|
||||
key2 = Sodium::Sign::SecretKey.new key1.to_slice
|
||||
key1.to_slice.should eq key2.to_slice
|
||||
key1.public_key.to_slice.should eq key2.public_key.to_slice
|
||||
end
|
||||
|
||||
it "seed keys" do
|
||||
seed = Bytes.new Sodium::Sign::SecretKey::SEED_SIZE
|
||||
key1 = Sodium::Sign::SecretKey.new seed: seed
|
||||
key2 = Sodium::Sign::SecretKey.new seed: seed
|
||||
key1.bytes.should eq key2.bytes
|
||||
key1.public_key.bytes.should eq key2.public_key.bytes
|
||||
key1.to_slice.should eq key2.to_slice
|
||||
key1.public_key.to_slice.should eq key2.public_key.to_slice
|
||||
end
|
||||
|
||||
it "signs and verifies" do
|
||||
@ -45,8 +45,4 @@ describe Sodium::Sign::SecretKey do
|
||||
skey.public_key.verify_detached "bar", sig
|
||||
end
|
||||
end
|
||||
|
||||
it "checks wiped" do
|
||||
check_wiped new_sign_key_bytes
|
||||
end
|
||||
end
|
||||
|
@ -7,7 +7,7 @@ module Sodium
|
||||
class CryptoBox
|
||||
include Wipe
|
||||
|
||||
MAC_SIZE = LibSodium.crypto_box_macbytes
|
||||
MAC_SIZE = LibSodium.crypto_box_macbytes.to_i
|
||||
|
||||
# BUG: precompute size
|
||||
@[Wipe::Var]
|
||||
|
@ -3,10 +3,10 @@ require "../key"
|
||||
|
||||
class Sodium::CryptoBox
|
||||
class PublicKey < Key
|
||||
KEY_SIZE = LibSodium.crypto_box_publickeybytes
|
||||
KEY_SIZE = LibSodium.crypto_box_publickeybytes.to_i
|
||||
SEAL_SIZE = LibSodium.crypto_box_sealbytes
|
||||
|
||||
getter bytes : Bytes
|
||||
delegate to_slice, to: @bytes
|
||||
|
||||
# :nodoc:
|
||||
# Only used by SecretKey
|
||||
|
@ -6,55 +6,73 @@ require "../crypto_box"
|
||||
class Sodium::CryptoBox
|
||||
# Key used for encryption + authentication or encryption without authentication, not for unencrypted signing.
|
||||
#
|
||||
# WARNING: This class takes ownership of any key material passed to it.
|
||||
# If you don't want this behavior pass a duplicate of the key/seed to initialize().
|
||||
class SecretKey < Key
|
||||
KEY_SIZE = LibSodium.crypto_box_secretkeybytes
|
||||
SEED_SIZE = LibSodium.crypto_box_seedbytes
|
||||
SEAL_SIZE = LibSodium.crypto_box_sealbytes
|
||||
KEY_SIZE = LibSodium.crypto_box_secretkeybytes.to_i
|
||||
SEED_SIZE = LibSodium.crypto_box_seedbytes.to_i
|
||||
SEAL_SIZE = LibSodium.crypto_box_sealbytes.to_i
|
||||
|
||||
getter public_key : PublicKey
|
||||
|
||||
@[Wipe::Var]
|
||||
getter bytes : Bytes
|
||||
@[Wipe::Var]
|
||||
@seed : Bytes?
|
||||
delegate to_slice, to: @sbuf
|
||||
|
||||
@seed : SecureBuffer?
|
||||
|
||||
# Generate a new random secret/public key pair.
|
||||
def initialize
|
||||
@bytes = Bytes.new(KEY_SIZE)
|
||||
@sbuf = SecureBuffer.new KEY_SIZE
|
||||
@public_key = PublicKey.new
|
||||
if LibSodium.crypto_box_keypair(@public_key.bytes, @bytes) != 0
|
||||
if LibSodium.crypto_box_keypair(@public_key.to_slice, self.to_slice) != 0
|
||||
raise Sodium::Error.new("crypto_box_keypair")
|
||||
end
|
||||
end
|
||||
|
||||
# Use existing secret and public keys.
|
||||
# Copies secret key to a SecureBuffer.
|
||||
# Recomputes the public key from a secret key if missing.
|
||||
def initialize(@bytes : Bytes, pkey : Bytes? = nil)
|
||||
def initialize(bytes : Bytes, pkey : Bytes? = nil)
|
||||
raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}") if bytes.bytesize != KEY_SIZE
|
||||
@sbuf = SecureBuffer.new bytes
|
||||
if pk = pkey
|
||||
@public_key = PublicKey.new pk
|
||||
else
|
||||
@public_key = PublicKey.new
|
||||
if LibSodium.crypto_scalarmult_base(@public_key.bytes, @bytes) != 0
|
||||
if LibSodium.crypto_scalarmult_base(@public_key.to_slice, self.to_slice) != 0
|
||||
raise Sodium::Error.new("crypto_scalarmult_base")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Derive a new secret/public key pair based on a consistent seed.
|
||||
def initialize(*, seed : Bytes)
|
||||
# Copies seed to a SecureBuffer.
|
||||
def initialize(*, seed : Bytes, erase = false)
|
||||
raise ArgumentError.new("Secret sign seed must be #{SEED_SIZE}, got #{seed.bytesize}") unless seed.bytesize == SEED_SIZE
|
||||
@seed = SecureBuffer.new seed, erase: erase
|
||||
|
||||
@sbuf = SecureBuffer.new KEY_SIZE
|
||||
@public_key = PublicKey.new
|
||||
if LibSodium.crypto_box_seed_keypair(@public_key.to_slice, self.to_slice, seed) != 0
|
||||
raise Sodium::Error.new("crypto_box_seed_keypair")
|
||||
end
|
||||
end
|
||||
|
||||
# Derive a new secret/public key pair based on a consistent seed.
|
||||
def initialize(*, seed : SecureBuffer)
|
||||
raise ArgumentError.new("Secret sign seed must be #{SEED_SIZE}, got #{seed.bytesize}") unless seed.bytesize == SEED_SIZE
|
||||
@seed = seed
|
||||
|
||||
@bytes = Bytes.new(KEY_SIZE)
|
||||
@sbuf = SecureBuffer.new KEY_SIZE
|
||||
@public_key = PublicKey.new
|
||||
if LibSodium.crypto_box_seed_keypair(@public_key.bytes, @bytes, seed) != 0
|
||||
if LibSodium.crypto_box_seed_keypair(@public_key.to_slice, self.to_slice, seed) != 0
|
||||
raise Sodium::Error.new("crypto_box_seed_keypair")
|
||||
end
|
||||
end
|
||||
|
||||
def seed
|
||||
# BUG: Generate seed if not set.
|
||||
@seed.not_nil!.to_slice
|
||||
end
|
||||
|
||||
# Return a Box containing a precomputed shared secret for use with authenticated encryption/decryption.
|
||||
def box(public_key) : CryptoBox
|
||||
CryptoBox.new self, public_key
|
||||
@ -77,7 +95,7 @@ class Sodium::CryptoBox
|
||||
end
|
||||
|
||||
def decrypt(src : Bytes, dst : Bytes = Bytes.new(src.bytesize - SEAL_SIZE)) : Bytes
|
||||
if LibSodium.crypto_box_seal_open(dst, src, src.bytesize, @public_key.bytes, @bytes) != 0
|
||||
if LibSodium.crypto_box_seal_open(dst, src, src.bytesize, @public_key.to_slice, self.to_slice) != 0
|
||||
raise Sodium::Error.new("crypto_box_seal_open")
|
||||
end
|
||||
dst
|
||||
|
@ -1,4 +1,5 @@
|
||||
require "./lib_sodium"
|
||||
require "./secure_buffer"
|
||||
require "./wipe"
|
||||
|
||||
module Sodium
|
||||
@ -17,33 +18,38 @@ module Sodium
|
||||
class Kdf
|
||||
include Wipe
|
||||
|
||||
KEY_SIZE = LibSodium.crypto_kdf_keybytes
|
||||
KEY_SIZE = LibSodium.crypto_kdf_keybytes.to_i
|
||||
CONTEXT_SIZE = LibSodium.crypto_kdf_contextbytes
|
||||
|
||||
@[Wipe::Var]
|
||||
getter bytes : Bytes
|
||||
|
||||
delegate to_slice, to: @bytes
|
||||
delegate to_slice, to: @sbuf
|
||||
|
||||
# Use an existing KDF key.
|
||||
#
|
||||
# WARNING: This class takes ownership of any key material passed to it.
|
||||
# If you don't want this behavior pass a duplicate of the key to initialize().
|
||||
def initialize(bytes : Bytes)
|
||||
# Optionally erases bytes after copying if erase is set
|
||||
def initialize(bytes : Bytes, erase = false)
|
||||
if bytes.bytesize != KEY_SIZE
|
||||
raise ArgumentError.new("bytes must be #{KEY_SIZE}, got #{bytes.bytesize}")
|
||||
end
|
||||
|
||||
@bytes = bytes
|
||||
@sbuf = SecureBuffer.new bytes, erase
|
||||
end
|
||||
|
||||
# Use an existing KDF SecureBuffer key.
|
||||
#
|
||||
# WARNING: This class takes ownership of any key material passed to it.
|
||||
# If you don't want this behavior pass a duplicate of the key to initialize().
|
||||
def initialize(@sbuf : SecureBuffer)
|
||||
if @sbuf.bytesize != KEY_SIZE
|
||||
raise ArgumentError.new("bytes must be #{KEY_SIZE}, got #{sbuf.bytesize}")
|
||||
end
|
||||
@sbuf.readonly
|
||||
end
|
||||
|
||||
# Generate a new random KDF key.
|
||||
#
|
||||
# WARNING: This class takes ownership of any key material passed to it.
|
||||
#
|
||||
# Make sure to save kdf.bytes before kdf goes out of scope.
|
||||
# Make sure to save kdf.to_slice before kdf goes out of scope.
|
||||
def initialize
|
||||
@bytes = Random::Secure.random_bytes(KEY_SIZE)
|
||||
@sbuf = SecureBuffer.random KEY_SIZE
|
||||
end
|
||||
|
||||
# Derive a consistent subkey based on `context` and `subkey_id`.
|
||||
@ -52,17 +58,42 @@ module Sodium
|
||||
# * context must be 8 bytes
|
||||
# * subkey_size must be 16..64 bytes as of libsodium 1.0.17
|
||||
#
|
||||
def derive(context, subkey_id, subkey_size)
|
||||
# Returns a SecureBuffer. May transfer ownership to SecretBox or SecretKey without copying.
|
||||
def derive(context, subkey_id, subkey_size) : SecureBuffer
|
||||
context = context.to_slice
|
||||
if context.bytesize != CONTEXT_SIZE
|
||||
raise ArgumentError.new("context must be #{CONTEXT_SIZE}, got #{context.bytesize}")
|
||||
end
|
||||
|
||||
subkey = Bytes.new subkey_size
|
||||
if (ret = LibSodium.crypto_kdf_derive_from_key(subkey, subkey.bytesize, subkey_id, context, @bytes)) != 0
|
||||
subkey = SecureBuffer.new subkey_size
|
||||
if (ret = LibSodium.crypto_kdf_derive_from_key(subkey, subkey.bytesize, subkey_id, context, self.to_slice)) != 0
|
||||
raise Sodium::Error.new("crypto_kdf_derive_from_key returned #{ret} (subkey size is probably out of range)")
|
||||
end
|
||||
subkey
|
||||
end
|
||||
|
||||
# Convenience method to create a new CryptoBox::Secret without handling the key.
|
||||
#
|
||||
# See derive() for further information on context and subkey_id.
|
||||
def derive_cryptobox(context, subkey_id) : CryptoBox::SecretKey
|
||||
subkey = derive context, subkey_id, CryptoBox::SecretKey::SEED_SIZE
|
||||
CryptoBox::SecretKey.new seed: subkey
|
||||
end
|
||||
|
||||
# Convenience method to create a new Sign::Secret without handling the key.
|
||||
#
|
||||
# See derive() for further information on context and subkey_id.
|
||||
def derive_sign(context, subkey_id) : Sign::SecretKey
|
||||
subkey = derive context, subkey_id, Sign::SecretKey::SEED_SIZE
|
||||
Sign::SecretKey.new seed: subkey
|
||||
end
|
||||
|
||||
# Convenience method to create a new SecretBox without handling the key.
|
||||
#
|
||||
# See derive() for further information on context and subkey_id.
|
||||
def derive_secretbox(context, subkey_id) : SecretBox
|
||||
subkey = derive context, subkey_id, SecretBox::KEY_SIZE
|
||||
SecretBox.new subkey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,15 +1,14 @@
|
||||
require "./secure_buffer"
|
||||
require "./wipe"
|
||||
|
||||
module Sodium
|
||||
abstract class Key
|
||||
include Sodium::Wipe
|
||||
|
||||
abstract def bytes
|
||||
|
||||
delegate to_slice, to: @bytes
|
||||
abstract def to_slice : Bytes
|
||||
|
||||
def to_base64
|
||||
Base64.encode(bytes)
|
||||
Base64.encode(to_slice)
|
||||
end
|
||||
|
||||
def self.from_base64(encoded_key)
|
||||
|
@ -44,8 +44,16 @@ module Sodium
|
||||
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
|
||||
|
||||
fun sodium_malloc(LibC::SizeT) : Pointer(LibC::UChar)
|
||||
fun sodium_free(Pointer(LibC::UChar)) : Nil
|
||||
|
||||
fun sodium_mprotect_noaccess(Pointer(LibC::UChar)) : LibC::Int
|
||||
fun sodium_mprotect_readonly(Pointer(LibC::UChar)) : LibC::Int
|
||||
fun sodium_mprotect_readwrite(Pointer(LibC::UChar)) : LibC::Int
|
||||
|
||||
NONCE_SIZE = crypto_box_noncebytes()
|
||||
|
||||
fun crypto_secretbox_easy(
|
||||
|
@ -1,4 +1,5 @@
|
||||
require "./lib_sodium"
|
||||
require "./secure_buffer"
|
||||
|
||||
module Sodium
|
||||
# [Argon2 Password Hashing](https://libsodium.gitbook.io/doc/password_hashing/the_argon2i_function)
|
||||
@ -74,19 +75,19 @@ module Sodium
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a consistent key based on [salt, pass, key_bytes, mode, ops_limit, mem_limit]
|
||||
# Returns a consistent key based on [salt, pass, key_bytes, mode, ops_limit, mem_limit] in a SecureBuffer
|
||||
#
|
||||
# Must set a mode before calling.
|
||||
def key_derive(salt, pass, key_bytes)
|
||||
key_derive salt.to_slice, pass.to_slice, key_bytes
|
||||
end
|
||||
|
||||
def key_derive(salt : Bytes, pass : Bytes, key_bytes) : Bytes
|
||||
def key_derive(salt : Bytes, pass : Bytes, key_bytes) : SecureBuffer
|
||||
raise "salt expected #{SALT_SIZE} bytes, got #{salt.bytesize} " if salt.bytesize != SALT_SIZE
|
||||
|
||||
if m = mode
|
||||
key = Bytes.new key_bytes
|
||||
if LibSodium.crypto_pwhash(key, key.bytesize, pass, pass.bytesize, salt, @opslimit, @memlimit, m) != 0
|
||||
key = SecureBuffer.new key_bytes
|
||||
if LibSodium.crypto_pwhash(key.to_slice, key.bytesize, pass, pass.bytesize, salt, @opslimit, @memlimit, m) != 0
|
||||
raise Sodium::Error.new("crypto_pwhash_str")
|
||||
end
|
||||
key
|
||||
@ -95,8 +96,14 @@ module Sodium
|
||||
end
|
||||
end
|
||||
|
||||
# Derives a key using key_derive and returns KDF.new(key)
|
||||
def kdf_derive(salt, pass, key_bytes) : Kdf
|
||||
key = key_derive salt, pass, key_bytes
|
||||
Kdf.new key
|
||||
end
|
||||
|
||||
# Returns a random salt for use with #key_derive
|
||||
def salt
|
||||
def random_salt
|
||||
Random::Secure.random_bytes SALT_SIZE
|
||||
end
|
||||
end
|
||||
|
@ -18,23 +18,33 @@ module Sodium
|
||||
# message = key.decrypt_easy encrypted, nonce
|
||||
# ```
|
||||
class SecretBox < Key
|
||||
KEY_SIZE = LibSodium.crypto_secretbox_keybytes
|
||||
NONCE_SIZE = LibSodium.crypto_secretbox_noncebytes
|
||||
MAC_SIZE = LibSodium.crypto_secretbox_macbytes
|
||||
KEY_SIZE = LibSodium.crypto_secretbox_keybytes.to_i
|
||||
NONCE_SIZE = LibSodium.crypto_secretbox_noncebytes.to_i
|
||||
MAC_SIZE = LibSodium.crypto_secretbox_macbytes.to_i
|
||||
|
||||
@[Wipe::Var]
|
||||
getter bytes : Bytes
|
||||
delegate to_slice, to: @buf
|
||||
|
||||
# Generate a new random key.
|
||||
# Generate a new random key held in a SecureBuffer.
|
||||
def initialize
|
||||
@bytes = Random::Secure.random_bytes(KEY_SIZE)
|
||||
@buf = SecureBuffer.random KEY_SIZE
|
||||
end
|
||||
|
||||
# Use an existing key from bytes.
|
||||
def initialize(@bytes : Bytes)
|
||||
# Use an existing SecureBuffer.
|
||||
protected def initialize(@buf : SecureBuffer)
|
||||
if @buf.bytesize != KEY_SIZE
|
||||
raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{@buf.bytesize}")
|
||||
end
|
||||
@buf.readonly
|
||||
end
|
||||
|
||||
# Copy bytes to a new SecureBuffer
|
||||
#
|
||||
# Optionally erases bytes after copying if erase is set
|
||||
protected def initialize(bytes : Bytes, erase = false)
|
||||
if bytes.bytesize != KEY_SIZE
|
||||
raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}")
|
||||
end
|
||||
@buf = SecureBuffer.new bytes, erase: erase
|
||||
end
|
||||
|
||||
def encrypt_easy(data)
|
||||
@ -60,7 +70,7 @@ module Sodium
|
||||
if dst.bytesize != (src.bytesize + MAC_SIZE)
|
||||
raise ArgumentError.new("dst.bytesize must be src.bytesize + MAC_SIZE, got #{dst.bytesize}")
|
||||
end
|
||||
if LibSodium.crypto_secretbox_easy(dst, src, src.bytesize, nonce.to_slice, @bytes) != 0
|
||||
if LibSodium.crypto_secretbox_easy(dst, src, src.bytesize, nonce.to_slice, self.to_slice) != 0
|
||||
raise Sodium::Error.new("crypto_secretbox_easy")
|
||||
end
|
||||
dst
|
||||
@ -77,7 +87,7 @@ module Sodium
|
||||
if dst.bytesize != (src.bytesize - MAC_SIZE)
|
||||
raise ArgumentError.new("dst.bytesize must be src.bytesize - MAC_SIZE, got #{dst.bytesize}")
|
||||
end
|
||||
if LibSodium.crypto_secretbox_open_easy(dst, src, src.bytesize, nonce.to_slice, @bytes) != 0
|
||||
if LibSodium.crypto_secretbox_open_easy(dst, src, src.bytesize, nonce.to_slice, self.to_slice) != 0
|
||||
raise Sodium::Error::DecryptionFailed.new("crypto_secretbox_easy")
|
||||
end
|
||||
dst
|
||||
|
78
src/sodium/secure_buffer.cr
Normal file
78
src/sodium/secure_buffer.cr
Normal file
@ -0,0 +1,78 @@
|
||||
require "./lib_sodium"
|
||||
require "./wipe"
|
||||
|
||||
module Sodium
|
||||
class SecureBuffer
|
||||
getter bytesize
|
||||
|
||||
# Allocate guarded memory using [sodium_malloc](https://libsodium.gitbook.io/doc/memory_management)
|
||||
def initialize(@bytesize : Int32)
|
||||
@ptr = LibSodium.sodium_malloc @bytesize
|
||||
end
|
||||
|
||||
# Returns a **readonly** random SecureBuffer.
|
||||
def self.random(size)
|
||||
buf = new(size)
|
||||
Random::Secure.random_bytes buf.to_slice
|
||||
buf.readonly
|
||||
end
|
||||
|
||||
# Copies bytes to a **readonly** SecureBuffer.
|
||||
# Optionally erases bytes after copying if erase is set
|
||||
def initialize(bytes : Bytes, erase = false)
|
||||
initialize bytes.bytesize
|
||||
bytes.copy_to self.to_slice
|
||||
Sodium.memzero(bytes) if erase
|
||||
readonly
|
||||
end
|
||||
|
||||
def wipe
|
||||
readwrite
|
||||
Sodium.memzero self.to_slice
|
||||
end
|
||||
|
||||
def finalize
|
||||
LibSodium.sodium_free @ptr
|
||||
end
|
||||
|
||||
def to_slice
|
||||
Slice(UInt8).new @ptr, @bytesize
|
||||
end
|
||||
|
||||
def to_unsafe
|
||||
@ptr
|
||||
end
|
||||
|
||||
# Makes a region allocated using sodium_malloc() or sodium_allocarray() inaccessible. It cannot be read or written, but the data are preserved.
|
||||
def noaccess
|
||||
if LibSodium.sodium_mprotect_noaccess(@ptr) != 0
|
||||
raise "sodium_mprotect_noaccess"
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# Marks a region allocated using sodium_malloc() or sodium_allocarray() as read-only.
|
||||
def readonly
|
||||
if LibSodium.sodium_mprotect_readonly(@ptr) != 0
|
||||
raise "sodium_mprotect_readonly"
|
||||
end
|
||||
self
|
||||
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().
|
||||
def readwrite
|
||||
if LibSodium.sodium_mprotect_readwrite(@ptr) != 0
|
||||
raise "sodium_mprotect_readwrite"
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def ==(other : self)
|
||||
self.to_slice == other.to_slice
|
||||
end
|
||||
|
||||
def ==(other : Bytes)
|
||||
self.to_slice == other
|
||||
end
|
||||
end
|
||||
end
|
@ -2,10 +2,10 @@ require "../lib_sodium"
|
||||
|
||||
module Sodium
|
||||
class Sign::PublicKey < Key
|
||||
KEY_SIZE = LibSodium.crypto_sign_publickeybytes
|
||||
SIG_SIZE = LibSodium.crypto_sign_bytes
|
||||
KEY_SIZE = LibSodium.crypto_sign_publickeybytes.to_i
|
||||
SIG_SIZE = LibSodium.crypto_sign_bytes.to_i
|
||||
|
||||
getter bytes : Bytes
|
||||
delegate to_slice, to: @bytes
|
||||
|
||||
# :nodoc:
|
||||
# Only used by SecretKey
|
||||
|
@ -15,49 +15,57 @@ module Sodium
|
||||
# key.public_key.verify_detached data
|
||||
# ```
|
||||
class Sign::SecretKey < Sodium::Key
|
||||
KEY_SIZE = LibSodium.crypto_sign_secretkeybytes
|
||||
SIG_SIZE = LibSodium.crypto_sign_bytes
|
||||
SEED_SIZE = LibSodium.crypto_sign_seedbytes
|
||||
KEY_SIZE = LibSodium.crypto_sign_secretkeybytes.to_i
|
||||
SIG_SIZE = LibSodium.crypto_sign_bytes.to_i
|
||||
SEED_SIZE = LibSodium.crypto_sign_seedbytes.to_i
|
||||
|
||||
getter public_key : PublicKey
|
||||
|
||||
@[Wipe::Var]
|
||||
getter bytes : Bytes
|
||||
@[Wipe::Var]
|
||||
@seed : Bytes?
|
||||
delegate to_slice, to: @sbuf
|
||||
|
||||
@seed : SecureBuffer?
|
||||
|
||||
# Generates a new random secret/public key pair.
|
||||
def initialize
|
||||
@bytes = Bytes.new(KEY_SIZE)
|
||||
@sbuf = SecureBuffer.new KEY_SIZE
|
||||
@public_key = PublicKey.new
|
||||
if LibSodium.crypto_sign_keypair(@public_key.bytes, @bytes) != 0
|
||||
if LibSodium.crypto_sign_keypair(@public_key.to_slice, self.to_slice) != 0
|
||||
raise Sodium::Error.new("crypto_sign_keypair")
|
||||
end
|
||||
end
|
||||
|
||||
# Use existing secret and public keys.
|
||||
# Copies secret key to a SecureBuffer.
|
||||
# Recomputes the public key from a secret key if missing.
|
||||
def initialize(@bytes : Bytes, pkey : Bytes? = nil)
|
||||
raise ArgumentError.new("Secret sign key must be #{KEY_SIZE}, got #{@bytes.bytesize}") unless @bytes.bytesize == KEY_SIZE
|
||||
def initialize(bytes : Bytes, pkey : Bytes? = nil)
|
||||
raise ArgumentError.new("Secret sign key must be #{KEY_SIZE}, got #{bytes.bytesize}") unless bytes.bytesize == KEY_SIZE
|
||||
|
||||
@sbuf = SecureBuffer.new bytes
|
||||
if pk = pkey
|
||||
@public_key = PublicKey.new pkey
|
||||
else
|
||||
@public_key = PublicKey.new
|
||||
if LibSodium.crypto_sign_ed25519_sk_to_pk(@public_key.bytes, @bytes) != 0
|
||||
if LibSodium.crypto_sign_ed25519_sk_to_pk(@public_key.to_slice, self.to_slice) != 0
|
||||
raise Sodium::Error.new("crypto_sign_ed25519_sk_to_pk")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Derive a new secret/public key pair based on a consistent seed.
|
||||
def initialize(*, seed : Bytes)
|
||||
# Copies seed to a SecureBuffer.
|
||||
def initialize(*, seed : Bytes, erase = false)
|
||||
raise ArgumentError.new("Secret sign seed must be #{SEED_SIZE}, got #{seed.bytesize}") unless seed.bytesize == SEED_SIZE
|
||||
initialize(seed: SecureBuffer.new(seed, erase: erase))
|
||||
end
|
||||
|
||||
# Derive a new secret/public key pair based on a consistent seed.
|
||||
def initialize(*, seed : SecureBuffer)
|
||||
raise ArgumentError.new("Secret sign seed must be #{SEED_SIZE}, got #{seed.bytesize}") unless seed.bytesize == SEED_SIZE
|
||||
@seed = seed
|
||||
|
||||
@bytes = Bytes.new(KEY_SIZE)
|
||||
@sbuf = SecureBuffer.new KEY_SIZE
|
||||
@public_key = PublicKey.new
|
||||
if LibSodium.crypto_sign_seed_keypair(@public_key.bytes, @bytes, seed) != 0
|
||||
if LibSodium.crypto_sign_seed_keypair(@public_key.to_slice, self.to_slice, seed.to_slice) != 0
|
||||
raise Sodium::Error.new("crypto_sign_seed_keypair")
|
||||
end
|
||||
end
|
||||
@ -70,7 +78,7 @@ module Sodium
|
||||
|
||||
def sign_detached(message : Bytes)
|
||||
sig = Bytes.new(SIG_SIZE)
|
||||
if LibSodium.crypto_sign_detached(sig, out sig_len, message, message.bytesize, @bytes) != 0
|
||||
if LibSodium.crypto_sign_detached(sig, out sig_len, message, message.bytesize, self.to_slice) != 0
|
||||
raise Error.new("crypto_sign_detached")
|
||||
end
|
||||
sig
|
||||
|
Loading…
Reference in New Issue
Block a user