-
Notifications
You must be signed in to change notification settings - Fork 71
Add ML-DSA (FIPS 204) post-quantum signature support #651
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
base: main
Are you sure you want to change the base?
Changes from all commits
042d5f7
43b5317
7401714
58ef7d8
036194d
5898249
dcd65cd
2859acc
d47f99c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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). | ||
| 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 = | ||
|
|
@@ -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)) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| 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); | ||
|
|
@@ -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 |
|---|---|---|
| @@ -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 { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. draft-eastlake-rfc9231bis (3.3.15) allows an optional |
||
|
|
||
| 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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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-moreinstead oftbdso you may as well go ahead and use that, as that will probably be the final URI.