diff options
Diffstat (limited to 'spec/ruby/library/openssl')
19 files changed, 955 insertions, 36 deletions
diff --git a/spec/ruby/library/openssl/cipher_spec.rb b/spec/ruby/library/openssl/cipher_spec.rb index f66f25f9c6..91da1c592c 100644 --- a/spec/ruby/library/openssl/cipher_spec.rb +++ b/spec/ruby/library/openssl/cipher_spec.rb @@ -4,6 +4,6 @@ require 'openssl' describe "OpenSSL::Cipher's CipherError" do it "exists under OpenSSL::Cipher namespace" do - OpenSSL::Cipher.should have_constant :CipherError + OpenSSL::Cipher.should.const_defined?(:CipherError, false) end end diff --git a/spec/ruby/library/openssl/config/freeze_spec.rb b/spec/ruby/library/openssl/config/freeze_spec.rb deleted file mode 100644 index c814341b86..0000000000 --- a/spec/ruby/library/openssl/config/freeze_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require_relative '../../../spec_helper' -require_relative '../shared/constants' - -require 'openssl' - -version_is(OpenSSL::VERSION, ""..."2.2") do - describe "OpenSSL::Config#freeze" do - it "needs to be reviewed for completeness" - - it "freezes" do - c = OpenSSL::Config.new - -> { - c['foo'] = [ ['key', 'value'] ] - }.should_not raise_error - c.freeze - c.frozen?.should be_true - -> { - c['foo'] = [ ['key', 'value'] ] - }.should raise_error(TypeError) - end - end -end diff --git a/spec/ruby/library/openssl/digest/append_spec.rb b/spec/ruby/library/openssl/digest/append_spec.rb new file mode 100644 index 0000000000..08802b7253 --- /dev/null +++ b/spec/ruby/library/openssl/digest/append_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require_relative 'shared/update' + +describe "OpenSSL::Digest#<<" do + it_behaves_like :openssl_digest_update, :<< +end diff --git a/spec/ruby/library/openssl/digest/block_length_spec.rb b/spec/ruby/library/openssl/digest/block_length_spec.rb new file mode 100644 index 0000000000..444ed9d20d --- /dev/null +++ b/spec/ruby/library/openssl/digest/block_length_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe "OpenSSL::Digest#block_length" do + context "when the digest object is created via a name argument" do + it "returns a SHA1 block length" do + OpenSSL::Digest.new('sha1').block_length.should == SHA1Constants::BlockLength + end + + it "returns a SHA256 block length" do + OpenSSL::Digest.new('sha256').block_length.should == SHA256Constants::BlockLength + end + + it "returns a SHA384 block length" do + OpenSSL::Digest.new('sha384').block_length.should == SHA384Constants::BlockLength + end + + it "returns a SHA512 block length" do + OpenSSL::Digest.new('sha512').block_length.should == SHA512Constants::BlockLength + end + end + + context "when the digest object is created via a subclass" do + it "returns a SHA1 block length" do + OpenSSL::Digest::SHA1.new.block_length.should == SHA1Constants::BlockLength + end + + it "returns a SHA256 block length" do + OpenSSL::Digest::SHA256.new.block_length.should == SHA256Constants::BlockLength + end + + it "returns a SHA384 block length" do + OpenSSL::Digest::SHA384.new.block_length.should == SHA384Constants::BlockLength + end + + it "returns a SHA512 block length" do + OpenSSL::Digest::SHA512.new.block_length.should == SHA512Constants::BlockLength + end + end +end diff --git a/spec/ruby/library/openssl/digest/digest_length_spec.rb b/spec/ruby/library/openssl/digest/digest_length_spec.rb new file mode 100644 index 0000000000..37d1cba9a7 --- /dev/null +++ b/spec/ruby/library/openssl/digest/digest_length_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe "OpenSSL::Digest#digest_length" do + context "when the digest object is created via a name argument" do + it "returns a SHA1 digest length" do + OpenSSL::Digest.new('sha1').digest_length.should == SHA1Constants::DigestLength + end + + it "returns a SHA256 digest length" do + OpenSSL::Digest.new('sha256').digest_length.should == SHA256Constants::DigestLength + end + + it "returns a SHA384 digest length" do + OpenSSL::Digest.new('sha384').digest_length.should == SHA384Constants::DigestLength + end + + it "returns a SHA512 digest length" do + OpenSSL::Digest.new('sha512').digest_length.should == SHA512Constants::DigestLength + end + end + + context "when the digest object is created via a subclass" do + it "returns a SHA1 digest length" do + OpenSSL::Digest::SHA1.new.digest_length.should == SHA1Constants::DigestLength + end + + it "returns a SHA256 digest length" do + OpenSSL::Digest::SHA256.new.digest_length.should == SHA256Constants::DigestLength + end + + it "returns a SHA384 digest length" do + OpenSSL::Digest::SHA384.new.digest_length.should == SHA384Constants::DigestLength + end + + it "returns a SHA512 digest length" do + OpenSSL::Digest::SHA512.new.digest_length.should == SHA512Constants::DigestLength + end + end +end diff --git a/spec/ruby/library/openssl/digest_spec.rb b/spec/ruby/library/openssl/digest/digest_spec.rb index b8e82d073f..cf27d01b6d 100644 --- a/spec/ruby/library/openssl/digest_spec.rb +++ b/spec/ruby/library/openssl/digest/digest_spec.rb @@ -1,12 +1,11 @@ -require_relative '../../spec_helper' -require_relative '../../library/digest/sha1/shared/constants' -require_relative '../../library/digest/sha256/shared/constants' -require_relative '../../library/digest/sha384/shared/constants' -require_relative '../../library/digest/sha512/shared/constants' +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' require 'openssl' -describe "OpenSSL::Digest" do - +describe "OpenSSL::Digest class methods" do describe ".digest" do it "returns a SHA1 digest" do OpenSSL::Digest.digest('sha1', SHA1Constants::Contents).should == SHA1Constants::Digest diff --git a/spec/ruby/library/openssl/digest/initialize_spec.rb b/spec/ruby/library/openssl/digest/initialize_spec.rb new file mode 100644 index 0000000000..e24ab51d14 --- /dev/null +++ b/spec/ruby/library/openssl/digest/initialize_spec.rb @@ -0,0 +1,137 @@ +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe "OpenSSL::Digest#initialize" do + describe "can be called with a digest name" do + it "returns a SHA1 object" do + OpenSSL::Digest.new("sha1").name.should == "SHA1" + end + + it "returns a SHA256 object" do + OpenSSL::Digest.new("sha256").name.should == "SHA256" + end + + it "returns a SHA384 object" do + OpenSSL::Digest.new("sha384").name.should == "SHA384" + end + + it "returns a SHA512 object" do + OpenSSL::Digest.new("sha512").name.should == "SHA512" + end + + version_is OpenSSL::VERSION, "4.0.0" do + it "throws an error when called with an unknown digest" do + -> { OpenSSL::Digest.new("wd40") }.should.raise(OpenSSL::Digest::DigestError, /wd40/) + end + end + + it "cannot be called with a symbol" do + -> { OpenSSL::Digest.new(:SHA1) }.should.raise(TypeError) + end + end + + describe "can be called with a digest object" do + it "returns a SHA1 object" do + OpenSSL::Digest.new(OpenSSL::Digest::SHA1.new).name.should == "SHA1" + end + + it "returns a SHA256 object" do + OpenSSL::Digest.new(OpenSSL::Digest::SHA256.new).name.should == "SHA256" + end + + it "returns a SHA384 object" do + OpenSSL::Digest.new(OpenSSL::Digest::SHA384.new).name.should == "SHA384" + end + + it "returns a SHA512 object" do + OpenSSL::Digest.new(OpenSSL::Digest::SHA512.new).name.should == "SHA512" + end + + it "ignores the state of the digest object" do + sha1 = OpenSSL::Digest.new('sha1', SHA1Constants::Contents) + OpenSSL::Digest.new(sha1).digest.should == SHA1Constants::BlankDigest + end + end + + it "cannot be called with a digest class" do + -> { OpenSSL::Digest.new(OpenSSL::Digest::SHA1) }.should.raise(TypeError) + end + + context "when called without an initial String argument" do + it "returns a SHA1 digest" do + OpenSSL::Digest.new("sha1").digest.should == SHA1Constants::BlankDigest + end + + it "returns a SHA256 digest" do + OpenSSL::Digest.new("sha256").digest.should == SHA256Constants::BlankDigest + end + + it "returns a SHA384 digest" do + OpenSSL::Digest.new("sha384").digest.should == SHA384Constants::BlankDigest + end + + it "returns a SHA512 digest" do + OpenSSL::Digest.new("sha512").digest.should == SHA512Constants::BlankDigest + end + end + + context "when called with an initial String argument" do + it "returns a SHA1 digest of that argument" do + OpenSSL::Digest.new("sha1", SHA1Constants::Contents).digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest of that argument" do + OpenSSL::Digest.new("sha256", SHA256Constants::Contents).digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest of that argument" do + OpenSSL::Digest.new("sha384", SHA384Constants::Contents).digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest of that argument" do + OpenSSL::Digest.new("sha512", SHA512Constants::Contents).digest.should == SHA512Constants::Digest + end + end + + context "can be called on subclasses" do + describe "can be called without an initial String argument on subclasses" do + it "returns a SHA1 digest" do + OpenSSL::Digest::SHA1.new.digest.should == SHA1Constants::BlankDigest + end + + it "returns a SHA256 digest" do + OpenSSL::Digest::SHA256.new.digest.should == SHA256Constants::BlankDigest + end + + it "returns a SHA384 digest" do + OpenSSL::Digest::SHA384.new.digest.should == SHA384Constants::BlankDigest + end + + it "returns a SHA512 digest" do + OpenSSL::Digest::SHA512.new.digest.should == SHA512Constants::BlankDigest + end + end + + describe "can be called with an initial String argument on subclasses" do + it "returns a SHA1 digest" do + OpenSSL::Digest::SHA1.new(SHA1Constants::Contents).digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest" do + OpenSSL::Digest::SHA256.new(SHA256Constants::Contents).digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest" do + OpenSSL::Digest::SHA384.new(SHA384Constants::Contents).digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest" do + OpenSSL::Digest::SHA512.new(SHA512Constants::Contents).digest.should == SHA512Constants::Digest + end + end + end +end diff --git a/spec/ruby/library/openssl/digest/name_spec.rb b/spec/ruby/library/openssl/digest/name_spec.rb new file mode 100644 index 0000000000..b379f35c1c --- /dev/null +++ b/spec/ruby/library/openssl/digest/name_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../../spec_helper' +require 'openssl' + +describe "OpenSSL::Digest#name" do + it "returns the name of digest" do + OpenSSL::Digest.new('SHA1').name.should == 'SHA1' + end + + it "converts the name to the internal representation of OpenSSL" do + OpenSSL::Digest.new('sha1').name.should == 'SHA1' + end + + it "works on subclasses too" do + OpenSSL::Digest::SHA1.new.name.should == 'SHA1' + end +end diff --git a/spec/ruby/library/openssl/digest/reset_spec.rb b/spec/ruby/library/openssl/digest/reset_spec.rb new file mode 100644 index 0000000000..c19bf46633 --- /dev/null +++ b/spec/ruby/library/openssl/digest/reset_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe "OpenSSL::Digest#reset" do + it "works for a SHA1 digest" do + digest = OpenSSL::Digest.new('sha1', SHA1Constants::Contents) + digest.reset + digest.update(SHA1Constants::Contents) + digest.digest.should == SHA1Constants::Digest + end + + it "works for a SHA256 digest" do + digest = OpenSSL::Digest.new('sha256', SHA256Constants::Contents) + digest.reset + digest.update(SHA256Constants::Contents) + digest.digest.should == SHA256Constants::Digest + end + + it "works for a SHA384 digest" do + digest = OpenSSL::Digest.new('sha384', SHA384Constants::Contents) + digest.reset + digest.update(SHA384Constants::Contents) + digest.digest.should == SHA384Constants::Digest + end + + it "works for a SHA512 digest" do + digest = OpenSSL::Digest.new('sha512', SHA512Constants::Contents) + digest.reset + digest.update(SHA512Constants::Contents) + digest.digest.should == SHA512Constants::Digest + end +end diff --git a/spec/ruby/library/openssl/digest/shared/update.rb b/spec/ruby/library/openssl/digest/shared/update.rb new file mode 100644 index 0000000000..37277c945d --- /dev/null +++ b/spec/ruby/library/openssl/digest/shared/update.rb @@ -0,0 +1,123 @@ +require_relative '../../../../library/digest/sha1/shared/constants' +require_relative '../../../../library/digest/sha256/shared/constants' +require_relative '../../../../library/digest/sha384/shared/constants' +require_relative '../../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe :openssl_digest_update, shared: true do + context "when given input as a single string" do + it "returns a SHA1 digest" do + digest = OpenSSL::Digest.new('sha1') + digest.send(@method, SHA1Constants::Contents) + digest.digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest" do + digest = OpenSSL::Digest.new('sha256') + digest.send(@method, SHA256Constants::Contents) + digest.digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest" do + digest = OpenSSL::Digest.new('sha384') + digest.send(@method, SHA384Constants::Contents) + digest.digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest" do + digest = OpenSSL::Digest.new('sha512') + digest.send(@method, SHA512Constants::Contents) + digest.digest.should == SHA512Constants::Digest + end + end + + context "when given input as multiple smaller substrings" do + it "returns a SHA1 digest" do + digest = OpenSSL::Digest.new('sha1') + SHA1Constants::Contents.each_char { |b| digest.send(@method, b) } + digest.digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest" do + digest = OpenSSL::Digest.new('sha256') + SHA256Constants::Contents.each_char { |b| digest.send(@method, b) } + digest.digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest" do + digest = OpenSSL::Digest.new('sha384') + SHA384Constants::Contents.each_char { |b| digest.send(@method, b) } + digest.digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest" do + digest = OpenSSL::Digest.new('sha512') + SHA512Constants::Contents.each_char { |b| digest.send(@method, b) } + digest.digest.should == SHA512Constants::Digest + end + end + + context "when input is not a String and responds to #to_str" do + it "returns a SHA1 digest" do + str = mock('str') + str.should_receive(:to_str).and_return(SHA1Constants::Contents) + digest = OpenSSL::Digest.new('sha1') + digest.send(@method, str) + digest.digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest" do + str = mock('str') + str.should_receive(:to_str).and_return(SHA256Constants::Contents) + digest = OpenSSL::Digest.new('sha256') + digest.send(@method, str) + digest.digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest" do + str = mock('str') + str.should_receive(:to_str).and_return(SHA384Constants::Contents) + digest = OpenSSL::Digest.new('sha384') + digest.send(@method, str) + digest.digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest" do + str = mock('str') + str.should_receive(:to_str).and_return(SHA512Constants::Contents) + digest = OpenSSL::Digest.new('sha512') + digest.send(@method, str) + digest.digest.should == SHA512Constants::Digest + end + end + + context "when input is not a String and does not respond to #to_str" do + it "raises a TypeError with SHA1" do + digest = OpenSSL::Digest.new('sha1') + -> { + digest.send(@method, Object.new) + }.should.raise(TypeError, 'no implicit conversion of Object into String') + end + + it "raises a TypeError with SHA256" do + digest = OpenSSL::Digest.new('sha256') + -> { + digest.send(@method, Object.new) + }.should.raise(TypeError, 'no implicit conversion of Object into String') + end + + it "raises a TypeError with SHA384" do + digest = OpenSSL::Digest.new('sha384') + -> { + digest.send(@method, Object.new) + }.should.raise(TypeError, 'no implicit conversion of Object into String') + end + + it "raises a TypeError with SHA512" do + digest = OpenSSL::Digest.new('sha512') + -> { + digest.send(@method, Object.new) + }.should.raise(TypeError, 'no implicit conversion of Object into String') + end + end +end diff --git a/spec/ruby/library/openssl/digest/update_spec.rb b/spec/ruby/library/openssl/digest/update_spec.rb new file mode 100644 index 0000000000..3a90b06c6b --- /dev/null +++ b/spec/ruby/library/openssl/digest/update_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require_relative 'shared/update' + +describe "OpenSSL::Digest#update" do + it_behaves_like :openssl_digest_update, :update +end diff --git a/spec/ruby/library/openssl/fixed_length_secure_compare_spec.rb b/spec/ruby/library/openssl/fixed_length_secure_compare_spec.rb new file mode 100644 index 0000000000..cc638e1f0d --- /dev/null +++ b/spec/ruby/library/openssl/fixed_length_secure_compare_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' +require 'openssl' + +describe "OpenSSL.fixed_length_secure_compare" do + it "returns true for two strings with the same content" do + input1 = "the quick brown fox jumps over the lazy dog" + input2 = "the quick brown fox jumps over the lazy dog" + OpenSSL.fixed_length_secure_compare(input1, input2).should == true + end + + it "returns false for two strings of equal size with different content" do + input1 = "the quick brown fox jumps over the lazy dog" + input2 = "the lazy dog jumps over the quick brown fox" + OpenSSL.fixed_length_secure_compare(input1, input2).should == false + end + + it "converts both arguments to strings using #to_str" do + input1 = mock("input1") + input1.should_receive(:to_str).and_return("the quick brown fox jumps over the lazy dog") + input2 = mock("input2") + input2.should_receive(:to_str).and_return("the quick brown fox jumps over the lazy dog") + OpenSSL.fixed_length_secure_compare(input1, input2).should == true + end + + it "does not accept arguments that are not string and cannot be coerced into strings" do + -> { + OpenSSL.fixed_length_secure_compare("input1", :input2) + }.should.raise(TypeError, 'no implicit conversion of Symbol into String') + + -> { + OpenSSL.fixed_length_secure_compare(Object.new, "input2") + }.should.raise(TypeError, 'no implicit conversion of Object into String') + end + + it "raises an ArgumentError for two strings of different size" do + input1 = "the quick brown fox jumps over the lazy dog" + input2 = "the quick brown fox" + -> { + OpenSSL.fixed_length_secure_compare(input1, input2) + }.should.raise(ArgumentError, 'inputs must be of equal length') + end +end diff --git a/spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb b/spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb new file mode 100644 index 0000000000..2558dbb9ec --- /dev/null +++ b/spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb @@ -0,0 +1,162 @@ +require_relative '../../../spec_helper' +require 'openssl' + +describe "OpenSSL::KDF.pbkdf2_hmac" do + before :each do + @defaults = { + salt: "\x00".b * 16, + iterations: 20_000, + length: 16, + hash: "sha1" + } + end + + it "creates the same value with the same input" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "supports nullbytes embedded in the password" do + key = OpenSSL::KDF.pbkdf2_hmac("sec\x00ret".b, **@defaults) + key.should == "\xB9\x7F\xB0\xC2\th\xC8<\x86\xF3\x94Ij7\xEF\xF1".b + end + + it "coerces the password into a String using #to_str" do + pass = mock("pass") + pass.should_receive(:to_str).and_return("secret") + key = OpenSSL::KDF.pbkdf2_hmac(pass, **@defaults) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "coerces the salt into a String using #to_str" do + salt = mock("salt") + salt.should_receive(:to_str).and_return("\x00".b * 16) + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, salt: salt) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "coerces the iterations into an Integer using #to_int" do + iterations = mock("iterations") + iterations.should_receive(:to_int).and_return(20_000) + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: iterations) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "coerces the length into an Integer using #to_int" do + length = mock("length") + length.should_receive(:to_int).and_return(16) + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: length) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "accepts a OpenSSL::Digest object as hash" do + hash = OpenSSL::Digest.new("sha1") + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: hash) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "accepts an empty password" do + key = OpenSSL::KDF.pbkdf2_hmac("", **@defaults) + key.should == "k\x9F-\xB1\xF7\x9A\v\xA1(C\xF9\x85!P\xEF\x8C".b + end + + it "accepts an empty salt" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, salt: "") + key.should == "\xD5f\xE5\xEA\xF91\x1D\xD3evD\xED\xDB\xE80\x80".b + end + + it "accepts an empty length" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: 0) + key.should.empty? + end + + it "accepts an arbitrary length" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: 19) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0\xCF\xBB\x7F".b + end + + it "accepts any hash function known to OpenSSL" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: "sha512") + key.should == "N\x12}D\xCE\x99\xDBC\x8E\xEC\xAAr\xEA1\xDF\xFF".b + end + + it "raises a TypeError when password is not a String and does not respond to #to_str" do + -> { + OpenSSL::KDF.pbkdf2_hmac(Object.new, **@defaults) + }.should.raise(TypeError, "no implicit conversion of Object into String") + end + + it "raises a TypeError when salt is not a String and does not respond to #to_str" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, salt: Object.new) + }.should.raise(TypeError, "no implicit conversion of Object into String") + end + + it "raises a TypeError when iterations is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: Object.new) + }.should.raise(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when length is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: Object.new) + }.should.raise(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when hash is neither a String nor an OpenSSL::Digest" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: Object.new) + }.should.raise(TypeError) + end + + version_is OpenSSL::VERSION, "4.0.0" do + it "raises a OpenSSL::Digest::DigestError for unknown digest algorithms" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: "wd40") + }.should.raise(OpenSSL::Digest::DigestError, /wd40/) + end + end + + it "treats salt as a required keyword" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:salt)) + }.should.raise(ArgumentError, 'missing keyword: :salt') + end + + it "treats iterations as a required keyword" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:iterations)) + }.should.raise(ArgumentError, 'missing keyword: :iterations') + end + + it "treats length as a required keyword" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:length)) + }.should.raise(ArgumentError, 'missing keyword: :length') + end + + it "treats hash as a required keyword" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:hash)) + }.should.raise(ArgumentError, 'missing keyword: :hash') + end + + it "treats all keywords as required" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret") + }.should.raise(ArgumentError, 'missing keywords: :salt, :iterations, :length, :hash') + end + + guard -> { OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000 } do + it "raises an OpenSSL::KDF::KDFError for 0 or less iterations" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: 0) + }.should.raise(OpenSSL::KDF::KDFError, "PKCS5_PBKDF2_HMAC: invalid iteration count") + + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: -1) + }.should.raise(OpenSSL::KDF::KDFError, /PKCS5_PBKDF2_HMAC/) + end + end +end diff --git a/spec/ruby/library/openssl/kdf/scrypt_spec.rb b/spec/ruby/library/openssl/kdf/scrypt_spec.rb new file mode 100644 index 0000000000..c00f91bb5b --- /dev/null +++ b/spec/ruby/library/openssl/kdf/scrypt_spec.rb @@ -0,0 +1,210 @@ +require_relative '../../../spec_helper' +require 'openssl' + +# LibreSSL seems not to support scrypt +guard -> { OpenSSL::OPENSSL_VERSION.start_with?('OpenSSL') and OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10100000 } do + describe "OpenSSL::KDF.scrypt" do + before :each do + @defaults = { + salt: "\x00".b * 16, + N: 2**14, + r: 8, + p: 1, + length: 32 + } + end + + it "creates the same value with the same input" do + key = OpenSSL::KDF.scrypt("secret", **@defaults) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "supports nullbytes embedded into the password" do + key = OpenSSL::KDF.scrypt("sec\x00ret".b, **@defaults) + key.should == "\xF9\xA4\xA0\xF1p\xF4\xF0\xCAT\xB4v\xEB\r7\x88N\xF7\x15]Ns\xFCwt4a\xC9\xC6\xA7\x13\x81&".b + end + + it "coerces the password into a String using #to_str" do + pass = mock("pass") + pass.should_receive(:to_str).and_return("secret") + key = OpenSSL::KDF.scrypt(pass, **@defaults) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the salt into a String using #to_str" do + salt = mock("salt") + salt.should_receive(:to_str).and_return("\x00".b * 16) + key = OpenSSL::KDF.scrypt("secret", **@defaults, salt: salt) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the N into an Integer using #to_int" do + n = mock("N") + n.should_receive(:to_int).and_return(2**14) + key = OpenSSL::KDF.scrypt("secret", **@defaults, N: n) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the r into an Integer using #to_int" do + r = mock("r") + r.should_receive(:to_int).and_return(8) + key = OpenSSL::KDF.scrypt("secret", **@defaults, r: r) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the p into an Integer using #to_int" do + p = mock("p") + p.should_receive(:to_int).and_return(1) + key = OpenSSL::KDF.scrypt("secret", **@defaults, p: p) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the length into an Integer using #to_int" do + length = mock("length") + length.should_receive(:to_int).and_return(32) + key = OpenSSL::KDF.scrypt("secret", **@defaults, length: length) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "accepts an empty password" do + key = OpenSSL::KDF.scrypt("", **@defaults) + key.should == "\xAA\xFC\xF5^E\x94v\xFFk\xE6\xF0vR\xE7\x13\xA7\xF5\x15'\x9A\xE4C\x9Dn\x18F_E\xD2\v\e\xB3".b + end + + it "accepts an empty salt" do + key = OpenSSL::KDF.scrypt("secret", **@defaults, salt: "") + key.should == "\x96\xACDl\xCB3/aN\xB0F\x8A#\xD7\x92\xD2O\x1E\v\xBB\xCE\xC0\xAA\xB9\x0F]\xB09\xEA8\xDD\e".b + end + + it "accepts a zero length" do + key = OpenSSL::KDF.scrypt("secret", **@defaults, length: 0) + key.should.empty? + end + + it "accepts an arbitrary length" do + key = OpenSSL::KDF.scrypt("secret", **@defaults, length: 19) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D".b + end + + it "raises a TypeError when password is not a String and does not respond to #to_str" do + -> { + OpenSSL::KDF.scrypt(Object.new, **@defaults) + }.should.raise(TypeError, "no implicit conversion of Object into String") + end + + it "raises a TypeError when salt is not a String and does not respond to #to_str" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, salt: Object.new) + }.should.raise(TypeError, "no implicit conversion of Object into String") + end + + it "raises a TypeError when N is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: Object.new) + }.should.raise(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when r is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, r: Object.new) + }.should.raise(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when p is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, p: Object.new) + }.should.raise(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when length is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, length: Object.new) + }.should.raise(TypeError, "no implicit conversion of Object into Integer") + end + + it "treats salt as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:salt)) + }.should.raise(ArgumentError, 'missing keyword: :salt') + end + + it "treats N as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:N)) + }.should.raise(ArgumentError, 'missing keyword: :N') + end + + it "treats r as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:r)) + }.should.raise(ArgumentError, 'missing keyword: :r') + end + + it "treats p as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:p)) + }.should.raise(ArgumentError, 'missing keyword: :p') + end + + it "treats length as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:length)) + }.should.raise(ArgumentError, 'missing keyword: :length') + end + + it "treats all keywords as required" do + -> { + OpenSSL::KDF.scrypt("secret") + }.should.raise(ArgumentError, 'missing keywords: :salt, :N, :r, :p, :length') + end + + it "requires N to be a power of 2" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: 2**14 - 1) + }.should.raise(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + end + + it "requires N to be at least 2" do + key = OpenSSL::KDF.scrypt("secret", **@defaults, N: 2) + key.should == "\x06A$a\xA9!\xBE\x01\x85\xA7\x18\xBCEa\x82\xC5\xFEl\x93\xAB\xBD\xF7\x8B\x84\v\xFC\eN\xEBQ\xE6\xD2".b + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: 1) + }.should.raise(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: 0) + }.should.raise(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: -1) + }.should.raise(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + end + + it "requires r to be positive" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, r: 0) + }.should.raise(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, r: -1) + }.should.raise(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + end + + it "requires p to be positive" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, p: 0) + }.should.raise(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, p: -1) + }.should.raise(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + end + + it "requires length to be not negative" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, length: -1) + }.should.raise(ArgumentError, "negative string size (or size too big)") + end + end +end diff --git a/spec/ruby/library/openssl/random/shared/random_bytes.rb b/spec/ruby/library/openssl/random/shared/random_bytes.rb index 037f10d409..4d1751e57e 100644 --- a/spec/ruby/library/openssl/random/shared/random_bytes.rb +++ b/spec/ruby/library/openssl/random/shared/random_bytes.rb @@ -1,11 +1,11 @@ require_relative '../../../../spec_helper' require 'openssl' -describe :openssl_random_bytes, shared: true do |cmd| +describe :openssl_random_bytes, shared: true do it "generates a random binary string of specified length" do (1..64).each do |idx| bytes = OpenSSL::Random.send(@method, idx) - bytes.should be_kind_of(String) + bytes.should.is_a?(String) bytes.length.should == idx end end @@ -24,6 +24,6 @@ describe :openssl_random_bytes, shared: true do |cmd| it "raises ArgumentError on negative arguments" do -> { OpenSSL::Random.send(@method, -1) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end end diff --git a/spec/ruby/library/openssl/secure_compare_spec.rb b/spec/ruby/library/openssl/secure_compare_spec.rb new file mode 100644 index 0000000000..ce2a4a0d43 --- /dev/null +++ b/spec/ruby/library/openssl/secure_compare_spec.rb @@ -0,0 +1,38 @@ +require_relative '../../spec_helper' +require 'openssl' + +describe "OpenSSL.secure_compare" do + it "returns true for two strings with the same content" do + input1 = "the quick brown fox jumps over the lazy dog" + input2 = "the quick brown fox jumps over the lazy dog" + OpenSSL.secure_compare(input1, input2).should == true + end + + it "returns false for two strings with different content" do + input1 = "the quick brown fox jumps over the lazy dog" + input2 = "the lazy dog jumps over the quick brown fox" + OpenSSL.secure_compare(input1, input2).should == false + end + + it "converts both arguments to strings using #to_str, but adds equality check for the original objects" do + input1 = mock("input1") + input1.should_receive(:to_str).and_return("the quick brown fox jumps over the lazy dog") + input2 = mock("input2") + input2.should_receive(:to_str).and_return("the quick brown fox jumps over the lazy dog") + OpenSSL.secure_compare(input1, input2).should == false + + input = mock("input") + input.should_receive(:to_str).twice.and_return("the quick brown fox jumps over the lazy dog") + OpenSSL.secure_compare(input, input).should == true + end + + it "does not accept arguments that are not string and cannot be coerced into strings" do + -> { + OpenSSL.secure_compare("input1", :input2) + }.should.raise(TypeError, 'no implicit conversion of Symbol into String') + + -> { + OpenSSL.secure_compare(Object.new, "input2") + }.should.raise(TypeError, 'no implicit conversion of Object into String') + end +end diff --git a/spec/ruby/library/openssl/shared/constants.rb b/spec/ruby/library/openssl/shared/constants.rb index 0bed4156a1..836f75011b 100644 --- a/spec/ruby/library/openssl/shared/constants.rb +++ b/spec/ruby/library/openssl/shared/constants.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary module HMACConstants Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." diff --git a/spec/ruby/library/openssl/x509/name/parse_spec.rb b/spec/ruby/library/openssl/x509/name/parse_spec.rb index 6624161d83..84e3d442f6 100644 --- a/spec/ruby/library/openssl/x509/name/parse_spec.rb +++ b/spec/ruby/library/openssl/x509/name/parse_spec.rb @@ -37,12 +37,12 @@ describe "OpenSSL::X509::Name.parse" do it "raises TypeError if the given string contains no key/value pairs" do -> do OpenSSL::X509::Name.parse("hello") - end.should raise_error(TypeError) + end.should.raise(TypeError) end it "raises OpenSSL::X509::NameError if the given string contains invalid keys" do -> do OpenSSL::X509::Name.parse("hello=goodbye") - end.should raise_error(OpenSSL::X509::NameError) + end.should.raise(OpenSSL::X509::NameError) end end diff --git a/spec/ruby/library/openssl/x509/store/verify_spec.rb b/spec/ruby/library/openssl/x509/store/verify_spec.rb new file mode 100644 index 0000000000..6a6a53d992 --- /dev/null +++ b/spec/ruby/library/openssl/x509/store/verify_spec.rb @@ -0,0 +1,78 @@ +require_relative '../../../../spec_helper' +require 'openssl' + +describe "OpenSSL::X509::Store#verify" do + it "returns true for valid certificate" do + key = OpenSSL::PKey::RSA.new 2048 + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 1 + cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=truffleruby/CN=TruffleRuby CA" + cert.issuer = cert.subject + cert.public_key = key.public_key + cert.not_before = Time.now - 10 + cert.not_after = cert.not_before + 365 * 24 * 60 * 60 + cert.sign key, OpenSSL::Digest.new('SHA256') + store = OpenSSL::X509::Store.new + store.add_cert(cert) + [store.verify(cert), store.error, store.error_string].should == [true, 0, "ok"] + end + + it "returns false for an expired certificate" do + key = OpenSSL::PKey::RSA.new 2048 + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 1 + cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=truffleruby/CN=TruffleRuby CA" + cert.issuer = cert.subject + cert.public_key = key.public_key + cert.not_before = Time.now - 10 + cert.not_after = Time.now - 5 + cert.sign key, OpenSSL::Digest.new('SHA256') + store = OpenSSL::X509::Store.new + store.add_cert(cert) + store.verify(cert).should == false + end + + it "returns false for an expired root certificate" do + root_key = OpenSSL::PKey::RSA.new 2048 + root_cert = OpenSSL::X509::Certificate.new + root_cert.version = 2 + root_cert.serial = 1 + root_cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=truffleruby/CN=TruffleRuby CA" + root_cert.issuer = root_cert.subject + root_cert.public_key = root_key.public_key + root_cert.not_before = Time.now - 10 + root_cert.not_after = Time.now - 5 + ef = OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = root_cert + ef.issuer_certificate = root_cert + root_cert.add_extension(ef.create_extension("basicConstraints","CA:TRUE",true)) + root_cert.add_extension(ef.create_extension("keyUsage","keyCertSign, cRLSign", true)) + root_cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false)) + root_cert.add_extension(ef.create_extension("authorityKeyIdentifier","keyid:always",false)) + root_cert.sign(root_key, OpenSSL::Digest.new('SHA256')) + + + key = OpenSSL::PKey::RSA.new 2048 + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 2 + cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=truffleruby/CN=TruffleRuby certificate" + cert.issuer = root_cert.subject + cert.public_key = key.public_key + cert.not_before = Time.now + cert.not_after = cert.not_before + 1 * 365 * 24 * 60 * 60 + ef = OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = cert + ef.issuer_certificate = root_cert + cert.add_extension(ef.create_extension("keyUsage","digitalSignature", true)) + cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false)) + cert.sign(root_key, OpenSSL::Digest.new('SHA256')) + + store = OpenSSL::X509::Store.new + store.add_cert(root_cert) + store.add_cert(cert) + store.verify(cert).should == false + end +end |
