Skip to content

Commit

Permalink
Merge 0742fd9 into 4536bf0
Browse files Browse the repository at this point in the history
  • Loading branch information
Nazarii Sheremet committed Dec 14, 2017
2 parents 4536bf0 + 0742fd9 commit 4459b03
Show file tree
Hide file tree
Showing 10 changed files with 330 additions and 41 deletions.
53 changes: 45 additions & 8 deletions lib/rbnacl/hmac/sha256.rb
Expand Up @@ -22,23 +22,60 @@ class SHA256 < Auth
sodium_constant :BYTES
sodium_constant :KEYBYTES

sodium_function :auth_hmacsha256,
:crypto_auth_hmacsha256,
%i[pointer pointer ulong_long pointer]
sodium_function :auth_hmacsha256_init,
:crypto_auth_hmacsha256_init,
%i[pointer pointer size_t]

sodium_function :auth_hmacsha256_verify,
:crypto_auth_hmacsha256_verify,
%i[pointer pointer ulong_long pointer]
sodium_function :auth_hmacsha256_update,
:crypto_auth_hmacsha256_update,
%i[pointer pointer ulong_long]

sodium_function :auth_hmacsha256_final,
:crypto_auth_hmacsha256_final,
%i[pointer pointer]

# Create instance without checking key length
#
# RFC 2104 HMAC
# The key for HMAC can be of any length.
#
# see https://tools.ietf.org/html/rfc2104#section-3
def initialize(key)
@key = Util.check_hmac_key(key, "#{self.class} key")
end

private

def compute_authenticator(authenticator, message)
self.class.auth_hmacsha256(authenticator, message, message.bytesize, key)
state = State.new

self.class.auth_hmacsha256_init(state, key, key.bytesize)
self.class.auth_hmacsha256_update(state, message, message.bytesize)
self.class.auth_hmacsha256_final(state, authenticator)
end

# libsodium crypto_auth_hmacsha256_verify works only for 32 byte keys
# ref: https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_auth/hmacsha256/auth_hmacsha256.c#L109
def verify_message(authenticator, message)
self.class.auth_hmacsha256_verify(authenticator, message, message.bytesize, key)
correct = Util.zeros(BYTES)
compute_authenticator(correct, message)
Util.verify32(correct, authenticator)
end
end

# The crupto_auth_hmacsha256_state struct representation
# ref: jedisct1/libsodium/src/libsodium/include/sodium/crypto_auth_hmacsha256.h
class SHA256State < FFI::Struct
layout :state, [:uint32, 8],
:count, :uint64,
:buf, [:uint8, 64]
end

# The crypto_hash_sha256_state struct representation
# ref: jedisct1/libsodium/src/libsodium/include/sodium/crypto_hash_sha256.h
class State < FFI::Struct
layout :ictx, SHA256State,
:octx, SHA256State
end
end
end
53 changes: 45 additions & 8 deletions lib/rbnacl/hmac/sha512.rb
Expand Up @@ -22,23 +22,60 @@ class SHA512 < Auth
sodium_constant :BYTES
sodium_constant :KEYBYTES

sodium_function :auth_hmacsha512,
:crypto_auth_hmacsha512,
%i[pointer pointer ulong_long pointer]
sodium_function :auth_hmacsha512_init,
:crypto_auth_hmacsha512_init,
%i[pointer pointer size_t]

sodium_function :auth_hmacsha512_verify,
:crypto_auth_hmacsha512_verify,
%i[pointer pointer ulong_long pointer]
sodium_function :auth_hmacsha512_update,
:crypto_auth_hmacsha512_update,
%i[pointer pointer ulong_long]

sodium_function :auth_hmacsha512_final,
:crypto_auth_hmacsha512_final,
%i[pointer pointer]

# Create instance without checking key length
#
# RFC 2104 HMAC
# The key for HMAC can be of any length.
#
# see https://tools.ietf.org/html/rfc2104#section-3
def initialize(key)
@key = Util.check_hmac_key(key, "#{self.class} key")
end

private

def compute_authenticator(authenticator, message)
self.class.auth_hmacsha512(authenticator, message, message.bytesize, key)
state = State.new

self.class.auth_hmacsha512_init(state, key, key.bytesize)
self.class.auth_hmacsha512_update(state, message, message.bytesize)
self.class.auth_hmacsha512_final(state, authenticator)
end

# libsodium crypto_auth_hmacsha512_verify works only for 32 byte keys
# ref: https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_auth/hmacsha512/auth_hmacsha512.c#L109
def verify_message(authenticator, message)
self.class.auth_hmacsha512_verify(authenticator, message, message.bytesize, key)
correct = Util.zeros(BYTES)
compute_authenticator(correct, message)
Util.verify64(correct, authenticator)
end
end

