Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ abstract class AbstractDOMSignatureMethod extends DOMStructure
implements SignatureMethod {

// denotes the type of signature algorithm
enum Type { DSA, RSA, ECDSA, EDDSA, HMAC }
enum Type { DSA, RSA, ECDSA, EDDSA, MLDSA, HMAC }

/**
* Verifies the passed-in signature with the specified key, using the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ public abstract class DOMSignatureMethod extends AbstractDOMSignatureMethod {
"http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519";
static final String ED448 =
"http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448";

// Provisional URIs for ML-DSA (FIPS 204) per draft-eastlake-rfc9231bis-xmlsec-uris
// section 3.3.15. These use the draft's "tbd" placeholder namespace and will need
// to be updated once final URIs are assigned (see SANTUARIO-634).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Draft 9 is now using xmldsig-more instead of tbd so you may as well go ahead and use that, as that will probably be the final URI.

static final String ML_DSA_44 =
"http://www.w3.org/tbd#ml-dsa-44";
static final String ML_DSA_65 =
"http://www.w3.org/tbd#ml-dsa-65";
static final String ML_DSA_87 =
"http://www.w3.org/tbd#ml-dsa-87";
static final String ECDSA_SHA3_224 =
"http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-224";
static final String ECDSA_SHA3_256 =
Expand Down Expand Up @@ -269,6 +279,12 @@ static SignatureMethod unmarshal(Element smElem) throws MarshalException {
return new EDDSA_ED25519(smElem);
} else if (alg.equals(ED448)) {
return new EDDSA_ED448(smElem);
} else if (alg.equals(ML_DSA_44)) {

@seanjmullan seanjmullan Sep 1, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something to think about as a (perhaps subsequent) improvement - we could store all the algorithm Strings in a static Map (then you could just do a Map.containsKey()) and reduce the number of classes by passing in arguments (stored with each map entry). Most of these subclasses are the same except for algorithm names. There are a few special cases though for algs that take parameters but I think the code could be structured differently for those to create them by the caller and only if needed.

return new MLDSA_44(smElem);
} else if (alg.equals(ML_DSA_65)) {
return new MLDSA_65(smElem);
} else if (alg.equals(ML_DSA_87)) {
return new MLDSA_87(smElem);
} else {
throw new MarshalException
("unsupported SignatureMethod algorithm: " + alg);
Expand Down Expand Up @@ -1291,4 +1307,87 @@ String getJCAAlgorithm() {
return "Ed448";
}
}

abstract static class AbstractMLDSASignatureMethod extends DOMSignatureMethod {

AbstractMLDSASignatureMethod(AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
super(params);
}

AbstractMLDSASignatureMethod(Element dmElem) throws MarshalException {
super(dmElem);
}

/** ML-DSA signatures are raw bytes; no reformatting needed. */
@Override
byte[] postSignFormat(Key key, byte[] sig) {
return sig;
}

/** ML-DSA signatures are raw bytes; no reformatting needed. */
@Override
byte[] preVerifyFormat(Key key, byte[] sig) {
return sig;
}

@Override
Type getAlgorithmType() {
return Type.MLDSA;
}
}

static final class MLDSA_44 extends AbstractMLDSASignatureMethod {
MLDSA_44(AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
super(params);
}
MLDSA_44(Element dmElem) throws MarshalException {
super(dmElem);
}
@Override
public String getAlgorithm() {
return ML_DSA_44;
}
@Override
String getJCAAlgorithm() {
return "ML-DSA-44";
}
}

static final class MLDSA_65 extends AbstractMLDSASignatureMethod {
MLDSA_65(AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
super(params);
}
MLDSA_65(Element dmElem) throws MarshalException {
super(dmElem);
}
@Override
public String getAlgorithm() {
return ML_DSA_65;
}
@Override
String getJCAAlgorithm() {
return "ML-DSA-65";
}
}

