Skip to content

Commit 8108e0a

Browse files
committed
cipher: don't set dummy encryption key in Cipher#initialize
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
1 parent 4eda408 commit 8108e0a

File tree

2 files changed

+36
-18
lines changed

2 files changed

+36
-18
lines changed

ext/openssl/ossl_cipher.c

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
*/
3737
VALUE cCipher;
3838
VALUE eCipherError;
39-
static ID id_auth_tag_len;
39+
static ID id_auth_tag_len, id_key_set;
4040

4141
static VALUE ossl_cipher_alloc(VALUE klass);
4242
static void ossl_cipher_free(void *ptr);
@@ -118,7 +118,6 @@ ossl_cipher_initialize(VALUE self, VALUE str)
118118
EVP_CIPHER_CTX *ctx;
119119
const EVP_CIPHER *cipher;
120120
char *name;
121-
unsigned char dummy_key[EVP_MAX_KEY_LENGTH] = { 0 };
122121

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

144134
return self;
@@ -251,6 +241,9 @@ ossl_cipher_init(int argc, VALUE *argv, VALUE self, int mode)
251241
ossl_raise(eCipherError, NULL);
252242
}
253243

244+
if (p_key)
245+
rb_ivar_set(self, id_key_set, Qtrue);
246+
254247
return self;
255248
}
256249

@@ -337,6 +330,8 @@ ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self)
337330
OPENSSL_cleanse(key, sizeof key);
338331
OPENSSL_cleanse(iv, sizeof iv);
339332

333+
rb_ivar_set(self, id_key_set, Qtrue);
334+
340335
return Qnil;
341336
}
342337

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

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

385+
if (!RTEST(rb_attr_get(self, id_key_set)))
386+
ossl_raise(eCipherError, "key not set");
387+
390388
StringValue(data);
391389
in = (unsigned char *)RSTRING_PTR(data);
392390
if ((in_len = RSTRING_LEN(data)) == 0)
@@ -488,6 +486,8 @@ ossl_cipher_set_key(VALUE self, VALUE key)
488486
if (EVP_CipherInit_ex(ctx, NULL, NULL, (unsigned char *)RSTRING_PTR(key), NULL, -1) != 1)
489487
ossl_raise(eCipherError, NULL);
490488

489+
rb_ivar_set(self, id_key_set, Qtrue);
490+
491491
return key;
492492
}
493493

@@ -1082,4 +1082,5 @@ Init_ossl_cipher(void)
10821082
rb_define_method(cCipher, "padding=", ossl_cipher_set_padding, 1);
10831083

10841084
id_auth_tag_len = rb_intern_const("auth_tag_len");
1085+
id_key_set = rb_intern_const("key_set");
10851086
}

test/test_cipher.rb

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def test_key_iv_set
9090

9191
def test_empty_data
9292
@c1.encrypt
93+
@c1.random_key
9394
assert_raise(ArgumentError){ @c1.update("") }
9495
end
9596

@@ -136,12 +137,10 @@ def test_AES
136137
}
137138
end
138139

139-
def test_AES_crush
140-
500.times do
141-
assert_nothing_raised("[Bug #2768]") do
142-
# it caused OpenSSL SEGV by uninitialized key
143-
OpenSSL::Cipher::AES128.new("ECB").update "." * 17
144-
end
140+
def test_update_raise_if_key_not_set
141+
assert_raise(OpenSSL::Cipher::CipherError) do
142+
# it caused OpenSSL SEGV by uninitialized key [Bug #2768]
143+
OpenSSL::Cipher::AES128.new("ECB").update "." * 17
145144
end
146145
end
147146

@@ -317,6 +316,24 @@ def test_aes_ocb_tag_len
317316
}
318317
end if has_cipher?("aes-128-ocb")
319318

319+
def test_aes_gcm_key_iv_order_issue
320+
pt = "[ruby/openssl#49]"
321+
cipher = OpenSSL::Cipher.new("aes-128-gcm").encrypt
322+
cipher.key = "x" * 16
323+
cipher.iv = "a" * 12
324+
ct1 = cipher.update(pt) << cipher.final
325+
tag1 = cipher.auth_tag
326+
327+
cipher = OpenSSL::Cipher.new("aes-128-gcm").encrypt
328+
cipher.iv = "a" * 12
329+
cipher.key = "x" * 16
330+
ct2 = cipher.update(pt) << cipher.final
331+
tag2 = cipher.auth_tag
332+
333+
assert_equal ct1, ct2
334+
assert_equal tag1, tag2
335+
end if has_cipher?("aes-128-gcm")
336+
320337
private
321338

322339
def new_encryptor(algo)

0 commit comments

Comments
 (0)