summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Williams <samuel.williams@shopify.com>2025-09-18 00:32:47 +1200
committergit <svn-admin@ruby-lang.org>2025-09-17 12:32:50 +0000
commita1f39b4b807a5412181ca3f1bf87e7c7d2d9f542 (patch)
tree7c69f43cb2920e3a42d339866114e6eca52e05d9
parent0f3c6ca480d3dab95b355392658972323f890e7d (diff)
[ruby/openssl] Add AuthTagError exception for AEAD authentication
failures (https://github.com/ruby/openssl/pull/939) * Add AuthTagError exception for AEAD authentication failures - Add OpenSSL::Cipher::AuthTagError as a subclass of CipherError - Raise AuthTagError specifically for AEAD cipher authentication tag verification failures - Enhanced error messages: 'AEAD authentication tag verification failed' for auth failures - Precise detection: Only EVP_CipherFinal_ex failures in AEAD ciphers raise AuthTagError - All other errors (key setup, IV setup, update failures, etc.) still raise CipherError - Comprehensive test coverage for GCM/CCM modes and error inheritance - Fully backwards compatible: AuthTagError < CipherError https://github.com/ruby/openssl/commit/9663b09040
-rw-r--r--ext/openssl/ossl_cipher.c15
-rw-r--r--test/openssl/test_cipher.rb13
2 files changed, 22 insertions, 6 deletions
diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c
index 07e651335d..bec634ae11 100644
--- a/ext/openssl/ossl_cipher.c
+++ b/ext/openssl/ossl_cipher.c
@@ -32,6 +32,7 @@
*/
static VALUE cCipher;
static VALUE eCipherError;
+static VALUE eAuthTagError;
static ID id_auth_tag_len, id_key_set;
static VALUE ossl_cipher_alloc(VALUE klass);
@@ -415,8 +416,17 @@ ossl_cipher_final(VALUE self)
GetCipher(self, ctx);
str = rb_str_new(0, EVP_CIPHER_CTX_block_size(ctx));
- if (!EVP_CipherFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), &out_len))
- ossl_raise(eCipherError, NULL);
+ if (!EVP_CipherFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), &out_len)) {
+ /* For AEAD ciphers, this is likely an authentication failure */
+ if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) {
+ /* For AEAD ciphers, EVP_CipherFinal_ex failures are authentication tag verification failures */
+ ossl_raise(eAuthTagError, "AEAD authentication tag verification failed");
+ }
+ else {
+ /* For non-AEAD ciphers */
+ ossl_raise(eCipherError, "cipher final failed");
+ }
+ }
assert(out_len <= RSTRING_LEN(str));
rb_str_set_len(str, out_len);
@@ -1027,6 +1037,7 @@ Init_ossl_cipher(void)
*/
cCipher = rb_define_class_under(mOSSL, "Cipher", rb_cObject);
eCipherError = rb_define_class_under(cCipher, "CipherError", eOSSLError);
+ eAuthTagError = rb_define_class_under(cCipher, "AuthTagError", eCipherError);
rb_define_alloc_func(cCipher, ossl_cipher_alloc);
rb_define_method(cCipher, "initialize_copy", ossl_cipher_copy, 1);
diff --git a/test/openssl/test_cipher.rb b/test/openssl/test_cipher.rb
index cd0b3dcb44..10858b3535 100644
--- a/test/openssl/test_cipher.rb
+++ b/test/openssl/test_cipher.rb
@@ -182,6 +182,10 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
end
end
+ def test_auth_tag_error_inheritance
+ assert_equal OpenSSL::Cipher::CipherError, OpenSSL::Cipher::AuthTagError.superclass
+ end
+
def test_authenticated
cipher = OpenSSL::Cipher.new('aes-128-gcm')
assert_predicate(cipher, :authenticated?)
@@ -212,7 +216,8 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag[0, 8], auth_data: aad)
assert_equal pt, cipher.update(ct) << cipher.final
- # wrong tag is rejected
+ # wrong tag is rejected - in CCM, authentication happens during update, but
+ # we consider this a general CipherError since update failures can have various causes
tag2 = tag.dup
tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff)
cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag2, auth_data: aad)
@@ -265,19 +270,19 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff)
cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag2, auth_data: aad)
cipher.update(ct)
- assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
+ assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final }
# wrong aad is rejected
aad2 = aad[0..-2] << aad[-1].succ
cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad2)
cipher.update(ct)
- assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
+ assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final }
# wrong ciphertext is rejected
ct2 = ct[0..-2] << ct[-1].succ
cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad)
cipher.update(ct2)
- assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
+ assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final }
end
def test_aes_gcm_variable_iv_len