/* * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.security.ssl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.lang.System.Logger; import java.lang.System.Logger.Level; import java.nio.ByteBuffer; import java.security.cert.Certificate; import java.security.cert.Extension; import java.security.cert.X509Certificate; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; import sun.security.action.GetPropertyAction; import sun.security.util.HexDumpEncoder; import sun.security.x509.*; /** * Implementation of SSL logger. * * If the system property "javax.net.debug" is not defined, the debug logging * is turned off. If the system property "javax.net.debug" is defined as * empty, the debug logger is specified by System.getLogger("javax.net.ssl"), * and applications can customize and configure the logger or use external * logging mechanisms. If the system property "javax.net.debug" is defined * and non-empty, a private debug logger implemented in this class is used. */ public final class SSLLogger { private static final System.Logger logger; private static final String property; public static final boolean isOn; static { String p = GetPropertyAction.privilegedGetProperty("javax.net.debug"); if (p != null) { if (p.isEmpty()) { property = ""; logger = System.getLogger("javax.net.ssl"); } else { property = p.toLowerCase(Locale.ENGLISH); if (property.equals("help")) { help(); } logger = new SSLConsoleLogger("javax.net.ssl", p); } isOn = true; } else { property = null; logger = null; isOn = false; } } private static void help() { System.err.println(); System.err.println("help print the help messages"); System.err.println("expand expand debugging information"); System.err.println(); System.err.println("all turn on all debugging"); System.err.println("ssl turn on ssl debugging"); System.err.println(); System.err.println("The following can be used with ssl:"); System.err.println("\trecord enable per-record tracing"); System.err.println("\thandshake print each handshake message"); System.err.println("\tkeygen print key generation data"); System.err.println("\tsession print session activity"); System.err.println("\tdefaultctx print default SSL initialization"); System.err.println("\tsslctx print SSLContext tracing"); System.err.println("\tsessioncache print session cache tracing"); System.err.println("\tkeymanager print key manager tracing"); System.err.println("\ttrustmanager print trust manager tracing"); System.err.println("\tpluggability print pluggability tracing"); System.err.println(); System.err.println("\thandshake debugging can be widened with:"); System.err.println("\tdata hex dump of each handshake message"); System.err.println("\tverbose verbose handshake message printing"); System.err.println(); System.err.println("\trecord debugging can be widened with:"); System.err.println("\tplaintext hex dump of record plaintext"); System.err.println("\tpacket print raw SSL/TLS packets"); System.err.println(); System.exit(0); } /** * Return true if the "javax.net.debug" property contains the * debug check points, or System.Logger is used. */ public static boolean isOn(String checkPoints) { if (property == null) { // debugging is turned off return false; } else if (property.isEmpty()) { // use System.Logger return true; } // use provider logger String[] options = checkPoints.split(","); for (String option : options) { option = option.trim(); if (!SSLLogger.hasOption(option)) { return false; } } return true; } private static boolean hasOption(String option) { option = option.toLowerCase(Locale.ENGLISH); if (property.contains("all")) { return true; } else { int offset = property.indexOf("ssl"); if (offset != -1 && property.indexOf("sslctx", offset) != -1) { // don't enable data and plaintext options by default if (!(option.equals("data") || option.equals("packet") || option.equals("plaintext"))) { return true; } } } return property.contains(option); } public static void severe(String msg, Object... params) { SSLLogger.log(Level.ERROR, msg, params); } public static void warning(String msg, Object... params) { SSLLogger.log(Level.WARNING, msg, params); } public static void info(String msg, Object... params) { SSLLogger.log(Level.INFO, msg, params); } public static void fine(String msg, Object... params) { SSLLogger.log(Level.DEBUG, msg, params); } public static void finer(String msg, Object... params) { SSLLogger.log(Level.TRACE, msg, params); } public static void finest(String msg, Object... params) { SSLLogger.log(Level.ALL, msg, params); } private static void log(Level level, String msg, Object... params) { if (logger.isLoggable(level)) { if (params == null || params.length == 0) { logger.log(level, msg); } else { try { String formatted = SSLSimpleFormatter.formatParameters(params); logger.log(level, msg, formatted); } catch (Exception exp) { // ignore it, just for debugging. } } } } static String toString(Object... params) { try { return SSLSimpleFormatter.formatParameters(params); } catch (Exception exp) { return "unexpected exception thrown: " + exp.getMessage(); } } private static class SSLConsoleLogger implements Logger { private final String loggerName; private final boolean useCompactFormat; SSLConsoleLogger(String loggerName, String options) { this.loggerName = loggerName; options = options.toLowerCase(Locale.ENGLISH); this.useCompactFormat = !options.contains("expand"); } @Override public String getName() { return loggerName; } @Override public boolean isLoggable(Level level) { return (level != Level.OFF); } @Override public void log(Level level, ResourceBundle rb, String message, Throwable thrwbl) { if (isLoggable(level)) { try { String formatted = SSLSimpleFormatter.format(this, level, message, thrwbl); System.err.write(formatted.getBytes("UTF-8")); } catch (Exception exp) { // ignore it, just for debugging. } } } @Override public void log(Level level, ResourceBundle rb, String message, Object... params) { if (isLoggable(level)) { try { String formatted = SSLSimpleFormatter.format(this, level, message, params); System.err.write(formatted.getBytes("UTF-8")); } catch (Exception exp) { // ignore it, just for debugging. } } } } private static class SSLSimpleFormatter { private static final ThreadLocal dateFormat = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat( "yyyy-MM-dd kk:mm:ss.SSS z", Locale.ENGLISH); } }; private static final MessageFormat basicCertFormat = new MessageFormat( "\"version\" : \"v{0}\",\n" + "\"serial number\" : \"{1}\",\n" + "\"signature algorithm\": \"{2}\",\n" + "\"issuer\" : \"{3}\",\n" + "\"not before\" : \"{4}\",\n" + "\"not after\" : \"{5}\",\n" + "\"subject\" : \"{6}\",\n" + "\"subject public key\" : \"{7}\"\n", Locale.ENGLISH); private static final MessageFormat extendedCertFormart = new MessageFormat( "\"version\" : \"v{0}\",\n" + "\"serial number\" : \"{1}\",\n" + "\"signature algorithm\": \"{2}\",\n" + "\"issuer\" : \"{3}\",\n" + "\"not before\" : \"{4}\",\n" + "\"not after\" : \"{5}\",\n" + "\"subject\" : \"{6}\",\n" + "\"subject public key\" : \"{7}\",\n" + "\"extensions\" : [\n" + "{8}\n" + "]\n", Locale.ENGLISH); // // private static MessageFormat certExtFormat = new MessageFormat( // "{0} [{1}] '{'\n" + // " critical: {2}\n" + // " value: {3}\n" + // "'}'", // Locale.ENGLISH); // private static final MessageFormat messageFormatNoParas = new MessageFormat( "'{'\n" + " \"logger\" : \"{0}\",\n" + " \"level\" : \"{1}\",\n" + " \"thread id\" : \"{2}\",\n" + " \"thread name\" : \"{3}\",\n" + " \"time\" : \"{4}\",\n" + " \"caller\" : \"{5}\",\n" + " \"message\" : \"{6}\"\n" + "'}'\n", Locale.ENGLISH); private static final MessageFormat messageCompactFormatNoParas = new MessageFormat( "{0}|{1}|{2}|{3}|{4}|{5}|{6}\n", Locale.ENGLISH); private static final MessageFormat messageFormatWithParas = new MessageFormat( "'{'\n" + " \"logger\" : \"{0}\",\n" + " \"level\" : \"{1}\",\n" + " \"thread id\" : \"{2}\",\n" + " \"thread name\" : \"{3}\",\n" + " \"time\" : \"{4}\",\n" + " \"caller\" : \"{5}\",\n" + " \"message\" : \"{6}\",\n" + " \"specifics\" : [\n" + "{7}\n" + " ]\n" + "'}'\n", Locale.ENGLISH); private static final MessageFormat messageCompactFormatWithParas = new MessageFormat( "{0}|{1}|{2}|{3}|{4}|{5}|{6} (\n" + "{7}\n" + ")\n", Locale.ENGLISH); private static final MessageFormat keyObjectFormat = new MessageFormat( "\"{0}\" : '{'\n" + "{1}" + "'}'\n", Locale.ENGLISH); // INFO: [TH: 123450] 2011-08-20 23:12:32.3225 PDT // log message // log message // ... private static String format(SSLConsoleLogger logger, Level level, String message, Object ... parameters) { if (parameters == null || parameters.length == 0) { Object[] messageFields = { logger.loggerName, level.getName(), Utilities.toHexString(Thread.currentThread().getId()), Thread.currentThread().getName(), dateFormat.get().format(new Date(System.currentTimeMillis())), formatCaller(), message }; if (logger.useCompactFormat) { return messageCompactFormatNoParas.format(messageFields); } else { return messageFormatNoParas.format(messageFields); } } Object[] messageFields = { logger.loggerName, level.getName(), Utilities.toHexString(Thread.currentThread().getId()), Thread.currentThread().getName(), dateFormat.get().format(new Date(System.currentTimeMillis())), formatCaller(), message, (logger.useCompactFormat ? formatParameters(parameters) : Utilities.indent(formatParameters(parameters))) }; if (logger.useCompactFormat) { return messageCompactFormatWithParas.format(messageFields); } else { return messageFormatWithParas.format(messageFields); } } private static String formatCaller() { boolean isSSLLogger = false; for (StackTraceElement ste: (new Throwable()).getStackTrace()) { String cn = ste.getClassName(); if (cn.equals("sun.security.ssl.SSLLogger")) { // May be SSLLogger.log() or SSLLogger.fine(), etc. isSSLLogger = true; } else if (isSSLLogger) { // Here is the caller to SSLLogger.fine(), etc. return ste.getFileName() + ":" + ste.getLineNumber(); } } return "unknown caller"; } private static boolean isLoggerImpl(StackTraceElement ste) { String cn = ste.getClassName(); System.out.println("class name: " + cn); return cn.equals("sun.security.ssl.SSLLogger"); } private static String formatParameters(Object ... parameters) { StringBuilder builder = new StringBuilder(512); boolean isFirst = true; for (Object parameter : parameters) { if (isFirst) { isFirst = false; } else { builder.append(",\n"); } if (parameter instanceof Throwable) { builder.append(formatThrowable((Throwable)parameter)); } else if (parameter instanceof Certificate) { builder.append(formatCertificate((Certificate)parameter)); } else if (parameter instanceof ByteArrayInputStream) { builder.append(formatByteArrayInputStream( (ByteArrayInputStream)parameter)); } else if (parameter instanceof ByteBuffer) { builder.append(formatByteBuffer((ByteBuffer)parameter)); } else if (parameter instanceof byte[]) { builder.append(formatByteArrayInputStream( new ByteArrayInputStream((byte[])parameter))); } else if (parameter instanceof Map.Entry) { @SuppressWarnings("unchecked") Map.Entry mapParameter = (Map.Entry)parameter; builder.append(formatMapEntry(mapParameter)); } else { builder.append(formatObject(parameter)); } } return builder.toString(); } // "throwable": { // ... // } private static String formatThrowable(Throwable throwable) { StringBuilder builder = new StringBuilder(512); ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); try (PrintStream out = new PrintStream(bytesOut)) { throwable.printStackTrace(out); builder.append(Utilities.indent(bytesOut.toString())); } Object[] fields = { "throwable", builder.toString() }; return keyObjectFormat.format(fields); } // "certificate": { // ... // } private static String formatCertificate(Certificate certificate) { if (!(certificate instanceof X509Certificate)) { return Utilities.indent(certificate.toString()); } StringBuilder builder = new StringBuilder(512); try { X509CertImpl x509 = X509CertImpl.toImpl((X509Certificate)certificate); X509CertInfo certInfo = (X509CertInfo)x509.get(X509CertImpl.NAME + "." + X509CertImpl.INFO); CertificateExtensions certExts = (CertificateExtensions) certInfo.get(X509CertInfo.EXTENSIONS); if (certExts == null) { Object[] certFields = { x509.getVersion(), Utilities.toHexString( x509.getSerialNumber().toByteArray()), x509.getSigAlgName(), x509.getIssuerX500Principal().toString(), dateFormat.get().format(x509.getNotBefore()), dateFormat.get().format(x509.getNotAfter()), x509.getSubjectX500Principal().toString(), x509.getPublicKey().getAlgorithm() }; builder.append(Utilities.indent( basicCertFormat.format(certFields))); } else { StringBuilder extBuilder = new StringBuilder(512); boolean isFirst = true; for (Extension certExt : certExts.getAllExtensions()) { if (isFirst) { isFirst = false; } else { extBuilder.append(",\n"); } extBuilder.append("{\n" + Utilities.indent(certExt.toString()) + "\n}"); } Object[] certFields = { x509.getVersion(), Utilities.toHexString( x509.getSerialNumber().toByteArray()), x509.getSigAlgName(), x509.getIssuerX500Principal().toString(), dateFormat.get().format(x509.getNotBefore()), dateFormat.get().format(x509.getNotAfter()), x509.getSubjectX500Principal().toString(), x509.getPublicKey().getAlgorithm(), Utilities.indent(extBuilder.toString()) }; builder.append(Utilities.indent( extendedCertFormart.format(certFields))); } } catch (Exception ce) { // ignore the exception } Object[] fields = { "certificate", builder.toString() }; return Utilities.indent(keyObjectFormat.format(fields)); } private static String formatByteArrayInputStream( ByteArrayInputStream bytes) { StringBuilder builder = new StringBuilder(512); try (ByteArrayOutputStream bytesOut = new ByteArrayOutputStream()) { HexDumpEncoder hexEncoder = new HexDumpEncoder(); hexEncoder.encodeBuffer(bytes, bytesOut); builder.append(Utilities.indent(bytesOut.toString())); } catch (IOException ioe) { // ignore it, just for debugging. } return builder.toString(); } private static String formatByteBuffer(ByteBuffer byteBuffer) { StringBuilder builder = new StringBuilder(512); try (ByteArrayOutputStream bytesOut = new ByteArrayOutputStream()) { HexDumpEncoder hexEncoder = new HexDumpEncoder(); hexEncoder.encodeBuffer(byteBuffer.duplicate(), bytesOut); builder.append(Utilities.indent(bytesOut.toString())); } catch (IOException ioe) { // ignore it, just for debugging. } return builder.toString(); } private static String formatMapEntry(Map.Entry entry) { String key = entry.getKey(); Object value = entry.getValue(); String formatted; if (value instanceof String) { // "key": "value" formatted = "\"" + key + "\": \"" + (String)value + "\""; } else if (value instanceof String[]) { // "key": [ "string a", // "string b", // "string c" // ] StringBuilder builder = new StringBuilder(512); String[] strings = (String[])value; builder.append("\"" + key + "\": [\n"); for (String string : strings) { builder.append(" \"" + string + "\""); if (string != strings[strings.length - 1]) { builder.append(","); } builder.append("\n"); } builder.append(" ]"); formatted = builder.toString(); } else if (value instanceof byte[]) { formatted = "\"" + key + "\": \"" + Utilities.toHexString((byte[])value) + "\""; } else if (value instanceof Byte) { formatted = "\"" + key + "\": \"" + Utilities.toHexString((byte)value) + "\""; } else { formatted = "\"" + key + "\": " + "\"" + value.toString() + "\""; } return Utilities.indent(formatted); } private static String formatObject(Object obj) { return obj.toString(); } } }