diff options
| author | Samuel Williams <samuel.williams@shopify.com> | 2025-09-18 00:32:47 +1200 |
|---|---|---|
| committer | git <svn-admin@ruby-lang.org> | 2025-09-17 12:32:50 +0000 |
| commit | a1f39b4b807a5412181ca3f1bf87e7c7d2d9f542 (patch) | |
| tree | 7c69f43cb2920e3a42d339866114e6eca52e05d9 | |
| parent | 0f3c6ca480d3dab95b355392658972323f890e7d (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.c | 15 | ||||
| -rw-r--r-- | test/openssl/test_cipher.rb | 13 |
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 |
