2019-06-30 04:20:30 +02:00
|
|
|
require "./lib_sodium"
|
|
|
|
require "./wipe"
|
|
|
|
|
2019-06-29 01:17:09 +02:00
|
|
|
module Sodium
|
2019-06-30 04:49:57 +02:00
|
|
|
# Key derivation function
|
|
|
|
#
|
|
|
|
# WARNING: This class takes ownership of any key material passed to it.
|
|
|
|
# Read **each** constructor WARNING for differences in usage.
|
|
|
|
#
|
|
|
|
# Usage:
|
|
|
|
# ```
|
|
|
|
# kdf = KDF.new
|
|
|
|
# subkey_id = 0
|
|
|
|
# output_size = 16
|
|
|
|
# subkey = kdf.derive "8bytectx", subkey_id, output_size
|
|
|
|
# ```
|
2019-05-28 23:31:31 +02:00
|
|
|
class Kdf
|
2019-06-30 04:20:30 +02:00
|
|
|
include Wipe
|
|
|
|
|
2019-06-30 04:49:57 +02:00
|
|
|
KEY_SIZE = LibSodium.crypto_kdf_keybytes
|
|
|
|
CONTEXT_SIZE = LibSodium.crypto_kdf_contextbytes
|
2019-06-30 02:21:00 +02:00
|
|
|
|
2019-06-30 04:20:30 +02:00
|
|
|
@[Wipe::Var]
|
|
|
|
getter bytes : Bytes
|
2019-05-28 23:31:31 +02:00
|
|
|
|
2019-06-28 02:35:31 +02:00
|
|
|
delegate to_slice, to: @bytes
|
|
|
|
|
2019-06-30 04:20:30 +02:00
|
|
|
# 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().
|
2019-05-28 23:31:31 +02:00
|
|
|
def initialize(bytes : Bytes)
|
2019-06-30 04:49:57 +02:00
|
|
|
if bytes.bytesize != KEY_SIZE
|
|
|
|
raise ArgumentError.new("bytes must be #{KEY_SIZE}, got #{bytes.bytesize}")
|
2019-05-28 23:31:31 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
@bytes = bytes
|
|
|
|
end
|
|
|
|
|
2019-06-30 04:20:30 +02:00
|
|
|
# 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.
|
2019-05-28 23:31:31 +02:00
|
|
|
def initialize
|
2019-06-30 04:49:57 +02:00
|
|
|
@bytes = Random::Secure.random_bytes(KEY_SIZE)
|
2019-05-28 23:31:31 +02:00
|
|
|
end
|
|
|
|
|
2019-06-30 04:20:30 +02:00
|
|
|
# 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
|
|
|
|
#
|
2019-06-28 01:52:45 +02:00
|
|
|
def derive(context, subkey_id, subkey_size)
|
2019-06-30 04:49:57 +02:00
|
|
|
context = context.to_slice
|
|
|
|
if context.bytesize != CONTEXT_SIZE
|
|
|
|
raise ArgumentError.new("context must be #{CONTEXT_SIZE}, got #{context.bytesize}")
|
2019-05-28 23:31:31 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
subkey = Bytes.new subkey_size
|
|
|
|
if (ret = LibSodium.crypto_kdf_derive_from_key(subkey, subkey.bytesize, subkey_id, context, @bytes)) != 0
|
2019-06-29 01:17:09 +02:00
|
|
|
raise Sodium::Error.new("crypto_kdf_derive_from_key returned #{ret} (subkey size is probably out of range)")
|
2019-05-28 23:31:31 +02:00
|
|
|
end
|
|
|
|
subkey
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|