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