|
1 | 1 | /******************************************************************************* |
2 | | - * Copyright (c) 2023 Oak Ridge National Laboratory. |
| 2 | + * Copyright (c) 2023-2025 Oak Ridge National Laboratory. |
3 | 3 | * All rights reserved. This program and the accompanying materials |
4 | 4 | * are made available under the terms of the Eclipse Public License v1.0 |
5 | 5 | * which accompanies this distribution, and is available at |
|
14 | 14 | import java.net.ServerSocket; |
15 | 15 | import java.net.Socket; |
16 | 16 | import java.security.KeyStore; |
| 17 | +import java.security.Principal; |
| 18 | +import java.security.cert.Certificate; |
| 19 | +import java.security.cert.X509Certificate; |
17 | 20 | import java.util.logging.Level; |
18 | 21 |
|
19 | 22 | import javax.naming.ldap.LdapName; |
|
22 | 25 | import javax.net.ssl.SSLContext; |
23 | 26 | import javax.net.ssl.SSLServerSocket; |
24 | 27 | import javax.net.ssl.SSLServerSocketFactory; |
| 28 | +import javax.net.ssl.SSLSession; |
25 | 29 | import javax.net.ssl.SSLSocket; |
26 | 30 | import javax.net.ssl.SSLSocketFactory; |
27 | 31 | import javax.net.ssl.TrustManagerFactory; |
@@ -175,19 +179,84 @@ public static Socket createClientSocket(final InetSocketAddress address, final b |
175 | 179 | socket.setEnabledProtocols(PROTOCOLS); |
176 | 180 | // Handshake starts when first writing, but that might delay SSL errors, so force handshake before we use the socket |
177 | 181 | socket.startHandshake(); |
| 182 | + |
| 183 | + if (logger.isLoggable(Level.FINE)) |
| 184 | + { |
| 185 | + try |
| 186 | + { // Key sections of example server certificate chain: |
| 187 | + // |
| 188 | + // Certificate[1]: |
| 189 | + // Owner: O=xxx.site.org, C=US, CN=ioc |
| 190 | + // Issuer: OU=EPICS Certificate Authority, O=ca.epics.org, C=US, CN=EPICS Root CA |
| 191 | + // |
| 192 | + // #1: ObjectId: 1.3.6.1.4.1.37427.1 Criticality=false |
| 193 | + // 0000: 43 45 52 54 3A 53 54 41 54 55 53 3A 64 30 62 62 CERT:STATUS:d0bb... |
| 194 | + // |
| 195 | + // Certificate[2]: |
| 196 | + // Owner: OU=EPICS Certificate Authority, O=ca.epics.org, C=US, CN=EPICS Root CA |
| 197 | + // Issuer: OU=EPICS Certificate Authority, O=ca.epics.org, C=US, CN=EPICS Root CA |
| 198 | + // |
| 199 | + // #1: ObjectId: 1.3.6.1.4.1.37427.1 Criticality=false |
| 200 | + // 0000: 43 45 52 54 3A 53 54 41 54 55 53 3A 64 30 62 62 CERT:STATUS:d0bb... |
| 201 | + final SSLSession session = socket.getSession(); |
| 202 | + logger.log(Level.FINE, "Server name: '" + getPrincipalCN(session.getPeerPrincipal()) + "'"); |
| 203 | + for (Certificate cert : session.getPeerCertificates()) |
| 204 | + if (cert instanceof X509Certificate x509) |
| 205 | + { |
| 206 | + logger.log(Level.FINE, "* " + x509.getSubjectX500Principal()); |
| 207 | + if (session.getPeerPrincipal().equals(x509.getSubjectX500Principal())) |
| 208 | + logger.log(Level.FINE, " - Server/IOC CN"); |
| 209 | + if (x509.getBasicConstraints() >= 0) |
| 210 | + logger.log(Level.FINE, " - Certificate Authority"); |
| 211 | + logger.log(Level.FINE, " - Expires " + x509.getNotAfter()); |
| 212 | + if (x509.getSubjectX500Principal().equals(x509.getIssuerX500Principal())) |
| 213 | + logger.log(Level.FINE, " - Self-signed"); |
| 214 | + |
| 215 | + byte[] value = x509.getExtensionValue("1.3.6.1.4.1.37427.1"); |
| 216 | + logger.log(Level.FINE, " - Status PV: " + decodeDERString(value)); |
| 217 | + } |
| 218 | + } |
| 219 | + catch (Exception ex) |
| 220 | + { |
| 221 | + logger.log(Level.WARNING, "Error while logging result of handshake with server", ex); |
| 222 | + } |
| 223 | + } |
| 224 | + |
178 | 225 | return socket; |
179 | 226 | } |
180 | 227 |
|
181 | | - /** Get name from local principal |
| 228 | + /** Decode DER String |
| 229 | + * @param der_value |
| 230 | + * @return |
| 231 | + * @throws Exception on error |
| 232 | + */ |
| 233 | + public static String decodeDERString(final byte[] der_value) throws Exception |
| 234 | + { |
| 235 | + if (der_value == null) |
| 236 | + return null; |
| 237 | + // https://en.wikipedia.org/wiki/X.690#DER_encoding: |
| 238 | + // Type 4, length 0..127, characters |
| 239 | + if (der_value.length < 2) |
| 240 | + throw new Exception("Need DER type and size, only received " + der_value.length + " bytes"); |
| 241 | + if (der_value[0] != 0x04) |
| 242 | + throw new Exception(String.format("Expected DER Octet String 0x04, got 0x%02X", der_value[0])); |
| 243 | + if (der_value[1] < 0) |
| 244 | + throw new Exception("Can only handle strings of length 0-127, got " + der_value[1]); |
| 245 | + if (der_value[1] != der_value.length-2) |
| 246 | + throw new Exception("DER string length " + der_value[1] + " but " + (der_value.length-2) + " data items"); |
| 247 | + return new String(der_value, 2, der_value[1]); |
| 248 | + } |
| 249 | + |
| 250 | + /** Get CN from principal |
182 | 251 | * |
183 | | - * @param socket {@link SSLSocket} that may have local principal |
184 | | - * @return Name (without "CN=..") if socket has certificate to authenticate or <code>null</code> |
| 252 | + * @param principal {@link Principal} that may have a CN |
| 253 | + * @return CN value (without "CN=..") or <code>null</code> |
185 | 254 | */ |
186 | | - public static String getLocalPrincipalName(final SSLSocket socket) |
| 255 | + public static String getPrincipalCN(final Principal principal) |
187 | 256 | { |
188 | 257 | try |
189 | 258 | { |
190 | | - final LdapName ldn = new LdapName(socket.getSession().getLocalPrincipal().getName()); |
| 259 | + final LdapName ldn = new LdapName(principal.getName()); |
191 | 260 | for (Rdn rdn : ldn.getRdns()) |
192 | 261 | if (rdn.getType().equals("CN")) |
193 | 262 | return (String) rdn.getValue(); |
@@ -229,14 +298,16 @@ public static TLSHandshakeInfo fromSocket(final SSLSocket socket) throws Excepti |
229 | 298 | { |
230 | 299 | // No way to check if there is peer info (certificates, principal, ...) |
231 | 300 | // other then success vs. exception.. |
232 | | - String name = socket.getSession().getPeerPrincipal().getName(); |
233 | | - if (name.startsWith("CN=")) |
234 | | - name = name.substring(3); |
235 | | - else |
236 | | - logger.log(Level.WARNING, "Peer " + socket.getInetAddress() + " sent '" + name + "' as principal name, expected 'CN=...'"); |
| 301 | + final Principal principal = socket.getSession().getPeerPrincipal(); |
| 302 | + String name = getPrincipalCN(principal); |
| 303 | + if (name == null) |
| 304 | + { |
| 305 | + logger.log(Level.WARNING, "Peer " + socket.getInetAddress() + " sent '" + principal + "' as principal name, expected 'CN=...'"); |
| 306 | + name = principal.getName(); |
| 307 | + } |
| 308 | + |
237 | 309 | final TLSHandshakeInfo info = new TLSHandshakeInfo(); |
238 | 310 | info.name = name; |
239 | | - |
240 | 311 | info.hostname = socket.getInetAddress().getHostName(); |
241 | 312 |
|
242 | 313 | return info; |
|
0 commit comments