/* * Copyright (c) 2000, 2013, 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 com.sun.security.sasl.util; import javax.security.sasl.*; import java.io.*; import java.util.Map; import java.util.StringTokenizer; import java.util.logging.Logger; import java.util.logging.Level; import sun.security.util.HexDumpEncoder; /** * The base class used by client and server implementations of SASL * mechanisms to process properties passed in the props argument * and strings with the same format (e.g., used in digest-md5). * * Also contains utilities for doing int to network-byte-order * transformations. * * @author Rosanna Lee */ public abstract class AbstractSaslImpl { protected boolean completed = false; protected boolean privacy = false; protected boolean integrity = false; protected byte[] qop; // ordered list of qops protected byte allQop; // a mask indicating which QOPs are requested protected byte[] strength; // ordered list of cipher strengths // These are relevant only when privacy or integray have been negotiated protected int sendMaxBufSize = 0; // specified by peer but can override protected int recvMaxBufSize = 65536; // optionally specified by self protected int rawSendSize; // derived from sendMaxBufSize protected String myClassName; protected AbstractSaslImpl(Map<String, ?> props, String className) throws SaslException { myClassName = className; // Parse properties to set desired context options if (props != null) { String prop; // "auth", "auth-int", "auth-conf" qop = parseQop(prop=(String)props.get(Sasl.QOP)); logger.logp(Level.FINE, myClassName, "constructor", "SASLIMPL01:Preferred qop property: {0}", prop); allQop = combineMasks(qop); if (logger.isLoggable(Level.FINE)) { logger.logp(Level.FINE, myClassName, "constructor", "SASLIMPL02:Preferred qop mask: {0}", allQop); if (qop.length > 0) { StringBuilder str = new StringBuilder(); for (int i = 0; i < qop.length; i++) { str.append(Byte.toString(qop[i])); str.append(' '); } logger.logp(Level.FINE, myClassName, "constructor", "SASLIMPL03:Preferred qops : {0}", str.toString()); } } // "low", "medium", "high" strength = parseStrength(prop=(String)props.get(Sasl.STRENGTH)); logger.logp(Level.FINE, myClassName, "constructor", "SASLIMPL04:Preferred strength property: {0}", prop); if (logger.isLoggable(Level.FINE) && strength.length > 0) { StringBuilder str = new StringBuilder(); for (int i = 0; i < strength.length; i++) { str.append(Byte.toString(strength[i])); str.append(' '); } logger.logp(Level.FINE, myClassName, "constructor", "SASLIMPL05:Cipher strengths: {0}", str.toString()); } // Max receive buffer size prop = (String)props.get(Sasl.MAX_BUFFER); if (prop != null) { try { logger.logp(Level.FINE, myClassName, "constructor", "SASLIMPL06:Max receive buffer size: {0}", prop); recvMaxBufSize = Integer.parseInt(prop); } catch (NumberFormatException e) { throw new SaslException( "Property must be string representation of integer: " + Sasl.MAX_BUFFER); } } // Max send buffer size prop = (String)props.get(MAX_SEND_BUF); if (prop != null) { try { logger.logp(Level.FINE, myClassName, "constructor", "SASLIMPL07:Max send buffer size: {0}", prop); sendMaxBufSize = Integer.parseInt(prop); } catch (NumberFormatException e) { throw new SaslException( "Property must be string representation of integer: " + MAX_SEND_BUF); } } } else { qop = DEFAULT_QOP; allQop = NO_PROTECTION; strength = STRENGTH_MASKS; } } /** * Determines whether this mechanism has completed. * * @return true if has completed; false otherwise; */ public boolean isComplete() { return completed; } /** * Retrieves the negotiated property. * @exception IllegalStateException if this authentication exchange has * not completed */ public Object getNegotiatedProperty(String propName) { if (!completed) { throw new IllegalStateException("SASL authentication not completed"); } switch (propName) { case Sasl.QOP: if (privacy) { return "auth-conf"; } else if (integrity) { return "auth-int"; } else { return "auth"; } case Sasl.MAX_BUFFER: return Integer.toString(recvMaxBufSize); case Sasl.RAW_SEND_SIZE: return Integer.toString(rawSendSize); case MAX_SEND_BUF: return Integer.toString(sendMaxBufSize); default: return null; } } protected static final byte combineMasks(byte[] in) { byte answer = 0; for (int i = 0; i < in.length; i++) { answer |= in[i]; } return answer; } protected static final byte findPreferredMask(byte pref, byte[] in) { for (int i = 0; i < in.length; i++) { if ((in[i]&pref) != 0) { return in[i]; } } return (byte)0; } private static final byte[] parseQop(String qop) throws SaslException { return parseQop(qop, null, false); } protected static final byte[] parseQop(String qop, String[] saveTokens, boolean ignore) throws SaslException { if (qop == null) { return DEFAULT_QOP; // default } return parseProp(Sasl.QOP, qop, QOP_TOKENS, QOP_MASKS, saveTokens, ignore); } private static final byte[] parseStrength(String strength) throws SaslException { if (strength == null) { return DEFAULT_STRENGTH; // default } return parseProp(Sasl.STRENGTH, strength, STRENGTH_TOKENS, STRENGTH_MASKS, null, false); } private static final byte[] parseProp(String propName, String propVal, String[] vals, byte[] masks, String[] tokens, boolean ignore) throws SaslException { StringTokenizer parser = new StringTokenizer(propVal, ", \t\n"); String token; byte[] answer = new byte[vals.length]; int i = 0; boolean found; while (parser.hasMoreTokens() && i < answer.length) { token = parser.nextToken(); found = false; for (int j = 0; !found && j < vals.length; j++) { if (token.equalsIgnoreCase(vals[j])) { found = true; answer[i++] = masks[j]; if (tokens != null) { tokens[j] = token; // save what was parsed } } } if (!found && !ignore) { throw new SaslException( "Invalid token in " + propName + ": " + propVal); } } // Initialize rest of array with 0 for (int j = i; j < answer.length; j++) { answer[j] = 0; } return answer; } /** * Outputs a byte array. Can be null. */ protected static final void traceOutput(String srcClass, String srcMethod, String traceTag, byte[] output) { traceOutput(srcClass, srcMethod, traceTag, output, 0, output == null ? 0 : output.length); } protected static final void traceOutput(String srcClass, String srcMethod, String traceTag, byte[] output, int offset, int len) { try { int origlen = len; Level lev; if (!logger.isLoggable(Level.FINEST)) { len = Math.min(16, len); lev = Level.FINER; } else { lev = Level.FINEST; } String content; if (output != null) { ByteArrayOutputStream out = new ByteArrayOutputStream(len); new HexDumpEncoder().encodeBuffer( new ByteArrayInputStream(output, offset, len), out); content = out.toString(); } else { content = "NULL"; } // Message id supplied by caller as part of traceTag logger.logp(lev, srcClass, srcMethod, "{0} ( {1} ): {2}", new Object[] {traceTag, origlen, content}); } catch (Exception e) { logger.logp(Level.WARNING, srcClass, srcMethod, "SASLIMPL09:Error generating trace output: {0}", e); } } /** * Returns the integer represented by 4 bytes in network byte order. */ protected static final int networkByteOrderToInt(byte[] buf, int start, int count) { if (count > 4) { throw new IllegalArgumentException("Cannot handle more than 4 bytes"); } int answer = 0; for (int i = 0; i < count; i++) { answer <<= 8; answer |= ((int)buf[start+i] & 0xff); } return answer; } /** * Encodes an integer into 4 bytes in network byte order in the buffer * supplied. */ protected static final void intToNetworkByteOrder(int num, byte[] buf, int start, int count) { if (count > 4) { throw new IllegalArgumentException("Cannot handle more than 4 bytes"); } for (int i = count-1; i >= 0; i--) { buf[start+i] = (byte)(num & 0xff); num >>>= 8; } } // ---------------- Constants ----------------- private static final String SASL_LOGGER_NAME = "javax.security.sasl"; protected static final String MAX_SEND_BUF = "javax.security.sasl.sendmaxbuffer"; /** * Logger for debug messages */ protected static final Logger logger = Logger.getLogger(SASL_LOGGER_NAME); // default 0 (no protection); 1 (integrity only) protected static final byte NO_PROTECTION = (byte)1; protected static final byte INTEGRITY_ONLY_PROTECTION = (byte)2; protected static final byte PRIVACY_PROTECTION = (byte)4; protected static final byte LOW_STRENGTH = (byte)1; protected static final byte MEDIUM_STRENGTH = (byte)2; protected static final byte HIGH_STRENGTH = (byte)4; private static final byte[] DEFAULT_QOP = new byte[]{NO_PROTECTION}; private static final String[] QOP_TOKENS = {"auth-conf", "auth-int", "auth"}; private static final byte[] QOP_MASKS = {PRIVACY_PROTECTION, INTEGRITY_ONLY_PROTECTION, NO_PROTECTION}; private static final byte[] DEFAULT_STRENGTH = new byte[]{ HIGH_STRENGTH, MEDIUM_STRENGTH, LOW_STRENGTH}; private static final String[] STRENGTH_TOKENS = {"low", "medium", "high"}; private static final byte[] STRENGTH_MASKS = {LOW_STRENGTH, MEDIUM_STRENGTH, HIGH_STRENGTH}; }