oscerd commented on code in PR #27385:
URL: https://github.com/apache/camel/pull/27385#discussion_r4186593311


##########
components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffePeerIdentity.java:
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.camel.component.spiffe;
+
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.List;
+
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+
+import io.spiffe.spiffeid.SpiffeId;
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+
+/**
+ * Extracts the peer SPIFFE ID from the <em>verified</em> TLS peer certificate 
carried on an exchange.
+ * <p>
+ * The identity is taken from the certificate the TLS layer authenticated, 
never from a message header a sender could
+ * set (see CAMEL-24730 for why that distinction matters in this component). 
The supported sources are, in order:
+ * <ol>
+ * <li>an {@link SSLSession} placed on the message by a TLS consumer - 
camel-netty-http stores it under
+ * {@code CamelNettySSLSession}. {@link SSLSession#getPeerCertificates()} 
returns only the peer certificates the
+ * handshake verified, and throws when the peer presented none;</li>
+ * <li>the Servlet container's verified client-certificate request attribute
+ * ({@code jakarta.servlet.request.X509Certificate}, or the legacy {@code 
javax} name), when a consumer surfaces it onto
+ * the message.</li>
+ * </ol>
+ */
+final class SpiffePeerIdentity {
+
+    static final String SPIFFE_URI_SCHEME = "spiffe://";
+    static final String SERVLET_X509_ATTRIBUTE = 
"jakarta.servlet.request.X509Certificate";
+    static final String SERVLET_X509_ATTRIBUTE_LEGACY = 
"javax.servlet.request.X509Certificate";
+
+    // RFC 5280 GeneralName tag for uniformResourceIdentifier, as returned by 
X509Certificate#getSubjectAlternativeNames
+    private static final int SAN_TYPE_URI = 6;
+
+    private SpiffePeerIdentity() {
+    }
+
+    /**
+     * Returns the SPIFFE ID of the verified TLS peer, or {@code null} when 
the exchange carries no verified peer
+     * certificate or the certificate is not a valid SPIFFE X509-SVID.
+     */
+    static SpiffeId fromExchange(Exchange exchange, String sslSessionHeader) {
+        X509Certificate peer = peerCertificate(exchange.getMessage(), 
sslSessionHeader);
+        return peer != null ? fromCertificate(peer) : null;
+    }
+
+    private static X509Certificate peerCertificate(Message message, String 
sslSessionHeader) {
+        SSLSession session = message.getHeader(sslSessionHeader, 
SSLSession.class);
+        if (session != null) {
+            X509Certificate cert = leafOf(peerCertificates(session));
+            if (cert != null) {
+                return cert;
+            }
+        }
+        X509Certificate cert = certificateFromHeader(message, 
SERVLET_X509_ATTRIBUTE);

Review Comment:
   Good catch — fixed in 640f042. I dropped the servlet-attribute fallback 
entirely rather than trying to validate it: a certificate object read from a 
message header carries no proof the TLS layer verified it, and no shipped 
consumer populates `jakarta.servlet.request.X509Certificate` as a Camel header, 
so it was only a latent injection path that contradicted the class contract. 
`SpiffePeerIdentity` now reads solely from the verified `SSLSession` 
(`getPeerCertificates()`), via the configurable `sslSessionHeader`. If a 
servlet/jetty mTLS path is wanted later it should read the cert from the 
verified request, not a header — I can track that as a follow-up.
   
   _Claude Code on behalf of oscerd_
   



##########
components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffePeerIdentity.java:
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.camel.component.spiffe;
+
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.List;
+
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+
+import io.spiffe.spiffeid.SpiffeId;
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+
+/**
+ * Extracts the peer SPIFFE ID from the <em>verified</em> TLS peer certificate 
carried on an exchange.
+ * <p>
+ * The identity is taken from the certificate the TLS layer authenticated, 
never from a message header a sender could
+ * set (see CAMEL-24730 for why that distinction matters in this component). 
The supported sources are, in order:
+ * <ol>
+ * <li>an {@link SSLSession} placed on the message by a TLS consumer - 
camel-netty-http stores it under
+ * {@code CamelNettySSLSession}. {@link SSLSession#getPeerCertificates()} 
returns only the peer certificates the
+ * handshake verified, and throws when the peer presented none;</li>
+ * <li>the Servlet container's verified client-certificate request attribute
+ * ({@code jakarta.servlet.request.X509Certificate}, or the legacy {@code 
javax} name), when a consumer surfaces it onto
+ * the message.</li>
+ * </ol>
+ */
+final class SpiffePeerIdentity {
+
+    static final String SPIFFE_URI_SCHEME = "spiffe://";
+    static final String SERVLET_X509_ATTRIBUTE = 
"jakarta.servlet.request.X509Certificate";
+    static final String SERVLET_X509_ATTRIBUTE_LEGACY = 
"javax.servlet.request.X509Certificate";
+
+    // RFC 5280 GeneralName tag for uniformResourceIdentifier, as returned by 
X509Certificate#getSubjectAlternativeNames
+    private static final int SAN_TYPE_URI = 6;
+
+    private SpiffePeerIdentity() {
+    }
+
+    /**
+     * Returns the SPIFFE ID of the verified TLS peer, or {@code null} when 
the exchange carries no verified peer
+     * certificate or the certificate is not a valid SPIFFE X509-SVID.
+     */
+    static SpiffeId fromExchange(Exchange exchange, String sslSessionHeader) {
+        X509Certificate peer = peerCertificate(exchange.getMessage(), 
sslSessionHeader);
+        return peer != null ? fromCertificate(peer) : null;
+    }
+
+    private static X509Certificate peerCertificate(Message message, String 
sslSessionHeader) {
+        SSLSession session = message.getHeader(sslSessionHeader, 
SSLSession.class);
+        if (session != null) {
+            X509Certificate cert = leafOf(peerCertificates(session));
+            if (cert != null) {
+                return cert;
+            }
+        }
+        X509Certificate cert = certificateFromHeader(message, 
SERVLET_X509_ATTRIBUTE);
+        if (cert == null) {
+            cert = certificateFromHeader(message, 
SERVLET_X509_ATTRIBUTE_LEGACY);
+        }
+        return cert;
+    }
+
+    private static Certificate[] peerCertificates(SSLSession session) {
+        try {
+            return session.getPeerCertificates();
+        } catch (SSLPeerUnverifiedException e) {
+            // the peer did not present a verified certificate - there is no 
identity to authorize on
+            return null;
+        }
+    }
+
+    private static X509Certificate certificateFromHeader(Message message, 
String name) {
+        Object value = message.getHeader(name);
+        if (value instanceof X509Certificate[] certs) {
+            return leafOf(certs);
+        }
+        if (value instanceof Certificate[] certs) {
+            return leafOf(certs);
+        }
+        if (value instanceof X509Certificate cert) {
+            return cert;
+        }
+        return null;
+    }
+
+    private static X509Certificate leafOf(Certificate[] chain) {
+        if (chain == null || chain.length == 0) {
+            return null;
+        }
+        // the peer's own (leaf) certificate is first in the chain returned by 
the JSSE
+        return chain[0] instanceof X509Certificate x509 ? x509 : null;
+    }
+
+    /**
+     * Extracts the SPIFFE ID from a certificate's URI SAN. A SPIFFE X509-SVID 
carries exactly one URI SAN, which is the
+     * SPIFFE ID; a certificate with none, or with more than one SPIFFE URI, 
is not a valid SVID and yields {@code null}
+     * rather than an arbitrary choice between identities.
+     */
+    static SpiffeId fromCertificate(X509Certificate cert) {
+        Collection<List<?>> sans;
+        try {
+            sans = cert.getSubjectAlternativeNames();
+        } catch (CertificateParsingException e) {
+            return null;
+        }
+        if (sans == null) {
+            return null;
+        }
+        String spiffeUri = null;
+        for (List<?> san : sans) {
+            if (san == null || san.size() < 2) {
+                continue;
+            }
+            if (san.get(0) instanceof Integer type && type == SAN_TYPE_URI
+                    && san.get(1) instanceof String uri && 
uri.startsWith(SPIFFE_URI_SCHEME)) {

Review Comment:
   Fixed in 640f042. `fromCertificate` now counts every type-6 (URI) SAN and 
returns null as soon as it sees a second one, then requires the single URI to 
carry the `spiffe://` scheme — so a certificate with e.g. `https://…` plus 
`spiffe://…` is rejected as a non-SVID instead of accepted. Added a regression 
test (`deniesACertificateWithASpiffeAndANonSpiffeUriSan`) next to the existing 
two-SPIFFE-SAN case.
   
   _Claude Code on behalf of oscerd_
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to