Partial internal switch to Crypto::Secret API

master
Didactic Drunk 2021-06-16 14:50:26 -07:00
parent fca40d7764
commit 0e1b64b1bf
11 changed files with 153 additions and 112 deletions

View File

@ -7,15 +7,8 @@ require "../../../src/sodium/cipher/chalsa"
it "xors" do
data = Bytes.new(100)
cipher1 = Sodium::Cipher::{{ name.id }}.new
cipher2 = Sodium::Cipher::{{ name.id }}.new
key = cipher1.random_key
cipher2.key = key
nonce = cipher1.random_nonce
cipher2.nonce = nonce
cipher1 = Sodium::Cipher::{{ name.id }}.random
cipher2 = Sodium::Cipher::{{ name.id }}.new key: cipher1.key, nonce: cipher1.nonce
output = cipher1.update data
output.should_not eq data # Verify encryption did something.

View File

@ -1,9 +1,12 @@
require "../spec_helper"
require "../../src/sodium/secure_buffer"
require "crypto-secret/test"
class FakeError < Exception
end
test_secret_class Sodium::SecureBuffer
describe Sodium::SecureBuffer do
it "allocates empty" do
buf = Sodium::SecureBuffer.new 5
@ -18,13 +21,6 @@ describe Sodium::SecureBuffer do
buf.readwrite
end
it "allocates random" do
buf1 = Sodium::SecureBuffer.random 5
buf2 = Sodium::SecureBuffer.random 5
(buf1 == buf2).should be_false
buf1.wipe
end
it "copies and erases" do
bytes = Bytes.new(5) { 1_u8 }

View File

