Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cipher: allow setting IV length when using AEAD ciphers #58

Merged
merged 2 commits into from
Jul 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
Version x.y.z
=============

This is the first release of openssl gem, formerly a standard library of Ruby,
ext/openssl. This is the successor of the version included in Ruby 2.3.


Backward compatibility notes
----------------------------

* Support for OpenSSL version 0.9.6 and 0.9.7 is completely removed. openssl gem
still works with OpenSSL 0.9.8, but users are strongly encouraged to upgrade
to at least 1.0.1, as OpenSSL < 1.0.1 will not receive any security fixes from
the OpenSSL development team.

* OpenSSL::Cipher#key= and #iv= reject too long inputs. They used to truncate
silently. [Bug #12561]

* OpenSSL::PKey::RSA#n=, #e=, #d=, #p=, #q=, #dmp1=, #dmq1=, #iqmp=,
OpenSSL::PKey::DSA#p=, #q=, #g=, #priv_key=, #pub_key=,
OpenSSL::PKey::DH#p=, #g=, #priv_key= and #pub_key= are deprecated. They are
disabled when built with OpenSSL 1.1.0, due to its API change. Instead,
OpenSSL::PKey::RSA#set_key, #set_factors, #set_crt_params,
OpenSSL::PKey::DSA#set_pqg, #set_key, OpenSSL::PKey::DH#set_pqg and #set_key
are added.

* OpenSSL::Random.pseudo_bytes is deprecated, and not defined when built with
OpenSSL 1.1.0. Use OpenSSL::Random.random_bytes instead.

* OpenSSL::SSL::SSLContext#tmp_ecdh_callback is deprecated. To specify the curve
to be used in ephemeral ECDH, use OpenSSL::SSL::SSLContext#ecdh_curves=. The
automatic curve selection is also now enabled by default when built with a
capable OpenSSL.

* RC4 cipher suites are removed from OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.
RC4 is now considered to be weak. [GH ruby/openssl#50]

Updates since Ruby 2.3
----------------------

* Supported platform

- Supports OpenSSL 1.1.0. [Feature #12324]

- OpenSSL < 0.9.8 is no longer supported.

* OpenSSL::Cipher

- OpenSSL::Cipher#key= and #iv= reject too long inputs. They used to truncate
silently. [Bug #12561]

- OpenSSL::Cipher#iv_len= is added. It allows changing IV (nonce) length if
using AEAD ciphers.
[Bug #8667] [Bug #10420] [GH ruby/ruby#569] [GH ruby/openssl#58]

* OpenSSL::Engine

- OpenSSL::Engine.cleanup does nothing when built with OpenSSL 1.1.0.

* OpenSSL::OCSP

- Accessor methods are added to OpenSSL::OCSP::CertificateId. [Feature #7181]

- OpenSSL::OCSP::Request and BasicResponse can be signed with non-SHA-1 hash
algorithm. [Feature #11552]

- OpenSSL::OCSP::CertificateId and BasicResponse can be encoded into DER.

- A new class OpenSSL::OCSP::SingleResponse is added for convenience.

- OpenSSL::OCSP::BasicResponse#add_status accepts absolute times. They used to
accept only relative seconds from the current time.

* OpenSSL::PKey

- OpenSSL::PKey::EC follows the general PKey interface. [Bug #6567]

- OpenSSL::PKey::RSA#n=, #e=, #d=, #p=, #q=, #dmp1=, #dmq1=, #iqmp=,
OpenSSL::PKey::DSA#p=, #q=, #g=, #priv_key=, #pub_key=,
OpenSSL::PKey::DH#p=, #g=, #priv_key= and #pub_key= are deprecated. They are
disabled when built with OpenSSL 1.1.0, due to its API change. Instead,
OpenSSL::PKey::RSA#set_key, #set_factors, #set_crt_params,
OpenSSL::PKey::DSA#set_pqg, #set_key, OpenSSL::PKey::DH#set_pqg and #set_key
are added.

* OpenSSL::Random

- OpenSSL::Random.pseudo_bytes is deprecated, and not defined when built with
OpenSSL 1.1.0. Use OpenSSL::Random.random_bytes instead.

* OpenSSL::SSL

- OpenSSL::PKey::DH::DEFAULT_512 is removed. Hence servers no longer use
512-bit DH group by default. It is considered too weak nowadays.
[Bug #11968] [GH ruby/ruby#1196]

- OpenSSL::SSL::SSLSocket#tmp_key is added. A client can call it after the
connection is established to retrieve the ephemeral key. [GH ruby/ruby#1318]

- The automatic ephemeral ECDH curve selection is enabled by default when
built with OpenSSL >= 1.0.2 or LibreSSL.

- OpenSSL::SSL::SSLContext#tmp_ecdh_callback is deprecated, as the underlying
API SSL_CTX_set_tmp_ecdh_callback() is removed in OpenSSL 1.1.0. It was
first added in Ruby 2.3.0.

- OpenSSL::SSL::SSLContext#security_level= is added. You can set the "security
level" of the SSL context. This is effective only when built with OpenSSL
1.1.0.

- RC4 cipher suites are removed from OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.
RC4 is now considered to be weak. [GH ruby/openssl#50]
1 change: 1 addition & 0 deletions ext/openssl/openssl_missing.h
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ IMPL_PKEY_GETTER(EC_KEY, ec)
#if defined(HAVE_AUTHENTICATED_ENCRYPTION) && !defined(EVP_CTRL_AEAD_GET_TAG)
# define EVP_CTRL_AEAD_GET_TAG EVP_CTRL_GCM_GET_TAG
# define EVP_CTRL_AEAD_SET_TAG EVP_CTRL_GCM_SET_TAG
# define EVP_CTRL_AEAD_SET_IVLEN EVP_CTRL_GCM_SET_IVLEN
#endif

#endif /* _OSSL_OPENSSL_MISSING_H_ */
60 changes: 57 additions & 3 deletions ext/openssl/ossl_cipher.c
Original file line number Diff line number Diff line change
Expand Up @@ -499,12 +499,17 @@ static VALUE
ossl_cipher_set_iv(VALUE self, VALUE iv)
{
EVP_CIPHER_CTX *ctx;
int iv_len;
int iv_len = 0;

StringValue(iv);
GetCipher(self, ctx);

iv_len = EVP_CIPHER_CTX_iv_length(ctx);
#if defined(HAVE_AUTHENTICATED_ENCRYPTION)
if (EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER)
iv_len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx);
#endif
if (!iv_len)
iv_len = EVP_CIPHER_CTX_iv_length(ctx);
if (RSTRING_LEN(iv) != iv_len)
ossl_raise(rb_eArgError, "iv must be %d bytes", iv_len);

Expand Down Expand Up @@ -638,11 +643,42 @@ ossl_cipher_is_authenticated(VALUE self)

return (EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER) ? Qtrue : Qfalse;
}

/*
* call-seq:
* cipher.iv_len = integer -> integer
*
* Sets the IV/nonce length of the Cipher. Normally block ciphers don't allow
* changing the IV length, but some make use of IV for 'nonce'. You may need
* this for interoperability with other applications.
*/
static VALUE
ossl_cipher_set_iv_length(VALUE self, VALUE iv_length)
{
int len = NUM2INT(iv_length);
EVP_CIPHER_CTX *ctx;

GetCipher(self, ctx);
if (!(EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER))
ossl_raise(eCipherError, "cipher does not support AEAD");

if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, len, NULL))
ossl_raise(eCipherError, "unable to set IV length");

/*
* EVP_CIPHER_CTX_iv_length() returns the default length. So we need to save
* the length somewhere. Luckily currently we aren't using app_data.
*/
EVP_CIPHER_CTX_set_app_data(ctx, (void *)(VALUE)len);

return iv_length;
}
#else
#define ossl_cipher_set_auth_data rb_f_notimplement
#define ossl_cipher_get_auth_tag rb_f_notimplement
#define ossl_cipher_set_auth_tag rb_f_notimplement
#define ossl_cipher_is_authenticated rb_f_notimplement
#define ossl_cipher_set_iv_length rb_f_notimplement
#endif

/*
Expand Down Expand Up @@ -708,13 +744,30 @@ ossl_cipher_set_padding(VALUE self, VALUE padding)
* Returns the key length in bytes of the Cipher.
*/
CIPHER_0ARG_INT(key_length)

/*
* call-seq:
* cipher.iv_len -> integer
*
* Returns the expected length in bytes for an IV for this Cipher.
*/
CIPHER_0ARG_INT(iv_length)
static VALUE
ossl_cipher_iv_length(VALUE self)
{
EVP_CIPHER_CTX *ctx;
int len = 0;

GetCipher(self, ctx);
#if defined(HAVE_AUTHENTICATED_ENCRYPTION)
if (EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER)
len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx);
#endif
if (!len)
len = EVP_CIPHER_CTX_iv_length(ctx);

return INT2NUM(len);
}

/*
* call-seq:
* cipher.block_size -> integer
Expand Down Expand Up @@ -952,6 +1005,7 @@ Init_ossl_cipher(void)
rb_define_method(cCipher, "key_len=", ossl_cipher_set_key_length, 1);
rb_define_method(cCipher, "key_len", ossl_cipher_key_length, 0);
rb_define_method(cCipher, "iv=", ossl_cipher_set_iv, 1);
rb_define_method(cCipher, "iv_len=", ossl_cipher_set_iv_length, 1);
rb_define_method(cCipher, "iv_len", ossl_cipher_iv_length, 0);
rb_define_method(cCipher, "block_size", ossl_cipher_block_size, 0);
rb_define_method(cCipher, "padding=", ossl_cipher_set_padding, 1);
Expand Down
40 changes: 40 additions & 0 deletions test/test_cipher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,46 @@ def test_aes_gcm_wrong_ciphertext
end
end

def test_aes_gcm_variable_iv_len
pt = "You should all use Authenticated Encryption!"
cipher = OpenSSL::Cipher.new("aes-128-gcm").encrypt
cipher.key = "x" * 16
assert_equal(12, cipher.iv_len)
cipher.iv = "a" * 12
ct1 = cipher.update(pt) << cipher.final
tag1 = cipher.auth_tag

cipher = OpenSSL::Cipher.new("aes-128-gcm").encrypt
cipher.key = "x" * 16
cipher.iv_len = 10
assert_equal(10, cipher.iv_len)
cipher.iv = "a" * 10
ct2 = cipher.update(pt) << cipher.final
tag2 = cipher.auth_tag

assert_not_equal ct1, ct2
assert_not_equal tag1, tag2

decipher = OpenSSL::Cipher.new("aes-128-gcm").decrypt
decipher.auth_tag = tag1
decipher.key = "x" * 16
decipher.iv_len = 12
decipher.iv = "a" * 12
assert_equal(pt, decipher.update(ct1) << decipher.final)

decipher.reset
decipher.auth_tag = tag2
assert_raise(OpenSSL::Cipher::CipherError) {
decipher.update(ct2) << decipher.final
}

decipher.reset
decipher.auth_tag = tag2
decipher.iv_len = 10
decipher.iv = "a" * 10
assert_equal(pt, decipher.update(ct2) << decipher.final)
end

end

private
Expand Down