Skip to content

Commit

Permalink
Add OpenSSL.memcmp? for constant time/timing safe string comparison
Browse files Browse the repository at this point in the history
  • Loading branch information
bdewater committed Aug 20, 2019
1 parent 505dae9 commit 4169467
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 0 deletions.
36 changes: 36 additions & 0 deletions ext/openssl/ossl.c
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,41 @@ static void Init_ossl_locks(void)
}
#endif /* !HAVE_OPENSSL_110_THREADING_API */

/*
* call-seq:
* OpenSSL.memcmp?(string, string) -> boolean
*
* Constant time memory comparison. Inputs must be of equal length, otherwise
* an error is raised since timing attacks could leak the length of a
* secret.
*
* For securely comparing user input, it's recommended to use hashing and
* regularly compare after to prevent an unlikely false positive due to a
* collision.
*
* user_input = "..."
* expected = "..."
* hashed_input = OpenSSL::Digest::SHA256.digest(user_input)
* hashed_expected = OpenSSL::Digest::SHA256.digest(expected)
* OpenSSL.memcmp?(hashed_input, hashed_expected) && user_input == expected
*/
static VALUE
ossl_crypto_memcmp(VALUE dummy, VALUE str1, VALUE str2)
{
const unsigned char *p1 = (const unsigned char *)StringValuePtr(str1);
const unsigned char *p2 = (const unsigned char *)StringValuePtr(str2);
long len1 = RSTRING_LEN(str1);
long len2 = RSTRING_LEN(str2);

if(len1 != len2)
ossl_raise(rb_eArgError, "inputs must be of equal length");

switch (CRYPTO_memcmp(p1, p2, len1)) {
case 0: return Qtrue;
default: return Qfalse;
}
}

/*
* OpenSSL provides SSL, TLS and general purpose cryptography. It wraps the
* OpenSSL[https://www.openssl.org/] library.
Expand Down Expand Up @@ -1125,6 +1160,7 @@ Init_openssl(void)
*/
mOSSL = rb_define_module("OpenSSL");
rb_global_variable(&mOSSL);
rb_define_singleton_method(mOSSL, "memcmp?", ossl_crypto_memcmp, 2);

/*
* OpenSSL ruby extension version
Expand Down
28 changes: 28 additions & 0 deletions test/test_ossl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true
require_relative "utils"

if defined?(OpenSSL)

class OpenSSL::OSSL < OpenSSL::SSLTestCase
def test_memcmp?
assert_raises(ArgumentError) { OpenSSL.memcmp?("aaa", "a") }
assert_raises(ArgumentError) { OpenSSL.memcmp?("aaa", "aa") }

assert OpenSSL.memcmp?("aaa", "aaa")
assert OpenSSL.memcmp?(
OpenSSL::Digest::SHA256.digest("aaa"), OpenSSL::Digest::SHA256.digest("aaa")
)

assert_raises(ArgumentError) { OpenSSL.memcmp?("aaa", "aaaa") }
refute OpenSSL.memcmp?("aaa", "baa")
refute OpenSSL.memcmp?("aaa", "aba")
refute OpenSSL.memcmp?("aaa", "aab")
assert_raises(ArgumentError) { OpenSSL.memcmp?("aaa", "aaab") }
assert_raises(ArgumentError) { OpenSSL.memcmp?("aaa", "b") }
assert_raises(ArgumentError) { OpenSSL.memcmp?("aaa", "bb") }
refute OpenSSL.memcmp?("aaa", "bbb")
assert_raises(ArgumentError) { OpenSSL.memcmp?("aaa", "bbbb") }
end
end

end

0 comments on commit 4169467

Please sign in to comment.