From 54a3cd8a8acad0de2c2c596fb62a5b0bb70aa7a9 Mon Sep 17 00:00:00 2001 From: Didactic Drunk <1479616+didactic-drunk@users.noreply.github.com> Date: Tue, 6 Aug 2019 14:30:16 -0700 Subject: [PATCH] Sodium::Nonce reuse detection. --- spec/sodium/crypto_box/secret_key_spec.cr | 14 +++++++++++ spec/sodium/secret_box_spec.cr | 11 +++++++++ src/sodium/crypto_box.cr | 3 ++- src/sodium/lib_sodium.cr | 2 ++ src/sodium/nonce.cr | 29 +++++++++++++++++++---- src/sodium/secret_box.cr | 3 ++- 6 files changed, 56 insertions(+), 6 deletions(-) diff --git a/spec/sodium/crypto_box/secret_key_spec.cr b/spec/sodium/crypto_box/secret_key_spec.cr index f1323a3..743c57a 100644 --- a/spec/sodium/crypto_box/secret_key_spec.cr +++ b/spec/sodium/crypto_box/secret_key_spec.cr @@ -99,6 +99,20 @@ describe Sodium::CryptoBox::SecretKey do String.new(decrypted).should eq(data) end + it "can't encrypt twice using the same nonce" do + data = "Hello World!".to_slice + + alice = Sodium::CryptoBox::SecretKey.new + bob = Sodium::CryptoBox::SecretKey.new + + alice.box bob.public_key do |box| + encrypted, nonce = box.encrypt data + expect_raises Sodium::Nonce::Error::Reused do + box.encrypt data, nonce: nonce + end + end + end + it "PyNaCl combined test vectors" do combined_test_vectors.each do |vec| box_from_vec(vec) do |box1, box2, nonce, plaintext, ciphertext| diff --git a/spec/sodium/secret_box_spec.cr b/spec/sodium/secret_box_spec.cr index eb71975..0f662ee 100644 --- a/spec/sodium/secret_box_spec.cr +++ b/spec/sodium/secret_box_spec.cr @@ -40,6 +40,17 @@ describe Sodium::SecretBox do end end + it "can't encrypt twice using the same nonce" do + box = Sodium::SecretBox.new + + message = "foobar" + encrypted, nonce = box.encrypt message + + expect_raises(Sodium::Nonce::Error::Reused) do + box.encrypt message.to_slice, nonce: nonce + end + end + it "PyNaCl combined test vectors" do combined_test_vectors.each do |vec| box, nonce, plaintext, ciphertext = box_from_test_vector vec diff --git a/src/sodium/crypto_box.cr b/src/sodium/crypto_box.cr index b00f55d..8a10996 100644 --- a/src/sodium/crypto_box.cr +++ b/src/sodium/crypto_box.cr @@ -35,7 +35,8 @@ module Sodium # Encrypts data and returns {ciphertext, nonce} # # Optionally supply a destination buffer. - def encrypt(src : Bytes, dst = Bytes.new(src.bytesize + MAC_SIZE), nonce = Nonce.new) : {Bytes, Nonce} + def encrypt(src : Bytes, dst = Bytes.new(src.bytesize + MAC_SIZE), nonce = Nonce.random) : {Bytes, Nonce} + nonce.used! if LibSodium.crypto_box_easy_afternm(dst, src, src.bytesize, nonce.to_slice, @key.to_slice) != 0 raise Error.new("crypto_box_easy") end diff --git a/src/sodium/lib_sodium.cr b/src/sodium/lib_sodium.cr index 0965a3c..d782f36 100644 --- a/src/sodium/lib_sodium.cr +++ b/src/sodium/lib_sodium.cr @@ -49,6 +49,8 @@ module Sodium fun sodium_memcmp(Pointer(LibC::UChar), Pointer(LibC::UChar), LibC::SizeT) : LibC::Int fun sodium_memzero(Pointer(LibC::UChar), LibC::SizeT) : Nil + fun sodium_increment(Pointer(LibC::UChar), LibC::SizeT) : Nil + fun sodium_malloc(LibC::SizeT) : Pointer(LibC::UChar) fun sodium_free(Pointer(LibC::UChar)) : Nil diff --git a/src/sodium/nonce.cr b/src/sodium/nonce.cr index cded6c8..6b6c7fe 100644 --- a/src/sodium/nonce.cr +++ b/src/sodium/nonce.cr @@ -3,9 +3,16 @@ require "random/secure" module Sodium class Nonce - NONCE_SIZE = LibSodium::NONCE_SIZE + class Error < Sodium::Error + class Reused < Error + end + end + + NONCE_SIZE = LibSodium::NONCE_SIZE.to_i + + getter? used + @used = false - getter bytes : Bytes delegate to_slice, to: @bytes def initialize(@bytes : Bytes) @@ -14,8 +21,22 @@ module Sodium end end - def initialize - @bytes = Random::Secure.random_bytes(NONCE_SIZE) + def self.random + self.new Random::Secure.random_bytes(NONCE_SIZE) + end + + def self.zero + self.new Bytes.new(NONCE_SIZE) + end + + def increment + LibSodium.sodium_increment @bytes, @bytes.bytesize + @used = false + end + + def used! + raise Error::Reused.new("attempted nonce reuse") if @used + @used = true end end end diff --git a/src/sodium/secret_box.cr b/src/sodium/secret_box.cr index b91dd2f..628b3c7 100644 --- a/src/sodium/secret_box.cr +++ b/src/sodium/secret_box.cr @@ -54,10 +54,11 @@ module Sodium # Encrypts data and returns {ciphertext, nonce} # # Optionally supply a destination buffer. - def encrypt(src : Bytes, dst : Bytes = Bytes.new(src.bytesize + MAC_SIZE), nonce : Nonce = Nonce.new) : {Bytes, Nonce} + def encrypt(src : Bytes, dst : Bytes = Bytes.new(src.bytesize + MAC_SIZE), nonce : Nonce = Nonce.random) : {Bytes, Nonce} if dst.bytesize != (src.bytesize + MAC_SIZE) raise ArgumentError.new("dst.bytesize must be src.bytesize + MAC_SIZE, got #{dst.bytesize}") end + nonce.used! if LibSodium.crypto_secretbox_easy(dst, src, src.bytesize, nonce.to_slice, self.to_slice) != 0 raise Sodium::Error.new("crypto_secretbox_easy") end