@@ -1090,26 +1090,23 @@ private String getCipherAlgorithm() {
1090
1090
public IRubyObject update (final ThreadContext context , final IRubyObject arg ) {
1091
1091
final Ruby runtime = context .runtime ;
1092
1092
1093
- if ( auth_tag != null ) throw newAuthTagPresentError (runtime );
1094
-
1095
1093
if ( isDebug (runtime ) ) dumpVars ( runtime .getOut (), "update()" );
1096
1094
1097
1095
checkCipherNotNull (runtime );
1096
+ checkAuthTag (runtime );
1098
1097
1099
1098
final ByteList data = arg .asString ().getByteList ();
1100
1099
final int length = data .length ();
1101
1100
if ( length == 0 ) {
1102
1101
throw runtime .newArgumentError ("data must not be empty" );
1103
1102
}
1104
1103
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 );
1110
1105
1111
1106
final ByteList str ;
1112
1107
try {
1108
+ updateAuthData (runtime ); // if any
1109
+
1113
1110
final byte [] in = data .getUnsafeBytes ();
1114
1111
final int offset = data .begin ();
1115
1112
final byte [] out = cipher .update (in , offset , length );
@@ -1143,43 +1140,32 @@ public IRubyObject update_deprecated(final ThreadContext context, final IRubyObj
1143
1140
public IRubyObject do_final (final ThreadContext context ) {
1144
1141
final Ruby runtime = context .runtime ;
1145
1142
1146
- if ( auth_tag != null ) throw newAuthTagPresentError (runtime );
1147
-
1148
1143
checkCipherNotNull (runtime );
1144
+ checkAuthTag (runtime );
1145
+
1149
1146
if ( ! cipherInited ) doInitCipher (runtime );
1150
1147
// trying to allow update after final like cruby-openssl. Bad idea.
1151
1148
if ( "RC4" .equalsIgnoreCase (cryptoBase ) ) return runtime .newString ("" );
1152
1149
1153
- final ByteList str ; boolean shared = false ;
1150
+ final ByteList str ;
1154
1151
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 ) ...
1177
1163
str = new ByteList (out , false );
1178
1164
if ( realIV != null ) setLastIVIfNeeded (out );
1179
1165
}
1180
- }
1181
- else {
1182
- str = new ByteList ( ByteList . NULL_ARRAY );
1166
+ else {
1167
+ str = new ByteList ( ByteList . NULL_ARRAY );
1168
+ }
1183
1169
}
1184
1170
1185
1171
//if ( ! isStreamCipher() ) {
@@ -1202,18 +1188,46 @@ public IRubyObject do_final(final ThreadContext context) {
1202
1188
debugStackTrace (runtime , e );
1203
1189
throw newCipherError (runtime , e );
1204
1190
}
1205
- return shared ? RubyString . newStringShared ( runtime , str ) : RubyString .newString (runtime , str );
1191
+ return RubyString .newString (runtime , str );
1206
1192
}
1207
1193
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
+ }
1217
1231
}
1218
1232
1219
1233
private void setLastIVIfNeeded (final byte [] tmpIV ) {
@@ -1247,6 +1261,17 @@ public IRubyObject auth_tag(final ThreadContext context) {
1247
1261
return context .nil ;
1248
1262
}
1249
1263
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
+
1250
1275
private boolean isAuthDataMode () { // Authenticated Encryption with Associated Data (AEAD)
1251
1276
return "GCM" .equalsIgnoreCase (cryptoMode ) || "CCM" .equalsIgnoreCase (cryptoMode );
1252
1277
}
@@ -1257,6 +1282,33 @@ private int getAuthTagLength() {
1257
1282
return Math .min (MAX_AUTH_TAG_LENGTH , this .key .length ); // in bytes
1258
1283
}
1259
1284
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
+
1260
1312
@ JRubyMethod (name = "authenticated?" )
1261
1313
public RubyBoolean authenticated_p (final ThreadContext context ) {
1262
1314
return context .runtime .newBoolean ( isAuthDataMode () );
0 commit comments