From d6c605d818059c7f2c04fdbd4f87b53dfb046914 Mon Sep 17 00:00:00 2001 From: jrihtarsic Date: Tue, 16 Jan 2024 08:10:42 +0100 Subject: [PATCH] Enable EdDSA signature (#260) * Enable EdDSA signature * PR comments --------- Co-authored-by: RIHTARSIC Joze --- .../apache/wss4j/common/WSS4JConstants.java | 7 +- .../wss4j/common/crypto/DERDecoder.java | 2 + .../src/test/resources/keys/wss-eddsa.p12 | Bin 0 -> 1687 bytes .../src/test/resources/wss-eddsa.properties | 4 + .../wss4j/dom/message/WSSecSignature.java | 53 ++++++++ .../wss4j/dom/message/SignatureCertTest.java | 113 +++++++++++++++++- 6 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 ws-security-common/src/test/resources/keys/wss-eddsa.p12 create mode 100644 ws-security-common/src/test/resources/wss-eddsa.properties diff --git a/ws-security-common/src/main/java/org/apache/wss4j/common/WSS4JConstants.java b/ws-security-common/src/main/java/org/apache/wss4j/common/WSS4JConstants.java index 66e3e3d20..af54a2090 100644 --- a/ws-security-common/src/main/java/org/apache/wss4j/common/WSS4JConstants.java +++ b/ws-security-common/src/main/java/org/apache/wss4j/common/WSS4JConstants.java @@ -158,6 +158,11 @@ protected WSS4JConstants() { "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"; public static final String ECDSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512"; + // see RFC 9231 for these algorithm definitions + public static final String ED25519 = + "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519"; + public static final String ED448 = + "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448"; public static final String MGF_SHA1 = "http://www.w3.org/2009/xmlenc11#mgf1sha1"; public static final String MGF_SHA224 = "http://www.w3.org/2009/xmlenc11#mgf1sha224"; @@ -273,4 +278,4 @@ protected WSS4JConstants() { URI_SOAP11_ENV, URI_SOAP12_ENV, }; -} \ No newline at end of file +} diff --git a/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/DERDecoder.java b/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/DERDecoder.java index b08031ff3..78a89267b 100644 --- a/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/DERDecoder.java +++ b/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/DERDecoder.java @@ -45,6 +45,8 @@ public class DERDecoder { public static final byte TYPE_OCTET_STRING = 0x04; /** DER type identifier for a sequence value */ public static final byte TYPE_SEQUENCE = 0x30; + /** DER type identifier for ASN.1 "OBJECT IDENTIFIER" value. */ + public static final byte TYPE_OBJECT_IDENTIFIER = 0x06; private byte[] arr; private int pos; diff --git a/ws-security-common/src/test/resources/keys/wss-eddsa.p12 b/ws-security-common/src/test/resources/keys/wss-eddsa.p12 new file mode 100644 index 0000000000000000000000000000000000000000..3e02ef16efeb0256608772f833ef6860523260ac GIT binary patch literal 1687 zcma)+doC^HRY1KL^On4*J`(uv-?k-=a1*S-|zdp?|DAwJP$}l(4YVxNJbbV1Tb-y zabJW1etFEN`W^uizOd9TrBLE+ATgrg;5D|ZSk*JRrPAV%RIKUOFTOP(fWnF@i9kpY zFNiJ#w-H!~0l00`mc*i9;`Tm%Bo(Z_fxe!u0nPw#fW>V<5dI&8Dlod@M-U<=BVI{R zMERqKZKIQu%d}G|y40V6FssHBGGBA=^XNBgGMb6m3OjP*lX&oR?U%cq*3@_|3ai<$ zUbA}J(%XR?4FmU<4&<&Jr0sZJVpwslVb!b9Auh7Ixl1tc#3JsUr}lfXHAiR9fr|iX zFvQwrIUuX2D=`9z+j<{~-{4JV@r7`CiYDV37HSHl%k@0J4#| zn-1_JM1-*+dVjc~4|gUhm!i(mIci0@_%n!_4}uRVJF*%_RK{f+ir$c-$b2402s;C! zZ!IZ{A4`@CAUOusZqFF0mTXDlzZ+^>^TEw0xMle+(dt8eO2<_1Lu5_SnGD9RfozsA zL79=%Y19~l_R0rdSNV784a3Xw*d9jbLUQ)hTclf+jGGjDj`HU>#N||Zxt|*tk)T9D zAK2VD3p9*Q*xN^;?KPJI@8_X7G*b#un-{lteaYuQ2X88Yx-5Oit(7V+`_{q+F5b zXk)_Z%GaZ}p_eqypnt=2^>Q-}(;eXHpH6;wRy&iH)ckbZrFF3jrRvwuBWP&Glm;^7 z90nY$XJP{BcdpnL)23JMWxYr3Umf)Ey)MmYtwIcg-1g!b34#ze*;v$FD(Mn2)hfO1 zch2Wz-XZZB5l2{1uXeN!%I1_xo%6KLn3K(3dd0qh!Ie{^d5hA+dn#yh6#44@&V-ak za3r3tE>m#!-mmlyFKO4u;kQw&YBYl>HTPbtEk?9l+IG=q>iJl%+f_rp1MwdHFseG` zxOdnBk{Fj1{wL?lVP#!sOrKMjZ~>EJQW?LBm+5PMBP-kUaq%w`g;{X=OChIHv+o$e z5kYlqkJw&zIkh|J?7q?k$JY_w*8)D`aof0iNoHd;#0di1gkTs{!EAVOv*odeYWey{ zrmugGMHzLlM=M84a9g}IS$>gxRN=JDRrdK#{hekDS&xJvF7f`>C9Od>F_8tc4yT4o z2<*w|SJm9!8~$5;)O>-IM|k4RP&M(q_WR~d;z+WO@~4g+>V5Hc4y;`jzum#u?EDZjrrwF-OeCycZnyHsy&%ZcUMi)XEBx4KNOr9(w6}Uy<=b za=c^$y7kjj>KZFU7A>2f*_3<5z(|AN&tcTiSlZIB#x3Uhj$hIXhtVdxX{xx%&{ahQ z#~oA4soYJ#csr)2Y13{Mb!>spmefJit2IKTAdfV>G+k3vuEwXEX_c#nt*Iu?%f!mD zUCM3suefx#KU8<%M-I7%!w^<)n|OynB8UZrzpra30EfU->}ZK@dJ>YOS;s?#V_WZ~ tH#{JZnjtZ?;#9uKx%D&>3^k!*2lZc9mh#js8;xBtA!;?hAblP2zX2|;x99)> literal 0 HcmV?d00001 diff --git a/ws-security-common/src/test/resources/wss-eddsa.properties b/ws-security-common/src/test/resources/wss-eddsa.properties new file mode 100644 index 000000000..5f97a4a2f --- /dev/null +++ b/ws-security-common/src/test/resources/wss-eddsa.properties @@ -0,0 +1,4 @@ +org.apache.wss4j.crypto.provider=org.apache.wss4j.common.crypto.Merlin +org.apache.wss4j.crypto.merlin.keystore.type=PKCS12 +org.apache.wss4j.crypto.merlin.keystore.password=security +org.apache.wss4j.crypto.merlin.keystore.file=keys/wss-eddsa.p12 diff --git a/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecSignature.java b/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecSignature.java index b296ff901..74fbd1b4d 100644 --- a/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecSignature.java +++ b/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecSignature.java @@ -21,6 +21,7 @@ import java.security.NoSuchProviderException; import java.security.Provider; +import java.security.PublicKey; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -46,6 +47,7 @@ import org.apache.wss4j.common.WSS4JConstants; import org.apache.wss4j.common.crypto.Crypto; import org.apache.wss4j.common.crypto.CryptoType; +import org.apache.wss4j.common.crypto.DERDecoder; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.common.token.BinarySecurity; import org.apache.wss4j.common.token.DOMX509Data; @@ -887,6 +889,12 @@ private X509Certificate[] getSigningCerts() throws WSSecurityException { sigAlgo = WSConstants.RSA; } else if (pubKeyAlgo.equalsIgnoreCase("EC")) { sigAlgo = WSConstants.ECDSA_SHA256; + } else if (pubKeyAlgo.equalsIgnoreCase("Ed25519")) { + sigAlgo = WSConstants.ED25519; + } else if (pubKeyAlgo.equalsIgnoreCase("ED448")) { + sigAlgo = WSConstants.ED448; + } else if (pubKeyAlgo.equalsIgnoreCase("EdDSA")) { + sigAlgo = getSigAlgorithmForEdDSAKey(certs[0].getPublicKey()); } else { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, @@ -898,6 +906,51 @@ private X509Certificate[] getSigningCerts() throws WSSecurityException { return certs; } + /** + * The method returns EdDSA signature algorithm URI for public key type (Ed25519 or Ed448). + * + * @param publicKey the public key to get the algorithm from + * @return the signature algorithm URI (ED25519 or ED448) for the EdDSA public key + * @throws WSSecurityException if the algorithm cannot be determined + */ + private static String getSigAlgorithmForEdDSAKey(PublicKey publicKey) throws WSSecurityException { + + if (!"x.509".equalsIgnoreCase(publicKey.getFormat())) { + throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "unknownAlgorithm", + new Object[]{"Unknown cert format!"}); + } + + DERDecoder decoder = new DERDecoder(publicKey.getEncoded()); + // find TYPE_OBJECT_IDENTIFIER (OID) for the public key algorithm + decoder.expect(decoder.TYPE_SEQUENCE); + decoder.getLength(); + decoder.expect(decoder.TYPE_SEQUENCE); + decoder.getLength(); + decoder.expect(decoder.TYPE_OBJECT_IDENTIFIER); + int size = decoder.getLength(); + if (size != 3) { + LOG.debug("Invalid ECDSA Public key OID byte size: [{}]", size); + throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, "invalidCert"); + } + + // The first two nodes 1.3 of the OID are encoded onto a single byte. The first node is multiplied by 40 + // and the result is added to the value of the second node 3 which gives 43 or 0x2B. + decoder.expect(43); + // The second byte is expected 101 from the OID 1.3.101 (EdDSA) + decoder.expect(101); + // The third byte defines algorithm 112 is for Ed25519 and 113 is for Ed448 + byte algDef = decoder.getBytes(1)[0]; + switch (algDef) { + case 112: + return WSConstants.ED25519; + case 113: + return WSConstants.ED448; + default: + throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, "unknownAlgorithm", + new Object[]{"Invalid ECDSA Public key OID!"}); + } + } + public boolean isIncludeSignatureToken() { return includeSignatureToken; } diff --git a/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/SignatureCertTest.java b/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/SignatureCertTest.java index 985424272..81c1777c3 100644 --- a/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/SignatureCertTest.java +++ b/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/SignatureCertTest.java @@ -36,16 +36,17 @@ import org.apache.wss4j.dom.handler.WSHandlerConstants; import org.apache.wss4j.dom.handler.WSHandlerResult; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.jupiter.api.Test; import org.w3c.dom.Document; +import javax.security.auth.x500.X500Principal; +import java.security.Security; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.Properties; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; /** * This is a test for WSS-40. Essentially it just tests that a message is signed using a @@ -81,11 +82,18 @@ public class SignatureCertTest { private WSSecurityEngine secEngine = new WSSecurityEngine(); private Crypto crypto; private Crypto cryptoCA; + private boolean isJDK16up; public SignatureCertTest() throws Exception { WSSConfig.init(); crypto = CryptoFactory.getInstance("wss40.properties"); cryptoCA = CryptoFactory.getInstance("wss40CA.properties"); + try { + int javaVersion = Integer.getInteger("java.specification.version", 0); + isJDK16up = javaVersion >= 16; + } catch (NumberFormatException ex) { + LOG.warn("Error in retrieving the java version: [{}]", ex.getMessage()); + } } /** @@ -340,6 +348,103 @@ public void testExpiredCertInKeystore() throws Exception { } } + /** + * The Ed25519 KeyValue test. + */ + @Test + public void testED25519SignatureDirectReference() throws Exception { + try { + // not needed after JDK 16 + if (!isJDK16up) { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + } + + Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG); + WSSecHeader secHeader = new WSSecHeader(doc); + secHeader.insertSecurityHeader(); + + Crypto ed_crypto = CryptoFactory.getInstance("wss-eddsa.properties"); + + WSSecSignature builder = new WSSecSignature(secHeader); + builder.setUserInfo("ed25519", "security"); + builder.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE); + Document signedDoc = builder.build(ed_crypto); + // test the algorithm attribute + String outputString = + XMLUtils.prettyDocumentToString(signedDoc); + if (LOG.isDebugEnabled()) { + LOG.debug(outputString); + } + + assertTrue(outputString.contains("Algorithm=\"http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519\"")); + + final WSHandlerResult results = verify(signedDoc, ed_crypto); + + WSSecurityEngineResult actionResult = + results.getActionResults().get(WSConstants.SIGN).get(0); + assertNotNull(actionResult); + + java.security.Principal principal = + (java.security.Principal) actionResult.get(WSSecurityEngineResult.TAG_PRINCIPAL); + assertTrue(principal instanceof X500Principal); + X500Principal x500Principal = (X500Principal) principal; + assertEquals(new X500Principal("CN=ED25519,O=EDELIVERY,C=EU"), x500Principal); + + } finally { + if (!isJDK16up) { + Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); + } + } + } + + /** + * Successful ECKeyValue test. + */ + @Test + public void testED448KeyValue() throws Exception { + try { + // not needed after JDK 16 + if (!isJDK16up) { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + } + + Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG); + WSSecHeader secHeader = new WSSecHeader(doc); + secHeader.insertSecurityHeader(); + + Crypto ed_crypto = CryptoFactory.getInstance("wss-eddsa.properties"); + + WSSecSignature builder = new WSSecSignature(secHeader); + builder.setUserInfo("ed448", "security"); + builder.setKeyIdentifierType(WSConstants.X509_KEY_IDENTIFIER); + Document signedDoc = builder.build(ed_crypto); + + String outputString = + XMLUtils.prettyDocumentToString(signedDoc); + if (LOG.isDebugEnabled()) { + LOG.debug(outputString); + } + + assertTrue(outputString.contains("Algorithm=\"http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448\"")); + + final WSHandlerResult results = verify(signedDoc, ed_crypto); + + WSSecurityEngineResult actionResult = + results.getActionResults().get(WSConstants.SIGN).get(0); + assertNotNull(actionResult); + + java.security.Principal principal = + (java.security.Principal) actionResult.get(WSSecurityEngineResult.TAG_PRINCIPAL); + assertTrue(principal instanceof X500Principal); + X500Principal x500Principal = (X500Principal) principal; + assertEquals(new X500Principal("CN=ED448, O=EDELIVERY, C=EU"), x500Principal); + } finally { + if (!isJDK16up) { + Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); + } + } + } + /** * Verifies the soap envelope *

@@ -361,4 +466,4 @@ private WSHandlerResult verify(Document doc, Crypto crypto) throws Exception { } -} \ No newline at end of file +}