Skip to content

Commit

Permalink
Enable EdDSA signature (#260)
Browse files Browse the repository at this point in the history
* Enable EdDSA signature

* PR comments

---------

Co-authored-by: RIHTARSIC Joze <[email protected]>
  • Loading branch information
jrihtarsic and RIHTARSIC Joze authored Jan 16, 2024
1 parent bedc338 commit aec28eb
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -273,4 +278,4 @@ protected WSS4JConstants() {
URI_SOAP11_ENV,
URI_SOAP12_ENV,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Binary file not shown.
4 changes: 4 additions & 0 deletions ws-security-common/src/test/resources/wss-eddsa.properties
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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());
}
}

/**
Expand Down Expand Up @@ -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
* <p/>
Expand All @@ -361,4 +466,4 @@ private WSHandlerResult verify(Document doc, Crypto crypto) throws Exception {
}


}
}

0 comments on commit aec28eb

Please sign in to comment.