Skip to content

Commit

Permalink
cipher: don't set dummy encryption key in Cipher#initialize
Browse files Browse the repository at this point in the history
Remove the encryption key initialization from Cipher#initialize. This
is effectively a revert of r32723 ("Avoid possible SEGV from AES
encryption/decryption", 2011-07-28).

r32723, which added the key initialization, was a workaround for
Ruby Bug #2768. For some certain ciphers, calling EVP_CipherUpdate()
before setting an encryption key caused segfault. It was not a problem
until OpenSSL implemented GCM mode - the encryption key could be
overridden by repeated calls of EVP_CipherInit_ex(). But, it is not the
case for AES-GCM ciphers. Setting a key, an IV, a key, in this order
causes the IV to be reset to an all-zero IV.

The problem of Bug #2768 persists on the current versions of OpenSSL.
So, make Cipher#update raise an exception if a key is not yet set by the
user. Since encrypting or decrypting without key does not make any
sense, this should not break existing applications.

Users can still call Cipher#key= and Cipher#iv= multiple times with
their own responsibility.

Reference: https://bugs.ruby-lang.org/issues/2768
Reference: https://bugs.ruby-lang.org/issues/8221
Reference: #49
  • Loading branch information
rhenium committed Sep 28, 2016
1 parent 4eda408 commit 8108e0a
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 18 deletions.
25 changes: 13 additions & 12 deletions ext/openssl/ossl_cipher.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
*/
VALUE cCipher;
VALUE eCipherError;
static ID id_auth_tag_len;
static ID id_auth_tag_len, id_key_set;

static VALUE ossl_cipher_alloc(VALUE klass);
static void ossl_cipher_free(void *ptr);
Expand Down Expand Up @@ -118,7 +118,6 @@ ossl_cipher_initialize(VALUE self, VALUE str)
EVP_CIPHER_CTX *ctx;
const EVP_CIPHER *cipher;
char *name;
unsigned char dummy_key[EVP_MAX_KEY_LENGTH] = { 0 };

name = StringValueCStr(str);
GetCipherInit(self, ctx);
Expand All @@ -129,16 +128,7 @@ ossl_cipher_initialize(VALUE self, VALUE str)
if (!(cipher = EVP_get_cipherbyname(name))) {
ossl_raise(rb_eRuntimeError, "unsupported cipher algorithm (%"PRIsVALUE")", str);
}
/*
* EVP_CipherInit_ex() allows to specify NULL to key and IV, however some
* ciphers don't handle well (OpenSSL's bug). [Bug #2768]
*
* The EVP which has EVP_CIPH_RAND_KEY flag (such as DES3) allows
* uninitialized key, but other EVPs (such as AES) does not allow it.
* Calling EVP_CipherUpdate() without initializing key causes SEGV so we
* set the data filled with "\0" as the key by default.
*/
if (EVP_CipherInit_ex(ctx, cipher, NULL, dummy_key, NULL, -1) != 1)
if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1)
ossl_raise(eCipherError, NULL);

return self;
Expand Down Expand Up @@ -251,6 +241,9 @@ ossl_cipher_init(int argc, VALUE *argv, VALUE self, int mode)
ossl_raise(eCipherError, NULL);
}

if (p_key)
rb_ivar_set(self, id_key_set, Qtrue);

return self;
}

Expand Down Expand Up @@ -337,6 +330,8 @@ ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self)
OPENSSL_cleanse(key, sizeof key);
OPENSSL_cleanse(iv, sizeof iv);

rb_ivar_set(self, id_key_set, Qtrue);

return Qnil;
}

Expand Down Expand Up @@ -387,6 +382,9 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self)

rb_scan_args(argc, argv, "11", &data, &str);

if (!RTEST(rb_attr_get(self, id_key_set)))
ossl_raise(eCipherError, "key not set");

StringValue(data);
in = (unsigned char *)RSTRING_PTR(data);
if ((in_len = RSTRING_LEN(data)) == 0)
Expand Down Expand Up @@ -488,6 +486,8 @@ ossl_cipher_set_key(VALUE self, VALUE key)
if (EVP_CipherInit_ex(ctx, NULL, NULL, (unsigned char *)RSTRING_PTR(key), NULL, -1) != 1)
ossl_raise(eCipherError, NULL);

rb_ivar_set(self, id_key_set, Qtrue);

return key;
}

Expand Down Expand Up @@ -1082,4 +1082,5 @@ Init_ossl_cipher(void)
rb_define_method(cCipher, "padding=", ossl_cipher_set_padding, 1);

id_auth_tag_len = rb_intern_const("auth_tag_len");
id_key_set = rb_intern_const("key_set");
}
29 changes: 23 additions & 6 deletions test/test_cipher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def test_key_iv_set

def test_empty_data
@c1.encrypt
@c1.random_key
assert_raise(ArgumentError){ @c1.update("") }
end

Expand Down Expand Up @@ -136,12 +137,10 @@ def test_AES
}
end

def test_AES_crush
500.times do
assert_nothing_raised("[Bug #2768]") do
# it caused OpenSSL SEGV by uninitialized key
OpenSSL::Cipher::AES128.new("ECB").update "." * 17
end
def test_update_raise_if_key_not_set
assert_raise(OpenSSL::Cipher::CipherError) do
# it caused OpenSSL SEGV by uninitialized key [Bug #2768]
OpenSSL::Cipher::AES128.new("ECB").update "." * 17
end
end

Expand Down Expand Up @@ -317,6 +316,24 @@ def test_aes_ocb_tag_len
}
end if has_cipher?("aes-128-ocb")

def test_aes_gcm_key_iv_order_issue
pt = "[ruby/openssl#49]"
cipher = OpenSSL::Cipher.new("aes-128-gcm").encrypt
cipher.key = "x" * 16
cipher.iv = "a" * 12
ct1 = cipher.update(pt) << cipher.final
tag1 = cipher.auth_tag

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

assert_equal ct1, ct2
assert_equal tag1, tag2
end if has_cipher?("aes-128-gcm")

private

def new_encryptor(algo)
Expand Down

0 comments on commit 8108e0a

Please sign in to comment.