This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch 3_0_x-fixes in repository https://gitbox.apache.org/repos/asf/ws-wss4j.git
The following commit(s) were added to refs/heads/3_0_x-fixes by this push: new f0e726731 WSS-717 Encryption with key identifier X509SKI (#438) f0e726731 is described below commit f0e72673123d5cb94692955bb9aa73dc16193d0a Author: Robin K. <139983390+koes-sop...@users.noreply.github.com> AuthorDate: Wed Feb 5 12:06:11 2025 +0100 WSS-717 Encryption with key identifier X509SKI (#438) * WSS-717 Added support for encryption with key identifier type X509_SKI - a base64 encoded plain X.509 SKI extension value wrapped in X509Data as a direct child of a KeyInfo element * Merge and compatibility adjustments after #418 * Enhanced unit test for X509SKI * Unused import * DOMX509SKI: restore "final" for instance variable skiBytes * Restored the "else" clause in WSSecEncryptedKey#createEncryptedKeyElement, added a new "else if" clause for X509SKI, extracted the KeyInfo generation into a new method as it is needed for X509SKI and the "old" cases --- .../org/apache/wss4j/common/token/DOMX509Data.java | 10 ++++ .../org/apache/wss4j/common/token/DOMX509SKI.java | 17 +++++++ .../java/org/apache/wss4j/dom/WSConstants.java | 6 +++ .../wss4j/dom/message/WSSecEncryptedKey.java | 57 ++++++++++++++-------- .../apache/wss4j/dom/message/EncryptionTest.java | 46 +++++++++++++++++ 5 files changed, 115 insertions(+), 21 deletions(-) diff --git a/ws-security-common/src/main/java/org/apache/wss4j/common/token/DOMX509Data.java b/ws-security-common/src/main/java/org/apache/wss4j/common/token/DOMX509Data.java index d21f00c7b..93a934d25 100644 --- a/ws-security-common/src/main/java/org/apache/wss4j/common/token/DOMX509Data.java +++ b/ws-security-common/src/main/java/org/apache/wss4j/common/token/DOMX509Data.java @@ -58,6 +58,16 @@ public final class DOMX509Data { element.appendChild(domIssuerSerial.getElement()); } + /** + * Constructor. + */ + public DOMX509Data(Document doc, DOMX509SKI x509SKI) { + element = + doc.createElementNS(WSS4JConstants.SIG_NS, "ds:X509Data"); + + element.appendChild(x509SKI.getElement()); + } + /** * Return true if this X509Data element contains a X509IssuerSerial element */ diff --git a/ws-security-common/src/main/java/org/apache/wss4j/common/token/DOMX509SKI.java b/ws-security-common/src/main/java/org/apache/wss4j/common/token/DOMX509SKI.java index cf05cf486..b9bfc2476 100644 --- a/ws-security-common/src/main/java/org/apache/wss4j/common/token/DOMX509SKI.java +++ b/ws-security-common/src/main/java/org/apache/wss4j/common/token/DOMX509SKI.java @@ -19,10 +19,15 @@ package org.apache.wss4j.common.token; +import org.apache.wss4j.common.WSS4JConstants; +import org.apache.wss4j.common.crypto.BouncyCastleUtils; import org.apache.wss4j.common.util.DOM2Writer; +import org.w3c.dom.Document; import org.apache.wss4j.common.util.XMLUtils; import org.w3c.dom.Element; +import java.security.cert.X509Certificate; + /** * An X.509 SKI token. @@ -31,6 +36,18 @@ public final class DOMX509SKI { private final Element element; private final byte[] skiBytes; + /** + * Constructor. + */ + public DOMX509SKI(Document doc, X509Certificate remoteCertificate) { + skiBytes = BouncyCastleUtils.getSubjectKeyIdentifierBytes(remoteCertificate); + + element = doc.createElementNS(WSS4JConstants.SIG_NS, "ds:X509SKI"); + element.setTextContent( + org.apache.xml.security.utils.XMLUtils.encodeToString(skiBytes + )); + } + /** * Constructor. */ diff --git a/ws-security-dom/src/main/java/org/apache/wss4j/dom/WSConstants.java b/ws-security-dom/src/main/java/org/apache/wss4j/dom/WSConstants.java index 34f92ef71..fcaa1dc95 100644 --- a/ws-security-dom/src/main/java/org/apache/wss4j/dom/WSConstants.java +++ b/ws-security-dom/src/main/java/org/apache/wss4j/dom/WSConstants.java @@ -341,6 +341,12 @@ public final class WSConstants extends WSS4JConstants { */ public static final int ISSUER_SERIAL_QUOTE_FORMAT = 15; + /** + * <code>X509_SKI</code> is used to set a ds:X509Data/ds:KeyValue element to refer to + * the base64 encoded plain value of a X509 V.3 SubjectKeyIdentifier extension + */ + public static final int X509_SKI = 16; + /* * The following values are bits that can be combined to form a set. * Be careful when adding new constants. diff --git a/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecEncryptedKey.java b/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecEncryptedKey.java index 204c311a4..77e8c80d1 100644 --- a/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecEncryptedKey.java +++ b/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecEncryptedKey.java @@ -260,10 +260,10 @@ public class WSSecEncryptedKey extends WSSecBase { } /** - * Now we need to setup the EncryptedKey header block: + * Now we need to set up the EncryptedKey header block: * 1) create a EncryptedKey element and set a wsu:Id for it - * 2) Generate ds:KeyInfo element, this wraps the wsse:SecurityTokenReference - * 3) Create and set up the SecurityTokenReference according to the keyIdentifier parameter + * 2) Generate ds:KeyInfo element + * 3) Create and set up the ds:KeyInfo child element - this can either be a SecurityTokenReference or X509Data/X509SKI * 4) Create the CipherValue element structure and insert the encrypted session key */ protected void createEncryptedKeyElement(X509Certificate remoteCert, Crypto crypto, KeyAgreementParameters dhSpec) @@ -276,6 +276,12 @@ public class WSSecEncryptedKey extends WSSecBase { if (customEKKeyInfoElement != null) { encryptedKeyElement.appendChild(getDocument().adoptNode(customEKKeyInfoElement)); + } else if (keyIdentifierType == WSConstants.X509_SKI) { + DOMX509SKI x509SKI = new DOMX509SKI(getDocument(), remoteCert); + DOMX509Data x509Data = new DOMX509Data(getDocument(), x509SKI); + + Element keyInfoElement = createKeyInfoElement(x509Data.getElement(), dhSpec); + encryptedKeyElement.appendChild(keyInfoElement); } else { SecurityTokenReference secToken = new SecurityTokenReference(getDocument()); if (addWSUNamespace) { @@ -378,29 +384,38 @@ public class WSSecEncryptedKey extends WSSecBase { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "unsupportedKeyId", new Object[] {keyIdentifierType}); } - Element keyInfoElement = + + Element keyInfoElement = createKeyInfoElement(secToken.getElement(), dhSpec); + encryptedKeyElement.appendChild(keyInfoElement); + } + } + + /** + * Method builds and returns a ds:KeyInfo element wrapping the provided child element. + */ + private Element createKeyInfoElement(Element childElement, KeyAgreementParameters dhSpec) throws WSSecurityException { + Element keyInfoElement = getDocument().createElementNS( - WSConstants.SIG_NS, WSConstants.SIG_PREFIX + ":" + WSConstants.KEYINFO_LN + WSConstants.SIG_NS, WSConstants.SIG_PREFIX + ":" + WSConstants.KEYINFO_LN ); - keyInfoElement.setAttributeNS( + keyInfoElement.setAttributeNS( WSConstants.XMLNS_NS, "xmlns:" + WSConstants.SIG_PREFIX, WSConstants.SIG_NS - ); - if (isKeyAgreementConfigured(keyAgreementMethod)) { - try { - AgreementMethodImpl agreementMethod = new AgreementMethodImpl(getDocument(), dhSpec); - agreementMethod.getRecipientKeyInfo().addUnknownElement(secToken.getElement()); - Element agreementMethodElement = agreementMethod.getElement(); - keyInfoElement.appendChild(agreementMethodElement); - } catch (XMLSecurityException e) { - throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "unsupportedKeyId", - new Object[] {keyIdentifierType}); - } - - } else { - keyInfoElement.appendChild(secToken.getElement()); + ); + if (isKeyAgreementConfigured(keyAgreementMethod)) { + try { + AgreementMethodImpl agreementMethod = new AgreementMethodImpl(getDocument(), dhSpec); + agreementMethod.getRecipientKeyInfo().addUnknownElement(childElement); + Element agreementMethodElement = agreementMethod.getElement(); + keyInfoElement.appendChild(agreementMethodElement); + } catch (XMLSecurityException e) { + throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "unsupportedKeyId", + new Object[] {keyIdentifierType}); } - encryptedKeyElement.appendChild(keyInfoElement); + + } else { + keyInfoElement.appendChild(childElement); } + return keyInfoElement; } /** diff --git a/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/EncryptionTest.java b/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/EncryptionTest.java index cbcf1ada0..6e44a441b 100644 --- a/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/EncryptionTest.java +++ b/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/EncryptionTest.java @@ -575,6 +575,52 @@ public class EncryptionTest { verify(encryptedDoc, encCrypto, keystoreCallbackHandler); } + + /** + * Test that encrypts a WS-Security envelope. + * The test uses the X509_SKI key identifier type. + */ + @Test + public void testEncryptionX509SKI() throws Exception { + Crypto encCrypto = CryptoFactory.getInstance("wss-ecdh.properties"); + + Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG); + WSSecHeader secHeader = new WSSecHeader(doc); + secHeader.insertSecurityHeader(); + + WSSecEncrypt builder = new WSSecEncrypt(secHeader); + builder.setUserInfo("secp256r1"); + builder.setKeyEncAlgo(WSConstants.KEYWRAP_AES128); + builder.setKeyAgreementMethod(WSConstants.AGREEMENT_METHOD_ECDH_ES); + builder.setKeyDerivationMethod(WSConstants.KEYDERIVATION_CONCATKDF); + builder.setDigestAlgorithm(WSS4JConstants.SHA256); + builder.setKeyIdentifierType(WSConstants.X509_SKI); + + LOG.info("Before Encrypting X509SKI"); + KeyGenerator keyGen = KeyUtils.getKeyGenerator(WSConstants.AES_128_GCM); + SecretKey symmetricKey = keyGen.generateKey(); + + Document encryptedDoc = builder.build(encCrypto, symmetricKey); + LOG.info("After Encrypting X509SKI"); + + String outputString = + XMLUtils.prettyDocumentToString(encryptedDoc); + + if (LOG.isDebugEnabled()) { + LOG.debug("Encrypted message with X509SKI:"); + LOG.debug(outputString); + } + + assertTrue(outputString.contains("X509Data")); + assertTrue(outputString.contains("X509SKI")); + + RequestData data = new RequestData(); + data.setCallbackHandler(keystoreCallbackHandler); + data.setDecCrypto(encCrypto); + data.setIgnoredBSPRules(Collections.singletonList(BSPRule.R5426)); + new WSSecurityEngine().processSecurityHeader(encryptedDoc, data); + } + /** * Test that encrypts using EncryptedKeySHA1, where it uses a symmetric key, rather than a * generated session key which is then encrypted using a public key.