Skip to content

Commit b8b9723

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

File tree

2 files changed

+109
-31
lines changed

2 files changed

+109
-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: 88 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')
@@ -498,4 +496,80 @@ def test_encrypt_aes_256_cbc_invalid_buffer
498496
assert_raise(TypeError) { cipher.update('bar' * 10, buffer) }
499497
end
500498

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

0 commit comments

Comments
 (0)