This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-wss4j.git
The following commit(s) were added to refs/heads/master by this push:
new 204418698 Align StaX sender vouches with DOM layer (#720)
204418698 is described below
commit 20441869868e54a38bb68a5534085aa74ba747ce
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Mon Sep 21 11:07:53 2026 +0100
Align StaX sender vouches with DOM layer (#720)
---
.../processor/input/SAMLTokenInputHandler.java | 46 ++++++-
.../processor/input/SenderVouchesIdentityTest.java | 137 +++++++++++++++++++++
2 files changed, 182 insertions(+), 1 deletion(-)
diff --git
a/ws-security-stax/src/main/java/org/apache/wss4j/stax/impl/processor/input/SAMLTokenInputHandler.java
b/ws-security-stax/src/main/java/org/apache/wss4j/stax/impl/processor/input/SAMLTokenInputHandler.java
index c47c247f3..f98b04476 100644
---
a/ws-security-stax/src/main/java/org/apache/wss4j/stax/impl/processor/input/SAMLTokenInputHandler.java
+++
b/ws-security-stax/src/main/java/org/apache/wss4j/stax/impl/processor/input/SAMLTokenInputHandler.java
@@ -525,6 +525,40 @@ public class SAMLTokenInputHandler extends
AbstractInputSecurityHeaderHandler {
* which can not be done until the whole soap-header is processed and we
know that the whole soap-body
* is signed.
*/
+ /**
+ * Whether a security token identifies the sender that signed with it, as
opposed to merely
+ * proving possession of a key.
+ *
+ * A certificate or a public key was checked against the receiver's own
truststore when the
+ * token was verified - see X509SecurityTokenImpl#verify and
+ * RsaKeyValueSecurityTokenImpl#verify, both of which call
Crypto#verifyTrust. A bare symmetric
+ * key was not, and could not be: an EncryptedKey that the sender minted
for itself under the
+ * receiver's public certificate proves possession of a key the sender
chose, and nothing
+ * whatsoever about who sent it.
+ *
+ * Two symmetric cases do carry an identity. A key derived from a
UsernameToken required the
+ * password to derive, and a Kerberos session key came out of a ticket the
KDC issued to a
+ * named client. These are the streaming counterparts of the results the
DOM engine stamps
+ * with TAG_VALIDATED_TOKEN, plus its UT_SIGN case - see
DOMSAMLUtil#establishesSenderIdentity.
+ *
+ * The token is resolved to the root of its key wrapping chain first, so
that a
+ * DerivedKeyToken is judged on whatever it was derived from.
+ */
+ static boolean establishesSenderIdentity(SecurityToken securityToken)
throws XMLSecurityException {
+ SecurityToken rootToken = WSSUtils.getRootToken(securityToken);
+
+ X509Certificate[] x509Certificates = rootToken.getX509Certificates();
+ if (x509Certificates != null && x509Certificates.length > 0) {
+ return true;
+ }
+ if (rootToken.getPublicKey() != null) {
+ return true;
+ }
+
+ return
WSSecurityTokenConstants.USERNAME_TOKEN.equals(rootToken.getTokenType())
+ ||
WSSecurityTokenConstants.KERBEROS_TOKEN.equals(rootToken.getTokenType());
+ }
+
static class SAMLTokenVerifierInputProcessor extends
AbstractInputProcessor implements SecurityEventListener {
private SamlAssertionWrapper samlAssertionWrapper;
@@ -708,10 +742,20 @@ public class SAMLTokenInputHandler extends
AbstractInputSecurityHeaderHandler {
samlTokenSignedElementSecurityEvent =
signedElementSecurityEvent;
}
}
+ // A sender-vouches assertion is only worth as much as
the identity of
+ // whoever vouched for it. An assertion that is itself
signed carries that
+ // backing already, and the message signature merely
binds it to this
+ // message, so any signature will do. An unsigned
assertion has no such
+ // backing: the only party asserting it is whoever
signed the message, so
+ // that signature has to have been made with a
credential whose identity
+ // was actually established.
if (bodySignedPartSecurityEvent != null
&& samlTokenSignedElementSecurityEvent != null
&& bodySignedPartSecurityEvent.getSecurityToken()
- ==
samlTokenSignedElementSecurityEvent.getSecurityToken()) {
+ ==
samlTokenSignedElementSecurityEvent.getSecurityToken()
+ && (samlAssertionWrapper.isSigned()
+ || establishesSenderIdentity(
+
bodySignedPartSecurityEvent.getSecurityToken()))) {
return;
}
methodNotSatisfied = true;
diff --git
a/ws-security-stax/src/test/java/org/apache/wss4j/stax/impl/processor/input/SenderVouchesIdentityTest.java
b/ws-security-stax/src/test/java/org/apache/wss4j/stax/impl/processor/input/SenderVouchesIdentityTest.java
new file mode 100644
index 000000000..30b4d7b97
--- /dev/null
+++
b/ws-security-stax/src/test/java/org/apache/wss4j/stax/impl/processor/input/SenderVouchesIdentityTest.java
@@ -0,0 +1,137 @@
+/**
+ * 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.wss4j.stax.impl.processor.input;
+
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+
+import
org.apache.wss4j.stax.impl.securityToken.EncryptedKeySha1SecurityTokenImpl;
+import org.apache.wss4j.stax.securityToken.WSSecurityTokenConstants;
+import org.apache.xml.security.stax.securityToken.SecurityTokenConstants;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * An unsigned sender-vouches assertion is only as good as the identity of
whoever signed the
+ * message that carries it. These are the cases
SAMLTokenInputHandler#establishesSenderIdentity
+ * has to separate: a credential the receiver made a trust decision about,
against a bare
+ * symmetric key that says nothing about who sent the message.
+ */
+public class SenderVouchesIdentityTest {
+
+ /**
+ * The bypass: an EncryptedKey the sender minted for itself under the
receiver's public
+ * certificate. The HMAC signature it keys verifies perfectly and
identifies nobody.
+ */
+ @Test
+ public void testEncryptedKeyDoesNotVouch() throws Exception {
+ assertFalse(SAMLTokenInputHandler.establishesSenderIdentity(
+ token(WSSecurityTokenConstants.EncryptedKeyToken)));
+ }
+
+ /**
+ * The same key reached through a DerivedKeyToken. The derived token is
judged on what it was
+ * derived from, so this must not become a way round the previous case.
+ */
+ @Test
+ public void testKeyDerivedFromAnEncryptedKeyDoesNotVouch() throws
Exception {
+ EncryptedKeySha1SecurityTokenImpl derivedKey =
token(WSSecurityTokenConstants.DerivedKeyToken);
+
derivedKey.setKeyWrappingToken(token(WSSecurityTokenConstants.EncryptedKeyToken));
+
+
assertFalse(SAMLTokenInputHandler.establishesSenderIdentity(derivedKey));
+ }
+
+ /**
+ * A certificate was checked against the receiver's truststore when the
token was verified.
+ */
+ @Test
+ public void testCertificateVouches() throws Exception {
+ EncryptedKeySha1SecurityTokenImpl securityToken =
token(WSSecurityTokenConstants.X509V3Token);
+ securityToken.setX509Certificates(new X509Certificate[]
{transmitterCertificate()});
+
+
assertTrue(SAMLTokenInputHandler.establishesSenderIdentity(securityToken));
+ }
+
+ /**
+ * So was a bare public key - see RsaKeyValueSecurityTokenImpl#verify.
+ */
+ @Test
+ public void testPublicKeyVouches() throws Exception {
+ EncryptedKeySha1SecurityTokenImpl securityToken =
token(WSSecurityTokenConstants.KeyValueToken);
+ securityToken.setPublicKey(transmitterCertificate().getPublicKey());
+
+
assertTrue(SAMLTokenInputHandler.establishesSenderIdentity(securityToken));
+ }
+
+ /**
+ * A UsernameToken derived key carries no credential for a trust decision,
but deriving it
+ * required the password, so the sender is authenticated all the same.
This is the streaming
+ * counterpart of the DOM engine's UT_SIGN case.
+ */
+ @Test
+ public void testUsernameTokenVouches() throws Exception {
+ assertTrue(SAMLTokenInputHandler.establishesSenderIdentity(
+ token(WSSecurityTokenConstants.USERNAME_TOKEN)));
+ }
+
+ /**
+ * A Kerberos session key came out of a ticket the KDC issued to a named
client.
+ */
+ @Test
+ public void testKerberosTokenVouches() throws Exception {
+ assertTrue(SAMLTokenInputHandler.establishesSenderIdentity(
+ token(WSSecurityTokenConstants.KERBEROS_TOKEN)));
+ }
+
+ /**
+ * A token carrying only a symmetric key of some other provenance - a
SecurityContextToken,
+ * say - is no better placed to vouch than an EncryptedKey is.
+ */
+ @Test
+ public void testOtherSymmetricTokenDoesNotVouch() throws Exception {
+ assertFalse(SAMLTokenInputHandler.establishesSenderIdentity(
+ token(WSSecurityTokenConstants.SECURITY_CONTEXT_TOKEN)));
+ }
+
+ /**
+ * A token of the given type carrying nothing else.
EncryptedKeySha1SecurityTokenImpl is used
+ * only because it is a concrete inbound token whose credentials can be
set from a test; what
+ * is under test is the rule, not the class.
+ */
+ private EncryptedKeySha1SecurityTokenImpl
token(SecurityTokenConstants.TokenType tokenType) {
+ return new EncryptedKeySha1SecurityTokenImpl(null, null,
"sha1-identifier", "token-id") {
+ @Override
+ public SecurityTokenConstants.TokenType getTokenType() {
+ return tokenType;
+ }
+ };
+ }
+
+ private X509Certificate transmitterCertificate() throws Exception {
+ KeyStore keyStore = KeyStore.getInstance("jks");
+
keyStore.load(this.getClass().getClassLoader().getResourceAsStream("transmitter.jks"),
+ "default".toCharArray());
+ X509Certificate certificate =
(X509Certificate)keyStore.getCertificate("transmitter");
+ assertNotNull(certificate, "precondition: the test keystore holds the
transmitter certificate");
+ return certificate;
+ }
+}