-
Bug
-
Resolution: Fixed
-
P3
-
8u73, 9
-
b139
-
generic
-
generic
-
Verified
FULL PRODUCT VERSION :
java version "1.8.0_73"
Java(TM) SE Runtime Environment (build 1.8.0_73-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.73-b02, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.3.9600]
A DESCRIPTION OF THE PROBLEM :
The javadocs for Signature.verify() assert that "A call to this method resets this signature object to the state it was in when previously initialized for verification via a call to initVerify(PublicKey). That is, the object is reset and available to verify another signature from the identity whose public key was specified in the call to initVerify.".
Note in particular that it does NOT say that this won't happen if an exception is thrown. Suppose I have a set of candidate signatures I want to test. I attempt to verify the first signature by calling update() and then verify(), but the key is not the correct length, so a SecurityException is thrown. By the Javadocs, the Signature object should still have been reset. Now suppose I use the same Signature object to test the next candidate, again calling update() and verify(). This candidate, known to be valid, fails verification (no exception). Here is where it gets interesting: if I then re-test that same candidate, again calling update() and verify(), now verification succeeds! This suggests that the Signature object was not reset following the exception from the first case, but then was reset after the failure of the second case.
I observed this with JCE algorithm "SHA256withRSA", though I wouldn't be surprised if other algorithms suffered the same way.
This could be resolved different ways. At the very least I think the Javadocs need to be updated to make it clear what happens wrt. the Signature object reset in case of an exception.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1) Run the included test code. The test code verifies the "real" signature twice, then fake signature "foo", then the "real" signature twice again. It outputs the results one per line.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The "real" signature should have a "true" result in all four cases. The fake signature throws a SecurityException.
ACTUAL -
The "real" signature has a "false" result on the third case, and a "true" result on all other cases. The fake signature throws a SecurityException.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.util.Base64;
public class SignaturesTest {
public static void main(String[] args) throws GeneralSecurityException {
System.out.println("Java version: " + Runtime.class.getPackage().getImplementationVersion());
System.out.println();
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(512);
KeyPair keyPair = keyGen.generateKeyPair();
String data = "data to be signed";
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(keyPair.getPrivate());
sig.update(data.getBytes());
String signature = Base64.getEncoder().encodeToString(sig.sign());
String[] candidates = {
signature,
signature,
"foo",
signature,
signature,
};
sig.initVerify(keyPair.getPublic());
for(String candidate : candidates) {
try {
sig.update(data.getBytes());
boolean result = sig.verify(Base64.getDecoder().decode(candidate));
System.out.println(candidate + ": " + result);
} catch (GeneralSecurityException e) {
System.out.println(candidate + ": " + e.getClass().getSimpleName());
}
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
You can explicitly call Signature.initVerify() just prior to Signature.update() to work around the problem because it manually resets the signature object. This is not ideal however because it requires the holder of the Signature object to know the public key.
java version "1.8.0_73"
Java(TM) SE Runtime Environment (build 1.8.0_73-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.73-b02, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.3.9600]
A DESCRIPTION OF THE PROBLEM :
The javadocs for Signature.verify() assert that "A call to this method resets this signature object to the state it was in when previously initialized for verification via a call to initVerify(PublicKey). That is, the object is reset and available to verify another signature from the identity whose public key was specified in the call to initVerify.".
Note in particular that it does NOT say that this won't happen if an exception is thrown. Suppose I have a set of candidate signatures I want to test. I attempt to verify the first signature by calling update() and then verify(), but the key is not the correct length, so a SecurityException is thrown. By the Javadocs, the Signature object should still have been reset. Now suppose I use the same Signature object to test the next candidate, again calling update() and verify(). This candidate, known to be valid, fails verification (no exception). Here is where it gets interesting: if I then re-test that same candidate, again calling update() and verify(), now verification succeeds! This suggests that the Signature object was not reset following the exception from the first case, but then was reset after the failure of the second case.
I observed this with JCE algorithm "SHA256withRSA", though I wouldn't be surprised if other algorithms suffered the same way.
This could be resolved different ways. At the very least I think the Javadocs need to be updated to make it clear what happens wrt. the Signature object reset in case of an exception.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1) Run the included test code. The test code verifies the "real" signature twice, then fake signature "foo", then the "real" signature twice again. It outputs the results one per line.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The "real" signature should have a "true" result in all four cases. The fake signature throws a SecurityException.
ACTUAL -
The "real" signature has a "false" result on the third case, and a "true" result on all other cases. The fake signature throws a SecurityException.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.util.Base64;
public class SignaturesTest {
public static void main(String[] args) throws GeneralSecurityException {
System.out.println("Java version: " + Runtime.class.getPackage().getImplementationVersion());
System.out.println();
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(512);
KeyPair keyPair = keyGen.generateKeyPair();
String data = "data to be signed";
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(keyPair.getPrivate());
sig.update(data.getBytes());
String signature = Base64.getEncoder().encodeToString(sig.sign());
String[] candidates = {
signature,
signature,
"foo",
signature,
signature,
};
sig.initVerify(keyPair.getPublic());
for(String candidate : candidates) {
try {
sig.update(data.getBytes());
boolean result = sig.verify(Base64.getDecoder().decode(candidate));
System.out.println(candidate + ": " + result);
} catch (GeneralSecurityException e) {
System.out.println(candidate + ": " + e.getClass().getSimpleName());
}
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
You can explicitly call Signature.initVerify() just prior to Signature.update() to work around the problem because it manually resets the signature object. This is not ideal however because it requires the holder of the Signature object to know the public key.