static final class MLDSA_87 extends AbstractMLDSASignatureMethod {
MLDSA_87(AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
super(params);
}
MLDSA_87(Element dmElem) throws MarshalException {
super(dmElem);
}
@Override
public String getAlgorithm() {
return ML_DSA_87;
}
@Override
String getJCAAlgorithm() {
return "ML-DSA-87";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,13 @@ public SignatureMethod newSignatureMethod(String algorithm,
return new DOMSignatureMethod.EDDSA_ED25519(params);
} else if (algorithm.equals(DOMSignatureMethod.ED448)) {
return new DOMSignatureMethod.EDDSA_ED448(params);
}else {
} else if (algorithm.equals(DOMSignatureMethod.ML_DSA_44)) {
return new DOMSignatureMethod.MLDSA_44(params);
} else if (algorithm.equals(DOMSignatureMethod.ML_DSA_65)) {
return new DOMSignatureMethod.MLDSA_65(params);
} else if (algorithm.equals(DOMSignatureMethod.ML_DSA_87)) {
return new DOMSignatureMethod.MLDSA_87(params);
} else {
throw new NoSuchAlgorithmException("unsupported algorithm");
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/org/apache/xml/security/algorithms/JCEMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,18 @@ public static void registerDefaultAlgorithms() {
XMLSignature.ALGO_ID_SIGNATURE_EDDSA_ED448,
new Algorithm("Ed448", "Ed448", "Signature")
);
algorithmsMap.put(
XMLSignature.ALGO_ID_SIGNATURE_MLDSA_44,
new Algorithm("ML-DSA-44", "ML-DSA-44", "Signature")
);
algorithmsMap.put(
XMLSignature.ALGO_ID_SIGNATURE_MLDSA_65,
new Algorithm("ML-DSA-65", "ML-DSA-65", "Signature")
);
algorithmsMap.put(
XMLSignature.ALGO_ID_SIGNATURE_MLDSA_87,
new Algorithm("ML-DSA-87", "ML-DSA-87", "Signature")
);
algorithmsMap.put(
XMLSignature.ALGO_ID_MAC_HMAC_NOT_RECOMMENDED_MD5,
new Algorithm("", "HmacMD5", "Mac", 0, 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.apache.xml.security.algorithms.implementations.SignatureDSA;
import org.apache.xml.security.algorithms.implementations.SignatureECDSA;
import org.apache.xml.security.algorithms.implementations.SignatureEDDSA;
import org.apache.xml.security.algorithms.implementations.SignatureMLDSA;
import org.apache.xml.security.exceptions.AlgorithmAlreadyRegisteredException;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.signature.XMLSignature;
Expand Down Expand Up @@ -513,6 +514,15 @@ public static void registerDefaultAlgorithms() {
algorithmHash.put(
XMLSignature.ALGO_ID_SIGNATURE_EDDSA_ED448, SignatureEDDSA.SignatureEd448.class
);
algorithmHash.put(
XMLSignature.ALGO_ID_SIGNATURE_MLDSA_44, SignatureMLDSA.SignatureMLDSA44.class
);
algorithmHash.put(
XMLSignature.ALGO_ID_SIGNATURE_MLDSA_65, SignatureMLDSA.SignatureMLDSA65.class
);
algorithmHash.put(
XMLSignature.ALGO_ID_SIGNATURE_MLDSA_87, SignatureMLDSA.SignatureMLDSA87.class
);
algorithmHash.put(
XMLSignature.ALGO_ID_MAC_HMAC_NOT_RECOMMENDED_MD5, IntegrityHmac.IntegrityHmacMD5.class
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.xml.security.algorithms.implementations;

import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.AlgorithmParameterSpec;

import org.apache.xml.security.algorithms.JCEMapper;
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.XMLUtils;

/**
* ML-DSA (FIPS 204) signature algorithm implementation for XML-Dsig.
* Supports ML-DSA-44 (NIST security level 2), ML-DSA-65 (level 3),
* and ML-DSA-87 (level 5). Requires BouncyCastle 1.81+ as the JCA provider.
*/
public abstract class SignatureMLDSA extends SignatureAlgorithmSpi {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

draft-eastlake-rfc9231bis (3.3.15) allows an optional SignatureContext element to be specified as a parameter - however the java.security.Signature APIs do not support that parameter (see the Non-Goals of JEP 497 for more details). I think you should check for that parameter and if specified, throw an Exception that it isn't supported (i.e. bail out and do not verify or sign the signature).


private static final Logger LOG = System.getLogger(SignatureMLDSA.class.getName());

private final Signature signatureAlgorithm;

public SignatureMLDSA() throws XMLSignatureException {
this(null);
}

public SignatureMLDSA(Provider provider) throws XMLSignatureException {
String algorithmID = JCEMapper.translateURItoJCEID(this.engineGetURI());
LOG.log(Level.DEBUG, "Created SignatureMLDSA using {0}", algorithmID);

try {
if (provider == null) {
String providerId = JCEMapper.getProviderId();
if (providerId == null) {
this.signatureAlgorithm = Signature.getInstance(algorithmID);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can dismiss all these alerts.

} else {
this.signatureAlgorithm = Signature.getInstance(algorithmID, providerId);
}
} else {
this.signatureAlgorithm = Signature.getInstance(algorithmID, provider);
}
} catch (NoSuchAlgorithmException | NoSuchProviderException ex) {
Object[] exArgs = { algorithmID, ex.getLocalizedMessage() };
throw new XMLSignatureException("algorithms.NoSuchAlgorithm", exArgs);
}
}

@Override
protected void engineSetParameter(AlgorithmParameterSpec params) throws XMLSignatureException {
try {
this.signatureAlgorithm.setParameter(params);
} catch (InvalidAlgorithmParameterException ex) {
throw new XMLSignatureException(ex);
}
}

@Override
protected boolean engineVerify(byte[] signature) throws XMLSignatureException {
try {
LOG.log(Level.DEBUG, () -> "Called SignatureMLDSA.verify() on " + XMLUtils.encodeToString(signature));
return this.signatureAlgorithm.verify(signature);
} catch (SignatureException ex) {
throw new XMLSignatureException(ex);
}
}

@Override
protected void engineInitVerify(Key publicKey) throws XMLSignatureException {
engineInitVerify(publicKey, signatureAlgorithm);
}

@Override
protected byte[] engineSign() throws XMLSignatureException {
try {
return this.signatureAlgorithm.sign();
} catch (SignatureException ex) {
throw new XMLSignatureException(ex);
}
}

@Override
protected void engineInitSign(Key privateKey, SecureRandom secureRandom)
throws XMLSignatureException {
engineInitSign(privateKey, secureRandom, this.signatureAlgorithm);
}

@Override
protected void engineInitSign(Key privateKey) throws XMLSignatureException {
engineInitSign(privateKey, (SecureRandom) null);
}

@Override
protected void engineUpdate(byte[] input) throws XMLSignatureException {
try {
this.signatureAlgorithm.update(input);
} catch (SignatureException ex) {
throw new XMLSignatureException(ex);
}
}

@Override
protected void engineUpdate(byte input) throws XMLSignatureException {
try {
this.signatureAlgorithm.update(input);
} catch (SignatureException ex) {
throw new XMLSignatureException(ex);
}
}

@Override
protected void engineUpdate(byte[] buf, int offset, int len) throws XMLSignatureException {
try {
this.signatureAlgorithm.update(buf, offset, len);
} catch (SignatureException ex) {
throw new XMLSignatureException(ex);
}
}

@Override
protected String engineGetJCEAlgorithmString() {
return this.signatureAlgorithm.getAlgorithm();
}

@Override
protected String engineGetJCEProviderName() {
return this.signatureAlgorithm.getProvider().getName();
}

@Override
protected void engineSetHMACOutputLength(int HMACOutputLength) throws XMLSignatureException {
throw new XMLSignatureException("algorithms.HMACOutputLengthOnlyForHMAC");
}

@Override
protected void engineInitSign(Key signingKey, AlgorithmParameterSpec algorithmParameterSpec)
throws XMLSignatureException {
throw new XMLSignatureException("algorithms.CannotUseAlgorithmParameterSpecOnEdDSA");
}

/** ML-DSA-44 — NIST security level 2. */
public static class SignatureMLDSA44 extends SignatureMLDSA {
public SignatureMLDSA44() throws XMLSignatureException {
super();
}
public SignatureMLDSA44(Provider provider) throws XMLSignatureException {
super(provider);
}
@Override
public String engineGetURI() {
return XMLSignature.ALGO_ID_SIGNATURE_MLDSA_44;
}
}

/** ML-DSA-65 — NIST security level 3. */
public static class SignatureMLDSA65 extends SignatureMLDSA {
public SignatureMLDSA65() throws XMLSignatureException {
super();
}
public SignatureMLDSA65(Provider provider) throws XMLSignatureException {
super(provider);
}
@Override
public String engineGetURI() {
return XMLSignature.ALGO_ID_SIGNATURE_MLDSA_65;
}
}

/** ML-DSA-87 — NIST security level 5. */
public static class SignatureMLDSA87 extends SignatureMLDSA {
public SignatureMLDSA87() throws XMLSignatureException {
super();
}
public SignatureMLDSA87(Provider provider) throws XMLSignatureException {
super(provider);
}
@Override
public String engineGetURI() {
return XMLSignature.ALGO_ID_SIGNATURE_MLDSA_87;
}
}
}
Loading
Loading