# The crypto_auth_hmacsha512_state struct representation
# ref: jedisct1/libsodium/src/libsodium/include/sodium/crypto_auth_hmacsha512.h
class SHA512State < FFI::Struct
layout :state, [:uint64, 8],
:count, [:uint64, 2],
:buf, [:uint8, 128]
end

# The crypto_hash_sha512_state struct representation
# ref: jedisct1/libsodium/src/libsodium/include/sodium/crypto_hash_sha512.h
class State < FFI::Struct
layout :ictx, SHA512State,
:octx, SHA512State
end
end
end
17 changes: 17 additions & 0 deletions lib/rbnacl/test_vectors.rb
Expand Up @@ -163,6 +163,23 @@ module RbNaCl
auth_hmacsha512: "b2a31b8d4e01afcab2ee545b5caf4e3d212a99d7b3a116a97cec8e83c32e107d" \
"270e3921f69016c267a63ab4b226449a0dee0dc7dcb897a9bce9d27d788f8e8d",

# HMAC-SHA Identifiers and Test Vectors
# ref: https://tools.ietf.org/html/rfc4231#section-4.8
#
auth_hmac_key: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaa",
auth_hmac_data: "5468697320697320612074657374207573696e672061206c6172676572207468" \
"616e20626c6f636b2d73697a65206b657920616e642061206c61726765722074" \
"68616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565" \
"647320746f20626520686173686564206265666f7265206265696e6720757365" \
"642062792074686520484d414320616c676f726974686d2e",
auth_hmacsha256_tag: "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2",
auth_hmacsha512_tag: "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944" \
"b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58",

# AEAD ChaCha20-Poly1305 original implementation test vectors
# Taken from https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04
aead_chacha20poly1305_orig_key: "4290bcb154173531f314af57f3be3b5006da371ece272afa1b5dbdd1100a1007",
Expand Down
88 changes: 86 additions & 2 deletions lib/rbnacl/util.rb
Expand Up @@ -8,6 +8,7 @@ module Util

sodium_function :c_verify16, :crypto_verify_16, %i[pointer pointer]
sodium_function :c_verify32, :crypto_verify_32, %i[pointer pointer]
sodium_function :c_verify64, :crypto_verify_64, %i[pointer pointer]

module_function

Expand Down Expand Up @@ -81,6 +82,8 @@ def zero_pad(n, message)
# @param description [String] Description of the string (used in the error)
def check_length(string, length, description)
if string.nil?
# code below is runs only in test cases
# nil can't be converted to str with #to_str method
raise LengthError,
"#{description} was nil (Expected #{length.to_int})",
caller
Expand All @@ -106,6 +109,57 @@ def check_length(string, length, description)
# @param length [Integer] The only acceptable length of the string
# @param description [String] Description of the string (used in the error)
def check_string(string, length, description)
check_string_validation(string)
string = string.to_s
check_length(string, length, description)

string
end

# Check a passed in string, convertion if necessary
#
# This method will check the key, and raise error
# if argument is not a string, and if it's empty string.
#
# RFC 2104 HMAC
# The key for HMAC can be of any length (keys longer than B bytes are
# first hashed using H). However, less than L bytes is strongly
# discouraged as it would decrease the security strength of the
# function. Keys longer than L bytes are acceptable but the extra
# length would not significantly increase the function strength. (A
# longer key may be advisable if the randomness of the key is
# considered weak.)
#
# see https://tools.ietf.org/html/rfc2104#section-3
#
#
# @raise [ArgumentError] If we cannot convert to a string with #to_str
# @raise [RbNaCl::LengthError] If the string is empty
#
# @param string [#to_str] The input string
def check_hmac_key(string, _description)
check_string_validation(string)

string = string.to_str

if string.bytesize.zero?
raise LengthError,
"#{Description} was #{string.bytesize} bytes (Expected more than 0)",
caller
end

string
end

# Check a passed string is it valid
#
# Raise an error if passed argument is invalid
#
# @raise [TypeError] If string cannot convert to a string with #to_str
# @raise [EncodingError] If string have wrong encoding
#
# @param string [#to_str] The input string
def check_string_validation(string)
unless string.respond_to? :to_str
raise TypeError, "can't convert #{string.class} into String with #to_str"
end
Expand All @@ -114,9 +168,39 @@ def check_string(string, length, description)
unless string.encoding == Encoding::BINARY
raise EncodingError, "strings must use BINARY encoding (got #{string.encoding})"
end
check_length(string, length, description)
end

string
# Compare two 64 byte strings in constant time
#
# This should help to avoid timing attacks for string comparisons in your
# application. Note that many of the functions (such as HmacSha512#verify)
# use this method under the hood already.
#
# @param [String] one String #1
# @param [String] two String #2
#
# @return [Boolean] Well, are they equal?
def verify64(one, two)
return false unless two.bytesize == 64 && one.bytesize == 64
c_verify64(one, two)
end

