Hi all, I've recently tried to verify a signature from the austrian citizen security card (www.buergerkarte.at), which uses ECDSA-singatures.
Unfortunately, the code in SignatureECDSA.java passes the SignatureValue directly to the JCE-provider. However, the ECDSA xml-security spec at ftp://ftp.rfc-editor.org/in-notes/rfc4050.txt states, that the ECDSA SignatureValue is a concatenation of the raw BigIntegers. This is in line with the semantics of SignatureValue for conventional DSA signatures (SignatureDSA.java), where the SignatureValue is converted to the ASN1 representation used by the JCE provider. The attached patch adopts the procedure of converting the SignatureValue to ASN.1 for the ECDSA algorithm. With this poatch applied to xmlsec-1.4.0 I can verify the signatures of my autrian card. I will additionally ask Gregor Karlinger fom IAIK, which is actually responsible for the austrian citizen card specification and co-author of RFC4050/4051 to provide me with more testvectors, ecpecially containing an ESDSAKeyValue element, which is not yet implemented in xmlsec. I will then add some ECDSA-testcases to xmlsec. Regards, Wolfgang
--- src/org/apache/xml/security/algorithms/implementations/SignatureECDSA.java.orig 2006-12-17 12:54:30.000000000 +0100 +++ src/org/apache/xml/security/algorithms/implementations/SignatureECDSA.java 2007-04-08 10:00:24.000000000 +0200 @@ -18,6 +18,7 @@ +import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; @@ -33,6 +34,7 @@ import org.apache.xml.security.algorithms.SignatureAlgorithmSpi; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.signature.XMLSignatureException; +import org.apache.xml.security.utils.Base64; /** @@ -52,6 +54,105 @@ private java.security.Signature _signatureAlgorithm = null; /** + * Converts an ASN.1 ECDSA value to a XML Signature ECDSA Value. + * + * The JAVA JCE ECDSA Signature algorithm creates ASN.1 encoded (r,s) value + * pairs; the XML Signature requires the core BigInteger values. + * + * @param asn1Bytes + * @return the decode bytes + * + * @throws IOException + * @see <A HREF="http://www.w3.org/TR/xmldsig-core/#dsa-sha1">6.4.1 DSA</A> + * @see <A HREF="ftp://ftp.rfc-editor.org/in-notes/rfc4050.txt">3.3. ECDSA Signatures</A> + */ + private static byte[] convertASN1toXMLDSIG(byte asn1Bytes[]) + throws IOException { + + byte rLength = asn1Bytes[3]; + int i; + + for (i = rLength; (i > 0) && (asn1Bytes[(4 + rLength) - i] == 0); i--); + + byte sLength = asn1Bytes[5 + rLength]; + int j; + + for (j = sLength; + (j > 0) && (asn1Bytes[(6 + rLength + sLength) - j] == 0); j--); + + if ((asn1Bytes[0] != 48) || (asn1Bytes[1] != asn1Bytes.length - 2) + || (asn1Bytes[2] != 2) || (i > 24) + || (asn1Bytes[4 + rLength] != 2) || (j > 24)) { + throw new IOException("Invalid ASN.1 format of ECDSA signature"); + } + byte xmldsigBytes[] = new byte[48]; + + System.arraycopy(asn1Bytes, (4 + rLength) - i, xmldsigBytes, 24 - i, + i); + System.arraycopy(asn1Bytes, (6 + rLength + sLength) - j, xmldsigBytes, + 48 - j, j); + + return xmldsigBytes; + } + + /** + * Converts a XML Signature ECDSA Value to an ASN.1 DSA value. + * + * The JAVA JCE ECDSA Signature algorithm creates ASN.1 encoded (r,s) value + * pairs; the XML Signature requires the core BigInteger values. + * + * @param xmldsigBytes + * @return the encoded ASN.1 bytes + * + * @throws IOException + * @see <A HREF="http://www.w3.org/TR/xmldsig-core/#dsa-sha1">6.4.1 DSA</A> + * @see <A HREF="ftp://ftp.rfc-editor.org/in-notes/rfc4050.txt">3.3. ECDSA Signatures</A> + */ + private static byte[] convertXMLDSIGtoASN1(byte xmldsigBytes[]) + throws IOException { + + if (xmldsigBytes.length != 48) { + throw new IOException("Invalid XMLDSIG format of ECDSA signature"); + } + + int i; + + for (i = 24; (i > 0) && (xmldsigBytes[24 - i] == 0); i--); + + int j = i; + + if (xmldsigBytes[24 - i] < 0) { + j += 1; + } + + int k; + + for (k = 24; (k > 0) && (xmldsigBytes[48 - k] == 0); k--); + + int l = k; + + if (xmldsigBytes[48 - k] < 0) { + l += 1; + } + + byte asn1Bytes[] = new byte[6 + j + l]; + + asn1Bytes[0] = 48; + asn1Bytes[1] = (byte) (4 + j + l); + asn1Bytes[2] = 2; + asn1Bytes[3] = (byte) j; + + System.arraycopy(xmldsigBytes, 24 - i, asn1Bytes, (4 + j) - i, i); + + asn1Bytes[4 + j] = 2; + asn1Bytes[5 + j] = (byte) l; + + System.arraycopy(xmldsigBytes, 48 - k, asn1Bytes, (6 + j + l) - k, k); + + return asn1Bytes; + } + + /** * Constructor SignatureRSA * * @throws XMLSignatureException @@ -98,9 +199,16 @@ throws XMLSignatureException { try { - return this._signatureAlgorithm.verify(signature); + byte[] jcebytes = SignatureECDSA.convertXMLDSIGtoASN1(signature); + + if (log.isDebugEnabled()) + log.debug("Called ECDSA.verify() on " + Base64.encode(signature)); + + return this._signatureAlgorithm.verify(jcebytes); } catch (SignatureException ex) { throw new XMLSignatureException("empty", ex); + } catch (IOException ex) { + throw new XMLSignatureException("empty", ex); } } @@ -127,9 +235,13 @@ protected byte[] engineSign() throws XMLSignatureException { try { - return this._signatureAlgorithm.sign(); + byte jcebytes[] = this._signatureAlgorithm.sign(); + + return SignatureECDSA.convertASN1toXMLDSIG(jcebytes); } catch (SignatureException ex) { throw new XMLSignatureException("empty", ex); + } catch (IOException ex) { + throw new XMLSignatureException("empty", ex); } }