Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable EdDSA signature #260

Merged
merged 2 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
}


}
}