# Compare two 64 byte strings in constant time
#
# This should help to avoid timing attacks for string comparisons in your
# application. Note that many of the functions (such as HmacSha512#verify)
# use this method under the hood already.
#
# @param [String] one String #1
# @param [String] two String #2
#
# @raise [ArgumentError] If the strings are not equal in length
#
# @return [Boolean] Well, are they equal?
def verify64!(one, two)
check_length(one, 64, "First message")
check_length(two, 64, "Second message")
c_verify64(one, two)
end

# Compare two 32 byte strings in constant time
Expand Down
22 changes: 21 additions & 1 deletion spec/rbnacl/authenticators/poly1305_spec.rb
Expand Up @@ -2,7 +2,27 @@
# frozen_string_literal: true

RSpec.describe RbNaCl::OneTimeAuth do
let(:tag) { vector :auth_onetime }
let(:key) { vector "auth_key_#{described_class.key_bytes}".to_sym }
let(:message) { vector :auth_message }
let(:tag) { vector :auth_onetime }

context ".new" do
it "raises ArgumentError on a key which is too long" do
expect { described_class.new("\0" * described_class.key_bytes.succ) }.to raise_error(ArgumentError)
end
end

context ".auth" do
it "raises ArgumentError on a key which is too long" do
expect { described_class.auth("\0" * described_class.key_bytes.succ, message) }.to raise_error(ArgumentError)
end
end

context ".verify" do
it "raises ArgumentError on a key which is too long" do
expect { described_class.verify("\0" * described_class.key_bytes.succ, tag, message) }.to raise_error(ArgumentError)
end
end

include_examples "authenticator"
end
23 changes: 22 additions & 1 deletion spec/rbnacl/hmac/sha256_spec.rb
Expand Up @@ -2,7 +2,28 @@
# frozen_string_literal: true

RSpec.describe RbNaCl::HMAC::SHA256 do
let(:tag) { vector :auth_hmacsha256 }
let(:key) { vector :auth_hmac_key }
let(:message) { vector :auth_hmac_data }
let(:tag) { vector :auth_hmacsha256_tag }
let(:wrong_key) { "key".encode("utf-8") }

context ".new" do
it "raises EncodingError on a key with wrong encoding" do
expect { described_class.new(wrong_key) }.to raise_error(EncodingError)
end
end

context ".auth" do
it "raises EncodingError on a key with wrong encoding " do
expect { described_class.auth(wrong_key, message) }.to raise_error(EncodingError)
end
end

context ".verify" do
it "raises EncodingError on a key with wrong encoding" do
expect { described_class.verify(wrong_key, tag, message) }.to raise_error(EncodingError)
end
end

include_examples "authenticator"
end
22 changes: 21 additions & 1 deletion spec/rbnacl/hmac/sha512256_spec.rb
Expand Up @@ -2,7 +2,27 @@
# frozen_string_literal: true

RSpec.describe RbNaCl::HMAC::SHA512256 do
let(:tag) { vector :auth_hmacsha512256 }
let(:key) { vector "auth_key_#{described_class.key_bytes}".to_sym }
let(:message) { vector :auth_message }
let(:tag) { vector :auth_hmacsha512256 }

context ".new" do
it "raises ArgumentError on a key which is too long" do
expect { described_class.new("\0" * described_class.key_bytes.succ) }.to raise_error(ArgumentError)
end
end

context ".auth" do
it "raises ArgumentError on a key which is too long" do
expect { described_class.auth("\0" * described_class.key_bytes.succ, message) }.to raise_error(ArgumentError)
end
end

context ".verify" do
it "raises ArgumentError on a key which is too long" do
expect { described_class.verify("\0" * described_class.key_bytes.succ, tag, message) }.to raise_error(ArgumentError)
end
end

include_examples "authenticator"
end
23 changes: 22 additions & 1 deletion spec/rbnacl/hmac/sha512_spec.rb
Expand Up @@ -2,7 +2,28 @@
# frozen_string_literal: true

RSpec.describe RbNaCl::HMAC::SHA512 do
let(:tag) { vector :auth_hmacsha512 }
let(:key) { vector :auth_hmac_key }
let(:message) { vector :auth_hmac_data }
let(:tag) { vector :auth_hmacsha512_tag }
let(:wrong_key) { "key".encode("utf-8") }

context ".new" do
it "raises EncodingError on a key with wrong encoding" do
expect { described_class.new(wrong_key) }.to raise_error(EncodingError)
end
end

context ".auth" do
it "raises EncodingError on a key with wrong encoding " do
expect { described_class.auth(wrong_key, message) }.to raise_error(EncodingError)
end
end

context ".verify" do
it "raises EncodingError on a key with wrong encoding" do
expect { described_class.verify(wrong_key, tag, message) }.to raise_error(EncodingError)
end
end

include_examples "authenticator"
end

0 comments on commit 4459b03

Please sign in to comment.