Bug 44335 - can't validate after invalid validation
Summary: can't validate after invalid validation
Status: RESOLVED FIXED
Alias: None
Product: Security - Now in JIRA
Classification: Unclassified
Component: Signature (show other bugs)
Version: Java 1.4.1
Hardware: Other Windows XP
: P2 major
Target Milestone: ---
Assignee: XML Security Developers Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2008-01-31 17:59 UTC by Nayan Hajratwala
Modified: 2009-07-08 07:15 UTC (History)
1 user (show)



Attachments
A patch for this issue (5.38 KB, patch)
2009-06-09 04:57 UTC, coheigea
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Nayan Hajratwala 2008-01-31 17:59:04 UTC
I've run into a strange problem using xmlsec-1.4.1.

I can sign a document - no problem.

I can validate a VALID signature - no problem.

I can detect an INVALID signature - no problem.


However, once I detect an invalid signature, I validate any further signatures!

I've boiled my code down to a simple case that i've posted at
http://agileshrugged.com/files/soaptester.zip -- It's an eclipse project but
running it outside of eclipse should work if you include all the libraries in
the "lib" directory on your classpath.

There is a JUnit test "SoapUtilTest" in which testSign() and testValidate()
work, but testInvalidThenValid() fails.

I've tried this in JDK 1.4 & 1.6 with no luck.
Comment 1 Brent Putman 2008-02-04 16:34:13 UTC
I spent some time debugging this, as it sounded similar to bug reported for our
project.  I don't think the two are related, but here is what I learned.

This bug is happening when an exception is thrown in
XMLSignature#checkSignatureValue(Key), between the time that
SignatureAlgorithm#initVerify(Key) is called and
SignatureAlgorithm#verify(byte[]) is called, obviously resulting in the latter
never being called.

Because the verify operation is not performed, the (thread local) cached
java.security.Signature instance held in the SignatureAlgorithm instancesVerify
Map is left in a state (in this particular case) with updated data in the
buffer, that is never cleared/reset by a verify operation.

Ordinarily this wouldn't be a problem, because on the next verify op in the same
thread, the java.security.Signature instance would be reinitialized ultimately
with java.security.Signature#initVerify(PublicKey), via a call to
SignatureAlgorithmSpi.  However, due to the optimization that's being done in
SignatureAlgorithm#initVerify(Key), if the key used for the next verify op for
that algorithm in that thread is the *same* key as the previous (failed)
operation, it doesn't call the underlying
SignatureAlgorithmSpi#engineInitVerify(Key).

So the bug is that this optimization implicitly assumes that the previous op
with that key always ran to completion, which is not true in this case.

In the subsequent verification, the data over which the signature is calculated
is *added* to whatever data is in the buffer from the previous failed op,
resulting in a failed verification on an otherwise valid signature.

In this particular test case, the exception is being thrown here:

         //retrieve the byte[] from the stored signature
         byte sigBytes[] = this.getSignatureValue(); 

because the way the invalidly signed test case control document is being
invalidated is by modifying the ds:SignatureValue data, but with what is
syntactically invalid Base64-encoded data - it's not a multiple of 4 bytes and
also has an illegal character.  I suppose it could be argued that this is pretty
unlikely to happen in the real world, but IMHO it seems the library probably
ought to handle it more gracefully. Especially because I think you could get a
similar bug case if for example the code below that writes the signed data into
the SignatureAlgorithm were to throw an exception after having written some data
to the java.security.Signature buffer.


         // Get the canonicalized (normalized) SignedInfo
         SignerOutputStream so=new SignerOutputStream(sa);
         OutputStream bos=new UnsyncBufferedOutputStream(so);
         si.signInOctectStream(bos);
         try {
			bos.close();
		} catch (IOException e) {
			//Imposible
		}



I don't see a clean fix.  Perhaps ideally the SignatureAlgorithm class would
expose a method that could be called from the exception catch block and which
caused the cached algorithm -> key mapping in the keysVerify Map to be removed
or the key reference set to null.  This would ensure that
SignatureAlgorithmSpi#engineVerifyInit would be called in initVerify(Key) on the
next verification, regardless of what the most recently used key was.

An ugly workaround is to ensure that SignatureAlgorithm#verify(byte[]) is always
called.  For example, this "fixes" the problem in this test case by verifying
some dummy bytes before rethrowing the exception:

         //retrieve the byte[] from the stored signature
         byte sigBytes[] = null;
         try {
        	 sigBytes = this.getSignatureValue();
         } catch (XMLSignatureException e) {
        	 sa.verify("dummy-data".getBytes());
        	 throw new XMLSignatureException("empty", e);
         }


