Skip to content

Commit ff2420e

Browse files
committed
[fix] add Cipher#auth_data(arg) override (Rails 7.x compat)
1 parent 1913525 commit ff2420e

File tree

2 files changed

+108
-31
lines changed

2 files changed

+108
-31
lines changed

src/main/java/org/jruby/ext/openssl/Cipher.java

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,6 @@ public Cipher(Ruby runtime, RubyClass type) {
715715
private int generateKeyLength = -1;
716716
private int ivLength = -1;
717717
private boolean encryptMode = true;
718-
//private IRubyObject[] modeParams;
719718
private boolean cipherInited = false;
720719
private byte[] key;
721720
private byte[] realIV;
@@ -1265,11 +1264,24 @@ public IRubyObject set_padding(IRubyObject padding) {
12651264
}
12661265

12671266
private transient ByteList auth_tag;
1267+
private int auth_tag_len = 16;
12681268

12691269
@JRubyMethod(name = "auth_tag")
12701270
public IRubyObject auth_tag(final ThreadContext context) {
1271+
return getAuthTag(context, auth_tag_len);
1272+
}
1273+
1274+
@JRubyMethod(name = "auth_tag")
1275+
public IRubyObject auth_tag(final ThreadContext context, IRubyObject tag_len) {
1276+
return getAuthTag(context, tag_len.convertToInteger().getIntValue());
1277+
}
1278+
1279+
private IRubyObject getAuthTag(final ThreadContext context, final int tag_len) {
12711280
if ( auth_tag != null ) {
1272-
return RubyString.newString(context.runtime, auth_tag);
1281+
if (auth_tag.length() <= tag_len) {
1282+
return RubyString.newString(context.runtime, auth_tag);
1283+
}
1284+
return RubyString.newString(context.runtime, (ByteList) auth_tag.subSequence(0, tag_len));
12731285
}
12741286
if ( ! isAuthDataMode() ) {
12751287
throw newCipherError(context.runtime, "authentication tag not supported by this cipher");
@@ -1287,14 +1299,18 @@ public IRubyObject set_auth_tag(final ThreadContext context, final IRubyObject t
12871299
return auth_tag;
12881300
}
12891301

1302+
@JRubyMethod(name = "auth_tag_len=")
1303+
public IRubyObject set_auth_tag_len(IRubyObject tag_len) {
1304+
this.auth_tag_len = tag_len.convertToInteger().getIntValue();
1305+
return tag_len;
1306+
}
1307+
12901308
private boolean isAuthDataMode() { // Authenticated Encryption with Associated Data (AEAD)
12911309
return "GCM".equalsIgnoreCase(cryptoMode) || "CCM".equalsIgnoreCase(cryptoMode);
12921310
}
12931311

1294-
private static final int MAX_AUTH_TAG_LENGTH = 16;
1295-
12961312
private int getAuthTagLength() {
1297-
return Math.min(MAX_AUTH_TAG_LENGTH, this.key.length); // in bytes
1313+
return Math.min(auth_tag_len, this.key.length); // in bytes
12981314
}
12991315

13001316
private transient ByteList auth_data;
@@ -1346,22 +1362,10 @@ public IRubyObject random_iv(final ThreadContext context) {
13461362
this.set_iv(context, str); return str;
13471363
}
13481364

1349-
//String getAlgorithm() {
1350-
// return this.cipher.getAlgorithm();
1351-
//}
1352-
13531365
final String getName() {
13541366
return this.name;
13551367
}
13561368

1357-
//String getCryptoBase() {
1358-
// return this.cryptoBase;
1359-
//}
1360-
1361-
//String getCryptoMode() {
1362-
// return this.cryptoMode;
1363-
//}
1364-
13651369
final int getKeyLength() {
13661370
return keyLength;
13671371
}

src/test/ruby/test_cipher.rb

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -363,41 +363,39 @@ def test_aes_128_gcm
363363
#assert_equal "", cipher.final
364364
end
365365

366-
def test_aes_gcm
366+
def test_aes_gcm_custom
367367
['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'].each do |algo|
368368
pt = "You should all use Authenticated Encryption!"
369-
cipher, key, iv = new_encryptor(algo)
369+
cipher, key, iv = new_random_encryptor(algo)
370370

371371
cipher.auth_data = "aad"
372372
ct = cipher.update(pt) + cipher.final
373373
tag = cipher.auth_tag
374374
assert_equal(16, tag.size)
375375

376-
decipher = new_decryptor(algo, key, iv)
376+
decipher = new_decryptor(algo, key: key, iv: iv)
377377
decipher.auth_tag = tag
378378
decipher.auth_data = "aad"
379379

380380
assert_equal(pt, decipher.update(ct) + decipher.final)
381381
end
382382
end
383383

384-
def new_encryptor(algo)
384+
def test_authenticated
385+
cipher = OpenSSL::Cipher.new('aes-128-gcm')
386+
assert_predicate(cipher, :authenticated?)
387+
cipher = OpenSSL::Cipher.new('aes-128-cbc')
388+
assert_not_predicate(cipher, :authenticated?)
389+
end
390+
391+
def new_random_encryptor(algo)
385392
cipher = OpenSSL::Cipher.new(algo)
386393
cipher.encrypt
387394
key = cipher.random_key
388395
iv = cipher.random_iv
389396
[cipher, key, iv]
390397
end
391-
private :new_encryptor
392-
393-
def new_decryptor(algo, key, iv)
394-
OpenSSL::Cipher.new(algo).tap do |cipher|
395-
cipher.decrypt
396-
cipher.key = key
397-
cipher.iv = iv
398-
end
399-
end
400-
private :new_decryptor
398+
private :new_random_encryptor
401399

402400
def test_aes_128_gcm_with_auth_tag
403401
cipher = OpenSSL::Cipher.new('aes-128-gcm')
@@ -496,6 +494,81 @@ def test_encrypt_aes_256_cbc_invalid_buffer
496494
cipher.encrypt
497495
buffer = Object.new
498496
assert_raise(TypeError) { cipher.update('bar' * 10, buffer) }
497+
498+
def test_aes_gcm
499+
# GCM spec Appendix B Test Case 4
500+
key = ["feffe9928665731c6d6a8f9467308308"].pack("H*")
501+
iv = ["cafebabefacedbaddecaf888"].pack("H*")
502+
aad = ["feedfacedeadbeeffeedfacedeadbeef" \
503+
"abaddad2"].pack("H*")
504+
pt = ["d9313225f88406e5a55909c5aff5269a" \
505+
"86a7a9531534f7da2e4c303d8a318a72" \
506+
"1c3c0c95956809532fcf0e2449a6b525" \
507+
"b16aedf5aa0de657ba637b39"].pack("H*")
508+
ct = ["42831ec2217774244b7221b784d0d49c" \
509+
"e3aa212f2c02a4e035c17e2329aca12e" \
510+
"21d514b25466931c7d8f6a5aac84aa05" \
511+
"1ba30b396a0aac973d58e091"].pack("H*")
512+
tag = ["5bc94fbc3221a5db94fae95ae7121a47"].pack("H*")
513+
514+
cipher = new_encryptor("aes-128-gcm", key: key, iv: iv, auth_data: aad)
515+
# TODO JOpenSSL should raise
516+
# assert_raise(OpenSSL::Cipher::CipherError, 'unable to set authentication tag length: failed to get parameter') do
517+
# cipher.auth_tag_len = 16
518+
# end
519+
assert_equal ct, cipher.update(pt) << cipher.final
520+
assert_equal tag, cipher.auth_tag
521+
cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad)
522+
# TODO JOpenSSL should raise
523+
# assert_raise(OpenSSL::Cipher::CipherError, 'unable to set authentication tag length: failed to get parameter') do
524+
# cipher.auth_tag_len = 16
525+
# end
526+
assert_equal pt, cipher.update(ct) << cipher.final
527+
528+
# truncated tag is accepted
529+
cipher = new_encryptor("aes-128-gcm", key: key, iv: iv, auth_data: aad)
530+
assert_equal ct, cipher.update(pt) << cipher.final
531+
assert_equal tag[0, 8], cipher.auth_tag(8)
532+
assert_equal tag, cipher.auth_tag
533+
534+
# NOTE: MRI seems to just ignore the invalid tag?!
535+
# cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag[0, 8], auth_data: aad)
536+
# assert_equal pt, cipher.update(ct) << cipher.final
537+
538+
# wrong tag is rejected
539+
tag2 = tag.dup
540+
tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff)
541+
cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag2, auth_data: aad)
542+
cipher.update(ct)
543+
assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
544+
545+
# wrong aad is rejected
546+
aad2 = aad[0..-2] << aad[-1].succ
547+
cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad2)
548+
cipher.update(ct)
549+
assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
550+
551+
# wrong ciphertext is rejected
552+
ct2 = ct[0..-2] << ct[-1].succ
553+
cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad)
554+
cipher.update(ct2)
555+
assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
556+
end
557+
558+
private
559+
560+
def new_encryptor(algo, **kwargs)
561+
OpenSSL::Cipher.new(algo).tap do |cipher|
562+
cipher.encrypt
563+
kwargs.each {|k, v| cipher.send(:"#{k}=", v) }
564+
end
565+
end
566+
567+
def new_decryptor(algo, **kwargs)
568+
OpenSSL::Cipher.new(algo).tap do |cipher|
569+
cipher.decrypt
570+
kwargs.each {|k, v| cipher.send(:"#{k}=", v) }
571+
end
499572
end
500573

501574
end

0 commit comments

Comments
 (0)