API changed all Key classes .bytes to .to_slice

Switched most custom Wipe implementation to libsodium guarded memory.
This commit is contained in:
Didactic Drunk 2019-07-03 17:56:02 -07:00
parent 769e02e4c7
commit d1c8829fcf
16 changed files with 262 additions and 132 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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(

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View File

@ -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