@@ -1090,26 +1090,23 @@ private String getCipherAlgorithm() {
10901090 public IRubyObject update (final ThreadContext context , final IRubyObject arg ) {
10911091 final Ruby runtime = context .runtime ;
10921092
1093- if ( auth_tag != null ) throw newAuthTagPresentError (runtime );
1094-
10951093 if ( isDebug (runtime ) ) dumpVars ( runtime .getOut (), "update()" );
10961094
10971095 checkCipherNotNull (runtime );
1096+ checkAuthTag (runtime );
10981097
10991098 final ByteList data = arg .asString ().getByteList ();
11001099 final int length = data .length ();
11011100 if ( length == 0 ) {
11021101 throw runtime .newArgumentError ("data must not be empty" );
11031102 }
11041103
1105- if ( ! cipherInited ) {
1106- //if ( debug ) runtime.getOut().println("BEFORE INITING");
1107- doInitCipher (runtime );
1108- //if ( debug ) runtime.getOut().println("AFTER INITING");
1109- }
1104+ if ( ! cipherInited ) doInitCipher (runtime );
11101105
11111106 final ByteList str ;
11121107 try {
1108+ updateAuthData (runtime ); // if any
1109+
11131110 final byte [] in = data .getUnsafeBytes ();
11141111 final int offset = data .begin ();
11151112 final byte [] out = cipher .update (in , offset , length );
@@ -1143,43 +1140,32 @@ public IRubyObject update_deprecated(final ThreadContext context, final IRubyObj
11431140 public IRubyObject do_final (final ThreadContext context ) {
11441141 final Ruby runtime = context .runtime ;
11451142
1146- if ( auth_tag != null ) throw newAuthTagPresentError (runtime );
1147-
11481143 checkCipherNotNull (runtime );
1144+ checkAuthTag (runtime );
1145+
11491146 if ( ! cipherInited ) doInitCipher (runtime );
11501147 // trying to allow update after final like cruby-openssl. Bad idea.
11511148 if ( "RC4" .equalsIgnoreCase (cryptoBase ) ) return runtime .newString ("" );
11521149
1153- final ByteList str ; boolean shared = false ;
1150+ final ByteList str ;
11541151 try {
1155- final byte [] out = cipher .doFinal ();
1156- if ( out != null ) {
1157- if ( isAuthDataMode () ) { // TODO only implemented encryption !
1158-
1159- // if GCM/CCM is being used, the authentication tag is appended
1160- // in the case of encryption, or verified in the case of decryption.
1161- // The result is stored in a new buffer.
1162- final int len = getAuthTagLength (); int strLen ;
1163- if ( ( strLen = out .length - len ) > 0 ) {
1164- str = new ByteList (out , 0 , strLen , false ); shared = true ;
1165- }
1166- else {
1167- str = new ByteList (ByteList .NULL_ARRAY ); strLen = 0 ;
1168- }
1169- auth_tag = new ByteList (out , strLen , out .length - strLen );
1170- }
1171- // TODO: Modifying this line appears to fix the issue, but I do
1172- // not have a good reason for why. Best I can tell, lastIv needs
1173- // to be set regardless of encryptMode, so we'll go with this
1174- // for now. JRUBY-3335.
1175- //if ( realIV != null && encryptMode ) ...
1176- else {
1152+ if ( isAuthDataMode () ) {
1153+ str = do_final_with_auth (runtime );
1154+ }
1155+ else {
1156+ final byte [] out = cipher .doFinal ();
1157+ if ( out != null ) {
1158+ // TODO: Modifying this line appears to fix the issue, but I do
1159+ // not have a good reason for why. Best I can tell, lastIv needs
1160+ // to be set regardless of encryptMode, so we'll go with this
1161+ // for now. JRUBY-3335.
1162+ //if ( realIV != null && encryptMode ) ...
11771163 str = new ByteList (out , false );
11781164 if ( realIV != null ) setLastIVIfNeeded (out );
11791165 }
1180- }
1181- else {
1182- str = new ByteList ( ByteList . NULL_ARRAY );
1166+ else {
1167+ str = new ByteList ( ByteList . NULL_ARRAY );
1168+ }
11831169 }
11841170
11851171 //if ( ! isStreamCipher() ) {
@@ -1202,18 +1188,46 @@ public IRubyObject do_final(final ThreadContext context) {
12021188 debugStackTrace (runtime , e );
12031189 throw newCipherError (runtime , e );
12041190 }
1205- return shared ? RubyString . newStringShared ( runtime , str ) : RubyString .newString (runtime , str );
1191+ return RubyString .newString (runtime , str );
12061192 }
12071193
1208- private RaiseException newAuthTagPresentError (final Ruby runtime ) {
1209- final String error ;
1210- //if ( encryptMode ) {
1211- error = "authentication tag already generated by cipher" ;
1212- //}
1213- //else {
1214- //error = "authentication tag already consumed by cipher";
1215- //}
1216- return newCipherError (runtime , error );
1194+ private ByteList do_final_with_auth (final Ruby runtime ) throws GeneralSecurityException {
1195+ updateAuthData (runtime ); // if any
1196+
1197+ final ByteList str ;
1198+ // if GCM/CCM is being used, the authentication tag is appended
1199+ // in the case of encryption, or verified in the case of decryption.
1200+ // The result is stored in a new buffer.
1201+ if ( encryptMode ) {
1202+ final byte [] out = cipher .doFinal ();
1203+
1204+ final int len = getAuthTagLength (); int strLen ;
1205+ if ( ( strLen = out .length - len ) > 0 ) {
1206+ str = new ByteList (out , 0 , strLen , false );
1207+ }
1208+ else {
1209+ str = new ByteList (ByteList .NULL_ARRAY ); strLen = 0 ;
1210+ }
1211+ auth_tag = new ByteList (out , strLen , out .length - strLen );
1212+ return str ;
1213+ }
1214+ else {
1215+ final byte [] out ;
1216+ if ( auth_tag != null ) {
1217+ final byte [] tag = auth_tag .getUnsafeBytes ();
1218+ out = cipher .doFinal (tag , auth_tag .begin (), auth_tag .length ());
1219+ }
1220+ else {
1221+ out = cipher .doFinal ();
1222+ }
1223+ return new ByteList (out , false );
1224+ }
1225+ }
1226+
1227+ private void checkAuthTag (final Ruby runtime ) {
1228+ if ( auth_tag != null && encryptMode ) {
1229+ throw newCipherError (runtime , "authentication tag already generated by cipher" );
1230+ }
12171231 }
12181232
12191233 private void setLastIVIfNeeded (final byte [] tmpIV ) {
@@ -1247,6 +1261,17 @@ public IRubyObject auth_tag(final ThreadContext context) {
12471261 return context .nil ;
12481262 }
12491263
1264+ @ JRubyMethod (name = "auth_tag=" )
1265+ public IRubyObject set_auth_tag (final ThreadContext context , final IRubyObject tag ) {
1266+ if ( ! isAuthDataMode () ) {
1267+ throw newCipherError (context .runtime , "authentication tag not supported by this cipher" );
1268+ }
1269+ final RubyString auth_tag = tag .asString ();
1270+ this .auth_tag = auth_tag .getByteList ();
1271+ auth_tag .setByteListShared ();
1272+ return auth_tag ;
1273+ }
1274+
12501275 private boolean isAuthDataMode () { // Authenticated Encryption with Associated Data (AEAD)
12511276 return "GCM" .equalsIgnoreCase (cryptoMode ) || "CCM" .equalsIgnoreCase (cryptoMode );
12521277 }
@@ -1257,6 +1282,33 @@ private int getAuthTagLength() {
12571282 return Math .min (MAX_AUTH_TAG_LENGTH , this .key .length ); // in bytes
12581283 }
12591284
1285+ private transient ByteList auth_data ;
1286+
1287+ @ JRubyMethod (name = "auth_data=" )
1288+ public IRubyObject set_auth_data (final ThreadContext context , final IRubyObject data ) {
1289+ if ( ! isAuthDataMode () ) {
1290+ throw newCipherError (context .runtime , "authentication data not supported by this cipher" );
1291+ }
1292+ final RubyString auth_data = data .asString ();
1293+ this .auth_data = auth_data .getByteList ();
1294+ auth_data .setByteListShared ();
1295+ return auth_data ;
1296+ }
1297+
1298+ private boolean updateAuthData (final Ruby runtime ) {
1299+ if ( auth_data == null ) return false ; // only to be set if auth-mode
1300+ //try {
1301+ final byte [] data = auth_data .getUnsafeBytes ();
1302+ cipher .updateAAD (data , auth_data .begin (), auth_data .length ());
1303+ //}
1304+ //catch (RuntimeException e) {
1305+ // debugStackTrace( runtime, e );
1306+ // throw newCipherError(runtime, e);
1307+ //}
1308+ auth_data = null ;
1309+ return true ;
1310+ }
1311+
12601312 @ JRubyMethod (name = "authenticated?" )
12611313 public RubyBoolean authenticated_p (final ThreadContext context ) {
12621314 return context .runtime .newBoolean ( isAuthDataMode () );
0 commit comments