I need to sign XML in Java using Digital Signatures.
I have a key pair using signature algorithm RSASSA-PSS:
See this code here from the OpenJDK test framework: https://github.com/openjdk/jdk/blob/master/test/lib/jdk/test/lib/security/XMLUtils.java
I'm using this utility to load the value:
Document doc = XMLUtils.string2doc("<a><b>Text</b>Raw</a>");
KeyPairGenerator instance = KeyPairGenerator.getInstance("RSASSA-PSS");
instance.initialize(2048);
KeyPair keyPair = instance.generateKeyPair();
PSSParameterSpec pspec = new PSSParameterSpec("SHA-384", "MGF1",
MGF1ParameterSpec.SHA512, 48, TRAILER_FIELD_BC);
Document signed = XMLUtils.signer(keyPair.getPrivate(), keyPair.getPublic())
.dm(DigestMethod.SHA384)
.sm(SignatureMethod.RSA_PSS, new RSAPSSParameterSpec(pspec))
.sign(doc);
System.out.println(XMLUtils.doc2string(signed));
System.out.println("Good? " + XMLUtils.validator().validate(signed, keyPair.getPublic()));
Unfortunately, this is not working
Exception in thread "main" java.security.KeyException: unsupported key algorithm: RSASSA-PSS
at java.xml.crypto/org.jcp.xml.dsig.internal.dom.DOMKeyInfoFactory.newKeyValue(DOMKeyInfoFactory.java:85)
because:
@Override
public KeyValue newKeyValue(PublicKey key) throws KeyException {
String algorithm = key.getAlgorithm();
if ("DSA".equals(algorithm)) {
return new DOMKeyValue.DSA((DSAPublicKey) key);
} else if ("RSA".equals(algorithm)) {
return new DOMKeyValue.RSA((RSAPublicKey) key);
} else if ("EC".equals(algorithm)) {
return new DOMKeyValue.EC((ECPublicKey) key);
} else {
throw new KeyException("unsupported key algorithm: " + algorithm);
}
}
It does not allow "RSASSA-PSS".
It appears that if it were to be changed to:
} else if ("RSASSA-PSS".equals(algorithm) || "RSA".equals(algorithm)) {
return new DOMKeyValue.RSA((RSAPublicKey) key);
That appears to work? Or maybe it just looks like it's working.
If this were changed KeyPairGenerator.getInstance("RSA")
that also works but then it wouldn't be the algorithm that I need.
Here is a recent change to the JDK related to this: https://github.com/openjdk/jdk/commit/8dbf7aa1#diff-fa025c90d09b130fe3a8da89b4ebc5fc3ec157ded54b858dabe8241d383880abR66
I notice that they convert the public key into a "self certificate" before using it in the XML signature.
X509Certificate cert = g.getSelfCertificate(new X500Name("CN=Me"), 100);
Those classes aren't public to Java so I can't use them. What is this doing exactly?
How do I properly sign an XML Signature using the RSASSA-PSS algorithm?
For KeyPairGenerator
, the SunRsaSign provider associates the generated RSA keys with the OID rsaEncryption (for getInstance("RSA")
) and rsassa-pss (for getInstance("RSASSA-PSS")
). The former generally identifies a (private or public) RSA key, the latter more specifically a key that is intended for signing with the RSASSA-PSS scheme.
However, the OID only specifies the context of the RSA key, technically the keys do not differ, e.g. in principle a public rsassa-pss key could be used for rsaes-oaep encryption. For security reasons, however, the keys should be used consistently with the OID.
For this reason, it is also possible for RSASSA-PSS to use getInstance("RSA")
instead of getInstance("RSASSA-PSS")
for key derivation and the existing implementation of the library is sufficient. The test cases of the library, which also use getInstance("RSA")
for key generation, even show that this is the intended method.
Since the OID does not technically determine the algorithm or padding, this must be done elsewhere. In the posted code this happens with sm(SignatureMethod.RSA_PSS, new RSAPSSParameterSpec(pspec))
where pspec
is of type PSSParameterSpec
and specifies the PSS parameters.