Also, I haven't looked at it in detail, but I would think that the signing side
of things might have a similar bug, since it essentially uses the same
optimizations in SignatureAlgorithm as to caching java.security.Signature
instances and conditional re-initialization of those based on the most recently
used Key.
Comment 2 Nayan Hajratwala 2008-02-05 08:44:30 UTC
thanks for looking into this ... I agree with your assessment that it is
unlikely to happen in production, and also that the library should handle it
gracefully.

Would it not be as simple as putting a try/catch around:

           byte sigBytes[] = this.getSignatureValue(); 

And then resetting the appropriate values if necessary?
Comment 3 Brent Putman 2008-02-05 13:17:00 UTC
Well, yes, that's basically what I illustrated in my "ugly workaround".  But the
only way to currently reset the appropriate values is to call verify (on some
bytes, it doesn't matter what).  That completes the init/update/verify cycle and
leaves the underlying SignatureAlgorithmSpi (really the wrapped
java.security.Signature/Mac instance) in a known good state.

Re-invoking initVerify(Key) there wouldn't work because it's the source of the
problem in the first place.  You'd have to pass in the current verification key,
b/c that's all you have - and that would be a noop, b/c of the optimization.

Those are the only 2 methods that I can see which cause the underlying engine's
state to be reset.

So other than calling verify on dummy data (less than ideal...), one obvious
option is to expose a new method on SignatureAlgorithm, which causes the
underlying engine Spi instance to be effectively reset (or recreated?) to a
known good state, either prior to or at the time of, the next verification.

Another option, which just occurred to me, is for the engine Spi instance to
keep track of what state it is in with respect to the init/update/verify cycle
(or init/update/sign).  The SignatureAlgorithm could then check this state and
do the right thing if the engine isn't in the "ready" state.  This might be more
desirable, because it alleviates the caller (XMLSignature) from having to be
sure to reset the engine for all cases where exceptions might encountered in the
sign/verify cycles.  This would require augmenting the API of the
SignatureAlgorithmSpi, however.
Comment 4 Dominique LAURENT 2008-12-01 19:38:55 UTC
Hey,

I just got this bug.

I think there may be a quicker fix:

just move the two lines:

         //retrieve the byte[] from the stored signature
         byte sigBytes[] = this.getSignatureValue();

before the try/catch block:

      try {
         SignedInfo si=this.getSignedInfo();


This way, if the Base64 decoding fails, the SignatureAlgorithm hasn't yet been updated and does not need to be reset.
 
This avoids having to call #verify() just to reset the SignatureAlgorithm.



The checkSignatureValue method would look like this:

    public boolean checkSignatureValue(Key pk) throws XMLSignatureException {

        if (pk == null) {
            Object exArgs[] = { "Didn't get a key" };

            throw new XMLSignatureException("empty", exArgs);
        }

        //retrieve the byte[] from the stored signature
        // Do this before calling the SignatureAlgorithm
        // that way if something goes bad, the sa isn't corrupted
        byte sigBytes[] = this.getSignatureValue();

        try {
            SignedInfo si=this.getSignedInfo();
            //create a SignatureAlgorithms from the SignatureMethod inside
            //SignedInfo. This is used to validate the signature.
            SignatureAlgorithm sa =si.getSignatureAlgorithm();
            if (log.isDebugEnabled()) {
                log.debug("SignatureMethodURI = " + sa.getAlgorithmURI());
                log.debug("jceSigAlgorithm    = " + sa.getJCEAlgorithmString());
                log.debug("jceSigProvider     = " + sa.getJCEProviderName());
                log.debug("PublicKey = " + pk);
            }
            sa.initVerify(pk);

            // Get the canonicalized (normalized) SignedInfo
            SignerOutputStream so=new SignerOutputStream(sa);
            OutputStream bos=new UnsyncBufferedOutputStream(so);
            si.signInOctectStream(bos);
            try {
                bos.close();
            } catch (IOException e) {
                //Imposible
            }

            if (!sa.verify(sigBytes)) {
                log.warn("Signature verification failed.");
                return false;
            }

            return si.verify(this._followManifestsDuringValidation);
        } catch (XMLSecurityException ex) {
            throw new XMLSignatureException("empty", ex);
        }
    }
Comment 5 coheigea 2009-06-09 04:57:56 UTC
Created attachment 23779 [details]
A patch for this issue


See attached for a patch for this issue.

I added in two new methods to SignatureAction, one to clear the signature caches, and one to clear the verification caches.

The initializing code in XMLSignature for signing and verification is now wrapped in a try/catch that calls the corresponding cache clearing method in SignatureAction on an error, before throwing the exception. So if there's an error here, the Signature "state" is reset before the next attempt and there won't be a problem with a subsequent verification as per the bug.

I've tested it with the submitted test-case and it works fine.

Colm.
Comment 6 coheigea 2009-07-08 07:15:25 UTC
Patch applied.

Colm.