@ -20,40 +20,48 @@ detached_test_vectors = [
private def sign_from_vec(vec)
seckey = Sodium::Sign::SecretKey.new seed: vec[:seed].hexbytes
seckey.to_slice.should eq vec[:secret_key].hexbytes
seckey.key.readonly do |sslice|
sslice.should eq vec[:secret_key].hexbytes
end
seckey.public_key.to_slice.should eq vec[:public_key].hexbytes
plaintext = vec[:plaintext].hexbytes
signature = vec[:signature].hexbytes
{seckey, plaintext, signature}
end
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.to_slice, key1.public_key.to_slice
key1.to_slice.should eq key2.to_slice
key2 = key1.key.readonly do |kslice|
Sodium::Sign::SecretKey.new kslice, key1.public_key.to_slice
end
key1.key.should eq key2.key
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.to_slice
key1.to_slice.should eq key2.to_slice
key2 = key1.key.readonly do |kslice|
Sodium::Sign::SecretKey.new kslice
end
key1.key.should eq key2.key
key1.public_key.to_slice.should eq key2.public_key.to_slice
end
it "seed keys" do
it "loading seed -> key -> seed" do
seed = Bytes.new Sodium::Sign::SecretKey::SEED_SIZE
key1 = Sodium::Sign::SecretKey.new seed: seed
key2 = Sodium::Sign::SecretKey.new seed: Sodium::Sign::SecretKey.new(key1.to_slice).seed
key1.to_slice.should eq key2.to_slice
key2 = key1.key.readonly do |kslice|
Sodium::Sign::SecretKey.new kslice
end
key3 = Sodium::Sign::SecretKey.new seed: key2.seed
key1.key.should eq key2.key
key1.key.should eq key3.key
key1.public_key.to_slice.should eq key2.public_key.to_slice
key1.public_key.to_slice.should eq key3.public_key.to_slice
key1.seed.should eq seed
key1.seed.should eq key2.seed
key1.seed.should eq key3.seed
end
it "signs and verifies" do

View File

@ -12,7 +12,7 @@ module Sodium::Cipher::Aead
@key = SecureBuffer.random key_size
end
# Initializes with a reference to an existing ky.
# Initializes with a reference to an existing key.
def initialize(@key : SecureBuffer)
raise ArgumentError.new("key size mismatch, got #{@key.bytesize}, wanted #{key_size}") if @key.bytesize != key_size
@key.readonly
@ -30,6 +30,7 @@ module Sodium::Cipher::Aead
offset = src.bytesize
dst ||= Bytes.new (offset + mac_size)
mac = dst[offset, mac_size]
_, _, nonce = encrypt_detached src.to_slice, dst[0, offset], mac: mac, nonce: nonce, additional: additional
{dst, nonce}
end
@ -114,8 +115,8 @@ module Sodium::Cipher::Aead
ad_len = additional.try(&.bytesize) || 0
nonce.used!
@key.readonly do
r = LibSodium.crypto_aead{{ val.id }}_encrypt_detached(dst, mac, out mac_len, src, src.bytesize, additional, ad_len, nil, nonce.to_slice, @key.to_slice)
@key.readonly do |kslice|
r = LibSodium.crypto_aead{{ val.id }}_encrypt_detached(dst, mac, out mac_len, src, src.bytesize, additional, ad_len, nil, nonce.to_slice, kslice)
raise Sodium::Error.new("crypto_aead_{{ val.id }}_encrypt_detached") if r != 0
raise Sodium::Error.new("crypto_aead_{{ val.id }}_encrypt_detached mac size mismatch") if mac_len != MAC_SIZE
end
@ -134,8 +135,9 @@ module Sodium::Cipher::Aead
ad_len = additional.try(&.bytesize) || 0
r = @key.readonly do
LibSodium.crypto_aead{{ val.id }}_decrypt_detached(dst, nil, src, src.bytesize, mac, additional, ad_len, nonce.to_slice, @key.to_slice)
r = @key.readonly do |kslice|
LibSodium.crypto_aead{{ val.id }}_decrypt_detached(dst, nil, src, src.bytesize, mac, additional, ad_len, nonce.
to_slice, kslice)
end
raise Sodium::Error::DecryptionFailed.new("crypto_aead_{{ val.id }}_decrypt_detached") if r != 0
dst

View File

@ -6,27 +6,29 @@ module Sodium::Cipher
#
# What? They're both dance?
abstract class Chalsa
@key : Bytes | SecureBuffer | Nil
@nonce : Bytes?
getter key : Crypto::Secret
getter! nonce : Bytes?
# Advanced usage. Don't touch.
property offset = 0
getter! key
getter! nonce
def initialize
end
def initialize(key = nil, nonce = nil)
self.key = key if key
def initialize(key : Crypto::Secret | Bytes, nonce = nil)
raise ArgumentError.new("key must be #{key_size} bytes, got #{key.bytesize}") if key.bytesize != key_size
@key = key.is_a?(Crypto::Secret) ? key : Sodium::SecureBuffer.new(key)
# self.key = key if key
self.nonce = nonce if nonce
end
def key=(key : Bytes | SecureBuffer)
@[Deprecated("Use constructor to set key")]
def key=(key : Bytes | Crypto::Secret) : Crypto::Secret
raise ArgumentError.new("key must be #{key_size} bytes, got #{key.bytesize}") if key.bytesize != key_size
@key = key
key
@key = case key
in Crypto::Secret
key
in Bytes
Sodium::SecureBuffer.new key
end
@key.not_nil!
end
def nonce=(nonce : Bytes)
@ -35,6 +37,7 @@ module Sodium::Cipher
nonce
end
@[Deprecated("Use constructor to set key")]
def random_key
self.key = SecureBuffer.random key_size
end
@ -103,6 +106,10 @@ module Sodium::Cipher
KEY_SIZE = LibSodium.crypto_stream_chacha20_{{ ietf ? "ietf_".id : "".id }}keybytes.to_i32
NONCE_SIZE = LibSodium.crypto_stream_chacha20_{{ ietf ? "ietf_".id : "".id }}noncebytes.to_i32
def self.random
new key: Sodium::SecureBuffer.random(KEY_SIZE), nonce: Random::Secure.random_bytes(NONCE_SIZE)
end
# Xor's src with the cipher output and places in dst.
#
# src and dst may be the same object but should not overlap.

View File

@ -3,7 +3,7 @@ require "../secure_buffer"
module Sodium::Cipher
abstract class SecretStream
@state : SecureBuffer
@state : Crypto::Secret
@encrypt_decrypt = 0
@initialized = false
@ -17,7 +17,7 @@ module Sodium::Cipher
# * This property is set to nil after calling .update.
property additional : Bytes? = nil
@key : Bytes | SecureBuffer | Nil = nil
@key : Crypto::Secret | Nil = nil
def initialize
@state = SecureBuffer.new state_size
@ -31,15 +31,18 @@ module Sodium::Cipher
@encrypt_decrypt = -1
end
def key=(key : Bytes | SecureBuffer)
def key=(key : Bytes | Crypto::Secret) : Crypto::Secret
raise ArgumentError.new("key must be #{key_size} bytes, got #{key.bytesize}") if key.bytesize != key_size
key = key.is_a?(Crypto::Secret) ? key : Sodium::SecureBuffer.new(key)
@key = key
key
end
# Returns a random key in a SecureBuffer.
def random_key
self.key = SecureBuffer.random key_size
def random_key : Crypto::Secret
k = SecureBuffer.random key_size
self.key = k
k
end
# Only used for encryption.
@ -102,15 +105,21 @@ module Sodium::Cipher
case @encrypt_decrypt
when 1
if LibSodium.crypto_secretstream_{{ val.id }}_push(@state.to_slice, dst.to_slice, out dst_size, src, src.bytesize, ad, ad_size, @tag) != 0
raise Sodium::Error.new("crypto_streamsecret_{{ val.id }}_xor_ic")
dst_size = @state.readwrite do |stslice|
if LibSodium.crypto_secretstream_{{ val.id }}_push(stslice, dst.to_slice, out dsize, src, src.bytesize, ad, ad_size, @tag) != 0
raise Sodium::Error.new("crypto_streamsecret_{{ val.id }}_xor_ic")
end
dsize
end
@tag = 0
@additional = nil
dst[0, dst_size]
when -1
if LibSodium.crypto_secretstream_{{ val.id }}_pull(@state.to_slice, dst.to_slice, out dst_size2, out @tag, src, src.bytesize, ad, ad_size) != 0
raise Sodium::Error.new("crypto_streamsecret_{{ val.id }}_xor_ic")
dst_size2 = @state.readwrite do |stslice|
if LibSodium.crypto_secretstream_{{ val.id }}_pull(stslice, dst.to_slice, out dsize2, out @tag, src, src.bytesize, ad, ad_size) != 0
raise Sodium::Error.new("crypto_streamsecret_{{ val.id }}_xor_ic")
end
dsize2
end
@additional = nil
dst[0, dst_size2]
@ -123,19 +132,25 @@ module Sodium::Cipher
raise Sodium::Error.new("can't initialize more than once") if @initialized
if k = @key
case @encrypt_decrypt
when 1
if LibSodium.crypto_secretstream_xchacha20poly1305_init_push(@state.to_slice, header_buf.to_slice, k.to_slice) != 0
raise Sodium::Error.new("crypto_secretstream_xchacha20poly1305_init_push")
k.readonly do |kslice|
case @encrypt_decrypt
when 1
@state.readwrite do |stslice|
if LibSodium.crypto_secretstream_xchacha20poly1305_init_push(stslice, header_buf.to_slice, kslice) != 0
raise Sodium::Error.new("crypto_secretstream_xchacha20poly1305_init_push")
end
end
when -1
@state.readwrite do |stslice|
if LibSodium.crypto_secretstream_xchacha20poly1305_init_pull(stslice, header_buf.to_slice, kslice) != 0
raise Sodium::Error.new("crypto_secretstream_xchacha20poly1305_init_push")
end
end
when 0
raise Sodium::Error.new("must call .encrypt or .decrypt first")
else
abort "invalid encrypt_decrypt state #{@encrypt_decrypt}"
end
when -1
if LibSodium.crypto_secretstream_xchacha20poly1305_init_pull(@state.to_slice, header_buf.to_slice, k.to_slice) != 0
raise Sodium::Error.new("crypto_secretstream_xchacha20poly1305_init_push")
end
when 0
raise Sodium::Error.new("must call .encrypt or .decrypt first")
else
abort "invalid encrypt_decrypt state #{@encrypt_decrypt}"
end
else
raise Sodium::Error.new("must set an encryption/decryption key")

View File

@ -38,6 +38,8 @@ module Sodium
# Returns key
delegate_to_slice to: @sbuf
@sbuf : Crypto::Secret
# Use an existing KDF key.
#
# * Copies key to a new SecureBuffer
@ -50,8 +52,8 @@ module Sodium
@sbuf = SecureBuffer.new(bytes, erase).noaccess
end
# Use an existing KDF SecureBuffer key.
def initialize(@sbuf : SecureBuffer)
# Use an existing KDF Crypto::Secret key.
def initialize(@sbuf : Crypto::Secret)
if @sbuf.bytesize != KEY_SIZE
raise ArgumentError.new("bytes must be #{KEY_SIZE}, got #{sbuf.bytesize}")
end

View File

@ -25,7 +25,7 @@ module Sodium::Password
end
# :nodoc:
def derive_key(key : SecureBuffer, pass : Bytes | String, *, salt : Bytes? = nil) : Nil
def derive_key(key : Crypto::Secret, pass : Bytes | String, *, salt : Bytes? = nil) : Nil
m = mode || raise ArgumentError.new("mode not set")
salt ||= @salt
@ -33,8 +33,10 @@ module Sodium::Password
salt = salt.not_nil!
raise "salt expected #{SALT_SIZE} bytes, got #{salt.bytesize} " if salt.bytesize != SALT_SIZE
if LibSodium.crypto_pwhash(key.to_slice, key.bytesize, pass.to_slice, pass.bytesize, salt.to_slice, @ops, @mem, m) != 0
raise Sodium::Error.new("crypto_pwhash")
key.readwrite do |key_slice|
if LibSodium.crypto_pwhash(key_slice, key_slice.bytesize, pass.to_slice, pass.bytesize, salt.to_slice, @ops, @mem, m) != 0
raise Sodium::Error.new("crypto_pwhash")
end
end
end

View File

@ -19,19 +19,19 @@ module Sodium
NONCE_SIZE = LibSodium.crypto_secretbox_noncebytes.to_i
MAC_SIZE = LibSodium.crypto_secretbox_macbytes.to_i
# Returns key
@[Deprecated("Use `key.readonly` or `key.readwrite`")]
delegate_to_slice to: @key
# Encryption key
getter key : SecureBuffer
getter key : Crypto::Secret
# Generate a new random key held in a SecureBuffer.
def initialize
@key = SecureBuffer.random KEY_SIZE
end
# Use an existing SecureBuffer.
def initialize(@key : SecureBuffer)
# Use an existing Crypto::Secret.
def initialize(@key : Crypto::Secret)
if @key.bytesize != KEY_SIZE
raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{@key.bytesize}")
end
@ -91,8 +91,8 @@ module Sodium
dst ||= Bytes.new dst_size
raise ArgumentError.new("dst.bytesize must be src.bytesize - MAC_SIZE, got #{dst.bytesize}") if dst.bytesize != (src.bytesize - MAC_SIZE)
r = @key.readonly do
LibSodium.crypto_secretbox_open_easy(dst, src, src.bytesize, nonce.to_slice, @key)
r = @key.readonly do |kslice|
LibSodium.crypto_secretbox_open_easy(dst, src, src.bytesize, nonce.to_slice, kslice)
end
raise Sodium::Error::DecryptionFailed.new("crypto_secretbox_easy") if r != 0
dst

View File

@ -10,8 +10,6 @@ module Sodium
class SecureBuffer
include Crypto::Secret::Stateful
# @state = State::Readwrite
getter bytesize : Int32
def initialize(@bytesize : Int32)
@ -31,13 +29,13 @@ module Sodium
# :nodoc:
# For .dup
def initialize(sbuf : self)
def initialize(sbuf : Crypto::Secret)
initialize sbuf.bytesize
# Maybe not thread safe
sbuf.readonly do |s1|
self.to_slice do |s2|
s1.copy_to s2.to_slice
sbuf.readonly do |sslice|
readwrite do |dslice|
s1.copy_to s2
end
end
@ -63,8 +61,9 @@ module Sodium
Slice(UInt8).new @ptr, @bytesize
end
def to_slice(& : Bytes -> Nil)
yield Bytes.new @ptr, @bytesize
protected def to_slice(& : Bytes -> Nil)
ro = @state < State::Readonly
yield Bytes.new(@ptr, @bytesize, read_only: ro)
end
# :nodoc:
@ -72,11 +71,6 @@ module Sodium
@ptr
end
# WARNING: Not thread safe unless this object is readonly or readwrite
def dup
self.class.new self
end
protected def readwrite_impl : Nil
if LibSodium.sodium_mprotect_readwrite(@ptr) != 0
raise "sodium_mprotect_readwrite"

View File

@ -20,15 +20,20 @@ module Sodium
getter public_key : PublicKey
# Returns key
delegate_to_slice to: @sbuf
@[Deprecated("Switching to Crypto::Secret. Use `key.readonly` or `key.readwrite`")]
delegate_to_slice to: @key
getter key : Crypto::Secret
@seed : Crypto::Secret?
# Generates a new random secret/public key pair.
def initialize
@sbuf = SecureBuffer.new KEY_SIZE
@key = SecureBuffer.new KEY_SIZE
@public_key = PublicKey.new
if LibSodium.crypto_sign_keypair(@public_key.to_slice, self.to_slice) != 0
raise Sodium::Error.new("crypto_sign_keypair")
@key.readwrite do |kslice|
if LibSodium.crypto_sign_keypair(@public_key.to_slice, kslice) != 0
raise Sodium::Error.new("crypto_sign_keypair")
end
end
end
@ -38,13 +43,15 @@ module Sodium
def initialize(bytes : Bytes, pkey : Bytes? = nil, *, erase = false)
raise ArgumentError.new("Secret sign key must be #{KEY_SIZE}, got #{bytes.bytesize}") unless bytes.bytesize == KEY_SIZE
@sbuf = SecureBuffer.new bytes, erase: erase
@key = SecureBuffer.new bytes, erase: erase
if pk = pkey
@public_key = PublicKey.new pk
else
@public_key = PublicKey.new
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")
@key.readwrite do |kslice|
if LibSodium.crypto_sign_ed25519_sk_to_pk(@public_key.to_slice, kslice) != 0
raise Sodium::Error.new("crypto_sign_ed25519_sk_to_pk")
end
end
end
end
@ -61,17 +68,25 @@ module Sodium
raise ArgumentError.new("Secret sign seed must be #{SEED_SIZE}, got #{seed.bytesize}") unless seed.bytesize == SEED_SIZE
@seed = seed
@sbuf = SecureBuffer.new KEY_SIZE
@key = SecureBuffer.new KEY_SIZE
@public_key = PublicKey.new
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")
seed.readonly do |seed_slice|
@key.readwrite do |kslice|
if LibSodium.crypto_sign_seed_keypair(@public_key.to_slice, kslice, seed_slice) != 0
raise Sodium::Error.new("crypto_sign_seed_keypair")
end
end
end
end
getter seed : SecureBuffer? do
SecureBuffer.new(SEED_SIZE).tap do |s|
if LibSodium.crypto_sign_ed25519_sk_to_seed(s.to_slice, self.to_slice) != 0
raise Sodium::Error.new("crypto_sign_ed25519_sk_to_seed")
getter seed : Crypto::Secret? do
SecureBuffer.new(SEED_SIZE).tap do |seed_buf|
@key.readonly do |kslice|
seed_buf.readwrite do |seed_slice|
if LibSodium.crypto_sign_ed25519_sk_to_seed(seed_slice, kslice) != 0
raise Sodium::Error.new("crypto_sign_ed25519_sk_to_seed")
end
end
end
end.readonly
end
@ -84,17 +99,24 @@ module Sodium
def sign_detached(message : Bytes)
sig = Bytes.new(SIG_SIZE)
if LibSodium.crypto_sign_detached(sig, out sig_len, message, message.bytesize, self.to_slice) != 0
raise Error.new("crypto_sign_detached")
@key.readonly do |kslice|
if LibSodium.crypto_sign_detached(sig, out sig_len, message, message.bytesize, kslice) != 0
raise Error.new("crypto_sign_detached")
end
raise "expected #{sig.bytesize}, got #{sig_len}" if sig.bytesize != sig_len
end
sig
end
def to_curve25519 : CryptoBox::SecretKey
key = SecureBuffer.new CryptoBox::SecretKey::KEY_SIZE
LibSodium.crypto_sign_ed25519_sk_to_curve25519 key.to_slice, @sbuf.to_slice
key.readonly
CryptoBox::SecretKey.new key
sbuf = SecureBuffer.new CryptoBox::SecretKey::KEY_SIZE
sbuf.readwrite do |sbuf_slice|
@key.readonly do |kslice|
LibSodium.crypto_sign_ed25519_sk_to_curve25519 sbuf_slice, kslice
end
end
sbuf.readonly
CryptoBox::SecretKey.new sbuf
end
end
end