Skip to content

Commit 9663b09

Browse files
Add AuthTagError exception for AEAD authentication failures (#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
1 parent 9985c71 commit 9663b09

File tree

2 files changed

+22
-6
lines changed

2 files changed

+22
-6
lines changed

ext/openssl/ossl_cipher.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
*/
3333
static VALUE cCipher;
3434
static VALUE eCipherError;
35+
static VALUE eAuthTagError;
3536
static ID id_auth_tag_len, id_key_set;
3637

3738
static VALUE ossl_cipher_alloc(VALUE klass);
@@ -415,8 +416,17 @@ ossl_cipher_final(VALUE self)
415416

416417
GetCipher(self, ctx);
417418
str = rb_str_new(0, EVP_CIPHER_CTX_block_size(ctx));
418-
if (!EVP_CipherFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), &out_len))
419-
ossl_raise(eCipherError, NULL);
419+
if (!EVP_CipherFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), &out_len)) {
420+
/* For AEAD ciphers, this is likely an authentication failure */
421+
if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) {
422+
/* For AEAD ciphers, EVP_CipherFinal_ex failures are authentication tag verification failures */
423+
ossl_raise(eAuthTagError, "AEAD authentication tag verification failed");
424+
}
425+
else {
426+
/* For non-AEAD ciphers */
427+
ossl_raise(eCipherError, "cipher final failed");
428+
}
429+
}
420430
assert(out_len <= RSTRING_LEN(str));
421431
rb_str_set_len(str, out_len);
422432

@@ -1027,6 +1037,7 @@ Init_ossl_cipher(void)
10271037
*/
10281038
cCipher = rb_define_class_under(mOSSL, "Cipher", rb_cObject);
10291039
eCipherError = rb_define_class_under(cCipher, "CipherError", eOSSLError);
1040+
eAuthTagError = rb_define_class_under(cCipher, "AuthTagError", eCipherError);
10301041

10311042
rb_define_alloc_func(cCipher, ossl_cipher_alloc);
10321043
rb_define_method(cCipher, "initialize_copy", ossl_cipher_copy, 1);

test/openssl/test_cipher.rb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ def test_update_raise_if_key_not_set
182182
end
183183
end
184184

185+
def test_auth_tag_error_inheritance
186+
assert_equal OpenSSL::Cipher::CipherError, OpenSSL::Cipher::AuthTagError.superclass
187+
end
188+
185189
def test_authenticated
186190
cipher = OpenSSL::Cipher.new('aes-128-gcm')
187191
assert_predicate(cipher, :authenticated?)
@@ -212,7 +216,8 @@ def test_aes_ccm
212216
cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag[0, 8], auth_data: aad)
213217
assert_equal pt, cipher.update(ct) << cipher.final
214218

215-
# wrong tag is rejected
219+
# wrong tag is rejected - in CCM, authentication happens during update, but
220+
# we consider this a general CipherError since update failures can have various causes
216221
tag2 = tag.dup
217222
tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff)
218223
cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag2, auth_data: aad)
@@ -265,19 +270,19 @@ def test_aes_gcm
265270
tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff)
266271
cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag2, auth_data: aad)
267272
cipher.update(ct)
268-
assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
273+
assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final }
269274

270275
# wrong aad is rejected
271276
aad2 = aad[0..-2] << aad[-1].succ
272277
cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad2)
273278
cipher.update(ct)
274-
assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
279+
assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final }
275280

276281
# wrong ciphertext is rejected
277282
ct2 = ct[0..-2] << ct[-1].succ
278283
cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad)
279284
cipher.update(ct2)
280-
assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
285+
assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final }
281286
end
282287

283288
def test_aes_gcm_variable_iv_len

0 commit comments

Comments
 (0)