1 /* 2 * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.security.ssl; 27 28 import java.io.ByteArrayInputStream; 29 import java.io.ByteArrayOutputStream; 30 import java.io.IOException; 31 import java.io.PrintStream; 32 import java.lang.System.Logger; 33 import java.lang.System.Logger.Level; 34 import java.nio.ByteBuffer; 35 import java.security.cert.Certificate; 36 import java.security.cert.Extension; 37 import java.security.cert.X509Certificate; 38 import java.text.MessageFormat; 39 import java.time.Instant; 40 import java.time.ZoneId; 41 import java.time.format.DateTimeFormatter; 42 import java.util.Locale; 43 import java.util.Map; 44 import java.util.ResourceBundle; 45 46 import sun.security.action.GetPropertyAction; 47 import sun.security.util.HexDumpEncoder; 48 import sun.security.x509.*; 49 50 import static java.nio.charset.StandardCharsets.UTF_8; 51 52 /** 53 * Implementation of SSL logger. 54 * 55 * If the system property "javax.net.debug" is not defined, the debug logging 56 * is turned off. If the system property "javax.net.debug" is defined as 57 * empty, the debug logger is specified by System.getLogger("javax.net.ssl"), 58 * and applications can customize and configure the logger or use external 59 * logging mechanisms. If the system property "javax.net.debug" is defined 60 * and non-empty, a private debug logger implemented in this class is used. 61 */ 62 public final class SSLLogger { 63 private static final System.Logger logger; 64 private static final String property; 65 public static final boolean isOn; 66 67 static { 68 String p = GetPropertyAction.privilegedGetProperty("javax.net.debug"); 69 if (p != null) { 70 if (p.isEmpty()) { 71 property = ""; 72 logger = System.getLogger("javax.net.ssl"); 73 } else { 74 property = p.toLowerCase(Locale.ENGLISH); 75 if (property.equals("help")) { 76 help(); 77 } 78 79 logger = new SSLConsoleLogger("javax.net.ssl", p); 80 } 81 isOn = true; 82 } else { 83 property = null; 84 logger = null; 85 isOn = false; 86 } 87 } 88 89 private static void help() { 90 System.err.println(); 91 System.err.println("help print the help messages"); 92 System.err.println("expand expand debugging information"); 93 System.err.println(); 94 System.err.println("all turn on all debugging"); 95 System.err.println("ssl turn on ssl debugging"); 96 System.err.println(); 97 System.err.println("The following can be used with ssl:"); 98 System.err.println("\trecord enable per-record tracing"); 99 System.err.println("\thandshake print each handshake message"); 100 System.err.println("\tkeygen print key generation data"); 101 System.err.println("\tsession print session activity"); 102 System.err.println("\tdefaultctx print default SSL initialization"); 103 System.err.println("\tsslctx print SSLContext tracing"); 104 System.err.println("\tsessioncache print session cache tracing"); 105 System.err.println("\tkeymanager print key manager tracing"); 106 System.err.println("\ttrustmanager print trust manager tracing"); 107 System.err.println("\tpluggability print pluggability tracing"); 108 System.err.println(); 109 System.err.println("\thandshake debugging can be widened with:"); 110 System.err.println("\tdata hex dump of each handshake message"); 111 System.err.println("\tverbose verbose handshake message printing"); 112 System.err.println(); 113 System.err.println("\trecord debugging can be widened with:"); 114 System.err.println("\tplaintext hex dump of record plaintext"); 115 System.err.println("\tpacket print raw SSL/TLS packets"); 116 System.err.println(); 117 System.exit(0); 118 } 119 120 /** 121 * Return true if the "javax.net.debug" property contains the 122 * debug check points, or System.Logger is used. 123 */ 124 public static boolean isOn(String checkPoints) { 125 if (property == null) { // debugging is turned off 126 return false; 127 } else if (property.isEmpty()) { // use System.Logger 128 return true; 129 } // use provider logger 130 131 String[] options = checkPoints.split(","); 132 for (String option : options) { 133 option = option.trim(); 134 if (!SSLLogger.hasOption(option)) { 135 return false; 136 } 137 } 138 139 return true; 140 } 141 142 private static boolean hasOption(String option) { 143 option = option.toLowerCase(Locale.ENGLISH); 144 if (property.contains("all")) { 145 return true; 146 } else { 147 int offset = property.indexOf("ssl"); 148 if (offset != -1 && property.indexOf("sslctx", offset) != -1) { 149 // don't enable data and plaintext options by default 150 if (!(option.equals("data") 151 || option.equals("packet") 152 || option.equals("plaintext"))) { 153 return true; 154 } 155 } 156 } 157 158 return property.contains(option); 159 } 160 161 public static void severe(String msg, Object... params) { 162 SSLLogger.log(Level.ERROR, msg, params); 163 } 164 165 public static void warning(String msg, Object... params) { 166 SSLLogger.log(Level.WARNING, msg, params); 167 } 168 169 public static void info(String msg, Object... params) { 170 SSLLogger.log(Level.INFO, msg, params); 171 } 172 173 public static void fine(String msg, Object... params) { 174 SSLLogger.log(Level.DEBUG, msg, params); 175 } 176 177 public static void finer(String msg, Object... params) { 178 SSLLogger.log(Level.TRACE, msg, params); 179 } 180 181 public static void finest(String msg, Object... params) { 182 SSLLogger.log(Level.ALL, msg, params); 183 } 184 185 private static void log(Level level, String msg, Object... params) { 186 if (logger.isLoggable(level)) { 187 if (params == null || params.length == 0) { 188 logger.log(level, msg); 189 } else { 190 try { 191 String formatted = 192 SSLSimpleFormatter.formatParameters(params); 193 logger.log(level, msg, formatted); 194 } catch (Exception exp) { 195 // ignore it, just for debugging. 196 } 197 } 198 } 199 } 200 201 static String toString(Object... params) { 202 try { 203 return SSLSimpleFormatter.formatParameters(params); 204 } catch (Exception exp) { 205 return "unexpected exception thrown: " + exp.getMessage(); 206 } 207 } 208 209 private static class SSLConsoleLogger implements Logger { 210 private final String loggerName; 211 private final boolean useCompactFormat; 212 213 SSLConsoleLogger(String loggerName, String options) { 214 this.loggerName = loggerName; 215 options = options.toLowerCase(Locale.ENGLISH); 216 this.useCompactFormat = !options.contains("expand"); 217 } 218 219 @Override 220 public String getName() { 221 return loggerName; 222 } 223 224 @Override 225 public boolean isLoggable(Level level) { 226 return (level != Level.OFF); 227 } 228 229 @Override 230 public void log(Level level, 231 ResourceBundle rb, String message, Throwable thrwbl) { 232 if (isLoggable(level)) { 233 try { 234 String formatted = 235 SSLSimpleFormatter.format(this, level, message, thrwbl); 236 System.err.write(formatted.getBytes(UTF_8)); 237 } catch (Exception exp) { 238 // ignore it, just for debugging. 239 } 240 } 241 } 242 243 @Override 244 public void log(Level level, 245 ResourceBundle rb, String message, Object... params) { 246 if (isLoggable(level)) { 247 try { 248 String formatted = 249 SSLSimpleFormatter.format(this, level, message, params); 250 System.err.write(formatted.getBytes(UTF_8)); 251 } catch (Exception exp) { 252 // ignore it, just for debugging. 253 } 254 } 255 } 256 } 257 258 private static class SSLSimpleFormatter { 259 private static final String PATTERN = "yyyy-MM-dd kk:mm:ss.SSS z"; 260 private static final DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern(PATTERN, Locale.ENGLISH) 261 .withZone(ZoneId.systemDefault()); 262 263 private static final MessageFormat basicCertFormat = new MessageFormat( 264 "\"version\" : \"v{0}\",\n" + 265 "\"serial number\" : \"{1}\",\n" + 266 "\"signature algorithm\": \"{2}\",\n" + 267 "\"issuer\" : \"{3}\",\n" + 268 "\"not before\" : \"{4}\",\n" + 269 "\"not after\" : \"{5}\",\n" + 270 "\"subject\" : \"{6}\",\n" + 271 "\"subject public key\" : \"{7}\"\n", 272 Locale.ENGLISH); 273 274 private static final MessageFormat extendedCertFormart = 275 new MessageFormat( 276 "\"version\" : \"v{0}\",\n" + 277 "\"serial number\" : \"{1}\",\n" + 278 "\"signature algorithm\": \"{2}\",\n" + 279 "\"issuer\" : \"{3}\",\n" + 280 "\"not before\" : \"{4}\",\n" + 281 "\"not after\" : \"{5}\",\n" + 282 "\"subject\" : \"{6}\",\n" + 283 "\"subject public key\" : \"{7}\",\n" + 284 "\"extensions\" : [\n" + 285 "{8}\n" + 286 "]\n", 287 Locale.ENGLISH); 288 289 // 290 // private static MessageFormat certExtFormat = new MessageFormat( 291 // "{0} [{1}] '{'\n" + 292 // " critical: {2}\n" + 293 // " value: {3}\n" + 294 // "'}'", 295 // Locale.ENGLISH); 296 // 297 298 private static final MessageFormat messageFormatNoParas = 299 new MessageFormat( 300 "'{'\n" + 301 " \"logger\" : \"{0}\",\n" + 302 " \"level\" : \"{1}\",\n" + 303 " \"thread id\" : \"{2}\",\n" + 304 " \"thread name\" : \"{3}\",\n" + 305 " \"time\" : \"{4}\",\n" + 306 " \"caller\" : \"{5}\",\n" + 307 " \"message\" : \"{6}\"\n" + 308 "'}'\n", 309 Locale.ENGLISH); 310 311 private static final MessageFormat messageCompactFormatNoParas = 312 new MessageFormat( 313 "{0}|{1}|{2}|{3}|{4}|{5}|{6}\n", 314 Locale.ENGLISH); 315 316 private static final MessageFormat messageFormatWithParas = 317 new MessageFormat( 318 "'{'\n" + 319 " \"logger\" : \"{0}\",\n" + 320 " \"level\" : \"{1}\",\n" + 321 " \"thread id\" : \"{2}\",\n" + 322 " \"thread name\" : \"{3}\",\n" + 323 " \"time\" : \"{4}\",\n" + 324 " \"caller\" : \"{5}\",\n" + 325 " \"message\" : \"{6}\",\n" + 326 " \"specifics\" : [\n" + 327 "{7}\n" + 328 " ]\n" + 329 "'}'\n", 330 Locale.ENGLISH); 331 332 private static final MessageFormat messageCompactFormatWithParas = 333 new MessageFormat( 334 "{0}|{1}|{2}|{3}|{4}|{5}|{6} (\n" + 335 "{7}\n" + 336 ")\n", 337 Locale.ENGLISH); 338 339 private static final MessageFormat keyObjectFormat = new MessageFormat( 340 "\"{0}\" : '{'\n" + 341 "{1}" + 342 "'}'\n", 343 Locale.ENGLISH); 344 345 // INFO: [TH: 123450] 2011-08-20 23:12:32.3225 PDT 346 // log message 347 // log message 348 // ... 349 private static String format(SSLConsoleLogger logger, Level level, 350 String message, Object ... parameters) { 351 352 if (parameters == null || parameters.length == 0) { 353 Object[] messageFields = { 354 logger.loggerName, 355 level.getName(), 356 Utilities.toHexString(Thread.currentThread().getId()), 357 Thread.currentThread().getName(), 358 dateTimeFormat.format(Instant.now()), 359 formatCaller(), 360 message 361 }; 362 363 if (logger.useCompactFormat) { 364 return messageCompactFormatNoParas.format(messageFields); 365 } else { 366 return messageFormatNoParas.format(messageFields); 367 } 368 } 369 370 Object[] messageFields = { 371 logger.loggerName, 372 level.getName(), 373 Utilities.toHexString(Thread.currentThread().getId()), 374 Thread.currentThread().getName(), 375 dateTimeFormat.format(Instant.now()), 376 formatCaller(), 377 message, 378 (logger.useCompactFormat ? 379 formatParameters(parameters) : 380 Utilities.indent(formatParameters(parameters))) 381 }; 382 383 if (logger.useCompactFormat) { 384 return messageCompactFormatWithParas.format(messageFields); 385 } else { 386 return messageFormatWithParas.format(messageFields); 387 } 388 } 389 390 private static String formatCaller() { 391 return StackWalker.getInstance().walk(s -> 392 s.dropWhile(f -> 393 f.getClassName().startsWith("sun.security.ssl.SSLLogger") || 394 f.getClassName().startsWith("java.lang.System")) 395 .map(f -> f.getFileName() + ":" + f.getLineNumber()) 396 .findFirst().orElse("unknown caller")); 397 } 398 399 private static String formatParameters(Object ... parameters) { 400 StringBuilder builder = new StringBuilder(512); 401 boolean isFirst = true; 402 for (Object parameter : parameters) { 403 if (isFirst) { 404 isFirst = false; 405 } else { 406 builder.append(",\n"); 407 } 408 409 if (parameter instanceof Throwable) { 410 builder.append(formatThrowable((Throwable)parameter)); 411 } else if (parameter instanceof Certificate) { 412 builder.append(formatCertificate((Certificate)parameter)); 413 } else if (parameter instanceof ByteArrayInputStream) { 414 builder.append(formatByteArrayInputStream( 415 (ByteArrayInputStream)parameter)); 416 } else if (parameter instanceof ByteBuffer) { 417 builder.append(formatByteBuffer((ByteBuffer)parameter)); 418 } else if (parameter instanceof byte[]) { 419 builder.append(formatByteArrayInputStream( 420 new ByteArrayInputStream((byte[])parameter))); 421 } else if (parameter instanceof Map.Entry) { 422 @SuppressWarnings("unchecked") 423 Map.Entry<String, ?> mapParameter = 424 (Map.Entry<String, ?>)parameter; 425 builder.append(formatMapEntry(mapParameter)); 426 } else { 427 builder.append(formatObject(parameter)); 428 } 429 } 430 431 return builder.toString(); 432 } 433 434 // "throwable": { 435 // ... 436 // } 437 private static String formatThrowable(Throwable throwable) { 438 StringBuilder builder = new StringBuilder(512); 439 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 440 try (PrintStream out = new PrintStream(bytesOut)) { 441 throwable.printStackTrace(out); 442 builder.append(Utilities.indent(bytesOut.toString())); 443 } 444 Object[] fields = { 445 "throwable", 446 builder.toString() 447 }; 448 449 return keyObjectFormat.format(fields); 450 } 451 452 // "certificate": { 453 // ... 454 // } 455 private static String formatCertificate(Certificate certificate) { 456 457 if (!(certificate instanceof X509Certificate)) { 458 return Utilities.indent(certificate.toString()); 459 } 460 461 StringBuilder builder = new StringBuilder(512); 462 try { 463 X509CertImpl x509 = 464 X509CertImpl.toImpl((X509Certificate)certificate); 465 X509CertInfo certInfo = 466 (X509CertInfo)x509.get(X509CertImpl.NAME + "." + 467 X509CertImpl.INFO); 468 CertificateExtensions certExts = (CertificateExtensions) 469 certInfo.get(X509CertInfo.EXTENSIONS); 470 if (certExts == null) { 471 Object[] certFields = { 472 x509.getVersion(), 473 Utilities.toHexString( 474 x509.getSerialNumber().toByteArray()), 475 x509.getSigAlgName(), 476 x509.getIssuerX500Principal().toString(), 477 dateTimeFormat.format(x509.getNotBefore().toInstant()), 478 dateTimeFormat.format(x509.getNotAfter().toInstant()), 479 x509.getSubjectX500Principal().toString(), 480 x509.getPublicKey().getAlgorithm() 481 }; 482 builder.append(Utilities.indent( 483 basicCertFormat.format(certFields))); 484 } else { 485 StringBuilder extBuilder = new StringBuilder(512); 486 boolean isFirst = true; 487 for (Extension certExt : certExts.getAllExtensions()) { 488 if (isFirst) { 489 isFirst = false; 490 } else { 491 extBuilder.append(",\n"); 492 } 493 extBuilder.append("{\n" + 494 Utilities.indent(certExt.toString()) + "\n}"); 495 } 496 Object[] certFields = { 497 x509.getVersion(), 498 Utilities.toHexString( 499 x509.getSerialNumber().toByteArray()), 500 x509.getSigAlgName(), 501 x509.getIssuerX500Principal().toString(), 502 dateTimeFormat.format(x509.getNotBefore().toInstant()), 503 dateTimeFormat.format(x509.getNotAfter().toInstant()), 504 x509.getSubjectX500Principal().toString(), 505 x509.getPublicKey().getAlgorithm(), 506 Utilities.indent(extBuilder.toString()) 507 }; 508 builder.append(Utilities.indent( 509 extendedCertFormart.format(certFields))); 510 } 511 } catch (Exception ce) { 512 // ignore the exception 513 } 514 515 Object[] fields = { 516 "certificate", 517 builder.toString() 518 }; 519 520 return Utilities.indent(keyObjectFormat.format(fields)); 521 } 522 523 private static String formatByteArrayInputStream( 524 ByteArrayInputStream bytes) { 525 StringBuilder builder = new StringBuilder(512); 526 527 try (ByteArrayOutputStream bytesOut = new ByteArrayOutputStream()) { 528 HexDumpEncoder hexEncoder = new HexDumpEncoder(); 529 hexEncoder.encodeBuffer(bytes, bytesOut); 530 531 builder.append(Utilities.indent(bytesOut.toString())); 532 } catch (IOException ioe) { 533 // ignore it, just for debugging. 534 } 535 536 return builder.toString(); 537 } 538 539 private static String formatByteBuffer(ByteBuffer byteBuffer) { 540 StringBuilder builder = new StringBuilder(512); 541 try (ByteArrayOutputStream bytesOut = new ByteArrayOutputStream()) { 542 HexDumpEncoder hexEncoder = new HexDumpEncoder(); 543 hexEncoder.encodeBuffer(byteBuffer.duplicate(), bytesOut); 544 builder.append(Utilities.indent(bytesOut.toString())); 545 } catch (IOException ioe) { 546 // ignore it, just for debugging. 547 } 548 549 return builder.toString(); 550 } 551 552 private static String formatMapEntry(Map.Entry<String, ?> entry) { 553 String key = entry.getKey(); 554 Object value = entry.getValue(); 555 556 String formatted; 557 if (value instanceof String) { 558 // "key": "value" 559 formatted = "\"" + key + "\": \"" + (String)value + "\""; 560 } else if (value instanceof String[]) { 561 // "key": [ "string a", 562 // "string b", 563 // "string c" 564 // ] 565 StringBuilder builder = new StringBuilder(512); 566 String[] strings = (String[])value; 567 builder.append("\"" + key + "\": [\n"); 568 for (String string : strings) { 569 builder.append(" \"" + string + "\""); 570 if (string != strings[strings.length - 1]) { 571 builder.append(","); 572 } 573 builder.append("\n"); 574 } 575 builder.append(" ]"); 576 577 formatted = builder.toString(); 578 } else if (value instanceof byte[]) { 579 formatted = "\"" + key + "\": \"" + 580 Utilities.toHexString((byte[])value) + "\""; 581 } else if (value instanceof Byte) { 582 formatted = "\"" + key + "\": \"" + 583 Utilities.toHexString((byte)value) + "\""; 584 } else { 585 formatted = "\"" + key + "\": " + 586 "\"" + value.toString() + "\""; 587 } 588 589 return Utilities.indent(formatted); 590 } 591 592 private static String formatObject(Object obj) { 593 return obj.toString(); 594 } 595 } 596 }