103 lines
3.3 KiB
Crystal
103 lines
3.3 KiB
Crystal
require "./lib_sodium"
|
|
require "./secure_buffer"
|
|
require "./wipe"
|
|
|
|
module Sodium
|
|
# Key derivation function
|
|
#
|
|
# Usage:
|
|
# ```
|
|
# kdf = KDF.new
|
|
# subkey_id = 0
|
|
# output_size = 16
|
|
# subkey = kdf.derive "8bytectx", subkey_id, output_size
|
|
#
|
|
# Memory for this class is held in a sodium guarded page with noaccess.
|
|
# Readonly access is temporarily enabled when deriving keys.
|
|
# Calling #to_slice marks the page readonly permanently.
|
|
# ```
|
|
class Kdf
|
|
include Wipe
|
|
|
|
KEY_SIZE = LibSodium.crypto_kdf_keybytes.to_i
|
|
CONTEXT_SIZE = LibSodium.crypto_kdf_contextbytes
|
|
|
|
# Returns key
|
|
delegate to_slice, to: @sbuf
|
|
|
|
# Use an existing KDF key.
|
|
#
|
|
# * Copies key to a new SecureBuffer
|
|
# * 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
|
|
|
|
@sbuf = SecureBuffer.new(bytes, erase).noaccess
|
|
end
|
|
|
|
# Use an existing KDF SecureBuffer key.
|
|
def initialize(@sbuf : SecureBuffer)
|
|
if @sbuf.bytesize != KEY_SIZE
|
|
raise ArgumentError.new("bytes must be #{KEY_SIZE}, got #{sbuf.bytesize}")
|
|
end
|
|
@sbuf.noaccess
|
|
end
|
|
|
|
# Generate a new random KDF key.
|
|
#
|
|
# Make sure to save kdf.to_slice before kdf goes out of scope.
|
|
def initialize
|
|
@sbuf = SecureBuffer.random(KEY_SIZE).noaccess
|
|
end
|
|
|
|
# Derive a consistent subkey based on `context` and `subkey_id`.
|
|
#
|
|
# context and subkey don't need to be secret
|
|
# * context must be 8 bytes
|
|
# * subkey_size must be 16..64 bytes as of libsodium 1.0.17
|
|
#
|
|
# 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 = SecureBuffer.new subkey_size
|
|
@sbuf.readonly do
|
|
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
|
|
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
|