1 /* 2 * Copyright (c) 2000, 2013, 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 com.sun.security.sasl.util; 27 28 import javax.security.sasl.*; 29 import java.io.*; 30 import java.util.Map; 31 import java.util.StringTokenizer; 32 33 import java.util.logging.Logger; 34 import java.util.logging.Level; 35 36 import sun.misc.HexDumpEncoder; 37 38 /** 39 * The base class used by client and server implementations of SASL 40 * mechanisms to process properties passed in the props argument 41 * and strings with the same format (e.g., used in digest-md5). 42 * 43 * Also contains utilities for doing int to network-byte-order 44 * transformations. 45 * 46 * @author Rosanna Lee 47 */ 48 public abstract class AbstractSaslImpl { 49 50 protected boolean completed = false; 51 protected boolean privacy = false; 52 protected boolean integrity = false; 53 protected byte[] qop; // ordered list of qops 54 protected byte allQop; // a mask indicating which QOPs are requested 55 protected byte[] strength; // ordered list of cipher strengths 56 57 // These are relevant only when privacy or integray have been negotiated 58 protected int sendMaxBufSize = 0; // specified by peer but can override 59 protected int recvMaxBufSize = 65536; // optionally specified by self 60 protected int rawSendSize; // derived from sendMaxBufSize 61 62 protected String myClassName; 63 64 protected AbstractSaslImpl(Map<String, ?> props, String className) 65 throws SaslException { 66 myClassName = className; 67 68 // Parse properties to set desired context options 69 if (props != null) { 70 String prop; 71 72 // "auth", "auth-int", "auth-conf" 73 qop = parseQop(prop=(String)props.get(Sasl.QOP)); 74 logger.logp(Level.FINE, myClassName, "constructor", 75 "SASLIMPL01:Preferred qop property: {0}", prop); 76 allQop = combineMasks(qop); 77 78 if (logger.isLoggable(Level.FINE)) { 79 logger.logp(Level.FINE, myClassName, "constructor", 80 "SASLIMPL02:Preferred qop mask: {0}", allQop); 81 82 if (qop.length > 0) { 83 StringBuilder str = new StringBuilder(); 84 for (int i = 0; i < qop.length; i++) { 85 str.append(Byte.toString(qop[i])); 86 str.append(' '); 87 } 88 logger.logp(Level.FINE, myClassName, "constructor", 89 "SASLIMPL03:Preferred qops : {0}", str.toString()); 90 } 91 } 92 93 // "low", "medium", "high" 94 strength = parseStrength(prop=(String)props.get(Sasl.STRENGTH)); 95 logger.logp(Level.FINE, myClassName, "constructor", 96 "SASLIMPL04:Preferred strength property: {0}", prop); 97 if (logger.isLoggable(Level.FINE) && strength.length > 0) { 98 StringBuilder str = new StringBuilder(); 99 for (int i = 0; i < strength.length; i++) { 100 str.append(Byte.toString(strength[i])); 101 str.append(' '); 102 } 103 logger.logp(Level.FINE, myClassName, "constructor", 104 "SASLIMPL05:Cipher strengths: {0}", str.toString()); 105 } 106 107 // Max receive buffer size 108 prop = (String)props.get(Sasl.MAX_BUFFER); 109 if (prop != null) { 110 try { 111 logger.logp(Level.FINE, myClassName, "constructor", 112 "SASLIMPL06:Max receive buffer size: {0}", prop); 113 recvMaxBufSize = Integer.parseInt(prop); 114 } catch (NumberFormatException e) { 115 throw new SaslException( 116 "Property must be string representation of integer: " + 117 Sasl.MAX_BUFFER); 118 } 119 } 120 121 // Max send buffer size 122 prop = (String)props.get(MAX_SEND_BUF); 123 if (prop != null) { 124 try { 125 logger.logp(Level.FINE, myClassName, "constructor", 126 "SASLIMPL07:Max send buffer size: {0}", prop); 127 sendMaxBufSize = Integer.parseInt(prop); 128 } catch (NumberFormatException e) { 129 throw new SaslException( 130 "Property must be string representation of integer: " + 131 MAX_SEND_BUF); 132 } 133 } 134 } else { 135 qop = DEFAULT_QOP; 136 allQop = NO_PROTECTION; 137 strength = STRENGTH_MASKS; 138 } 139 } 140 141 /** 142 * Determines whether this mechanism has completed. 143 * 144 * @return true if has completed; false otherwise; 145 */ 146 public boolean isComplete() { 147 return completed; 148 } 149 150 /** 151 * Retrieves the negotiated property. 152 * @exception IllegalStateException if this authentication exchange has 153 * not completed 154 */ 155 public Object getNegotiatedProperty(String propName) { 156 if (!completed) { 157 throw new IllegalStateException("SASL authentication not completed"); 158 } 159 switch (propName) { 160 case Sasl.QOP: 161 if (privacy) { 162 return "auth-conf"; 163 } else if (integrity) { 164 return "auth-int"; 165 } else { 166 return "auth"; 167 } 168 case Sasl.MAX_BUFFER: 169 return Integer.toString(recvMaxBufSize); 170 case Sasl.RAW_SEND_SIZE: 171 return Integer.toString(rawSendSize); 172 case MAX_SEND_BUF: 173 return Integer.toString(sendMaxBufSize); 174 default: 175 return null; 176 } 177 } 178 179 protected static final byte combineMasks(byte[] in) { 180 byte answer = 0; 181 for (int i = 0; i < in.length; i++) { 182 answer |= in[i]; 183 } 184 return answer; 185 } 186 187 protected static final byte findPreferredMask(byte pref, byte[] in) { 188 for (int i = 0; i < in.length; i++) { 189 if ((in[i]&pref) != 0) { 190 return in[i]; 191 } 192 } 193 return (byte)0; 194 } 195 196 private static final byte[] parseQop(String qop) throws SaslException { 197 return parseQop(qop, null, false); 198 } 199 200 protected static final byte[] parseQop(String qop, String[] saveTokens, 201 boolean ignore) throws SaslException { 202 if (qop == null) { 203 return DEFAULT_QOP; // default 204 } 205 206 return parseProp(Sasl.QOP, qop, QOP_TOKENS, QOP_MASKS, saveTokens, ignore); 207 } 208 209 private static final byte[] parseStrength(String strength) 210 throws SaslException { 211 if (strength == null) { 212 return DEFAULT_STRENGTH; // default 213 } 214 215 return parseProp(Sasl.STRENGTH, strength, STRENGTH_TOKENS, 216 STRENGTH_MASKS, null, false); 217 } 218 219 private static final byte[] parseProp(String propName, String propVal, 220 String[] vals, byte[] masks, String[] tokens, boolean ignore) 221 throws SaslException { 222 223 StringTokenizer parser = new StringTokenizer(propVal, ", \t\n"); 224 String token; 225 byte[] answer = new byte[vals.length]; 226 int i = 0; 227 boolean found; 228 229 while (parser.hasMoreTokens() && i < answer.length) { 230 token = parser.nextToken(); 231 found = false; 232 for (int j = 0; !found && j < vals.length; j++) { 233 if (token.equalsIgnoreCase(vals[j])) { 234 found = true; 235 answer[i++] = masks[j]; 236 if (tokens != null) { 237 tokens[j] = token; // save what was parsed 238 } 239 } 240 } 241 if (!found && !ignore) { 242 throw new SaslException( 243 "Invalid token in " + propName + ": " + propVal); 244 } 245 } 246 // Initialize rest of array with 0 247 for (int j = i; j < answer.length; j++) { 248 answer[j] = 0; 249 } 250 return answer; 251 } 252 253 254 /** 255 * Outputs a byte array. Can be null. 256 */ 257 protected static final void traceOutput(String srcClass, String srcMethod, 258 String traceTag, byte[] output) { 259 traceOutput(srcClass, srcMethod, traceTag, output, 0, 260 output == null ? 0 : output.length); 261 } 262 263 protected static final void traceOutput(String srcClass, String srcMethod, 264 String traceTag, byte[] output, int offset, int len) { 265 try { 266 int origlen = len; 267 Level lev; 268 269 if (!logger.isLoggable(Level.FINEST)) { 270 len = Math.min(16, len); 271 lev = Level.FINER; 272 } else { 273 lev = Level.FINEST; 274 } 275 276 String content; 277 278 if (output != null) { 279 ByteArrayOutputStream out = new ByteArrayOutputStream(len); 280 new HexDumpEncoder().encodeBuffer( 281 new ByteArrayInputStream(output, offset, len), out); 282 content = out.toString(); 283 } else { 284 content = "NULL"; 285 } 286 287 // Message id supplied by caller as part of traceTag 288 logger.logp(lev, srcClass, srcMethod, "{0} ( {1} ): {2}", 289 new Object[] {traceTag, new Integer(origlen), content}); 290 } catch (Exception e) { 291 logger.logp(Level.WARNING, srcClass, srcMethod, 292 "SASLIMPL09:Error generating trace output: {0}", e); 293 } 294 } 295 296 297 /** 298 * Returns the integer represented by 4 bytes in network byte order. 299 */ 300 protected static final int networkByteOrderToInt(byte[] buf, int start, 301 int count) { 302 if (count > 4) { 303 throw new IllegalArgumentException("Cannot handle more than 4 bytes"); 304 } 305 306 int answer = 0; 307 308 for (int i = 0; i < count; i++) { 309 answer <<= 8; 310 answer |= ((int)buf[start+i] & 0xff); 311 } 312 return answer; 313 } 314 315 /** 316 * Encodes an integer into 4 bytes in network byte order in the buffer 317 * supplied. 318 */ 319 protected static final void intToNetworkByteOrder(int num, byte[] buf, 320 int start, int count) { 321 if (count > 4) { 322 throw new IllegalArgumentException("Cannot handle more than 4 bytes"); 323 } 324 325 for (int i = count-1; i >= 0; i--) { 326 buf[start+i] = (byte)(num & 0xff); 327 num >>>= 8; 328 } 329 } 330 331 // ---------------- Constants ----------------- 332 private static final String SASL_LOGGER_NAME = "javax.security.sasl"; 333 protected static final String MAX_SEND_BUF = "javax.security.sasl.sendmaxbuffer"; 334 335 /** 336 * Logger for debug messages 337 */ 338 protected static final Logger logger = Logger.getLogger(SASL_LOGGER_NAME); 339 340 // default 0 (no protection); 1 (integrity only) 341 protected static final byte NO_PROTECTION = (byte)1; 342 protected static final byte INTEGRITY_ONLY_PROTECTION = (byte)2; 343 protected static final byte PRIVACY_PROTECTION = (byte)4; 344 345 protected static final byte LOW_STRENGTH = (byte)1; 346 protected static final byte MEDIUM_STRENGTH = (byte)2; 347 protected static final byte HIGH_STRENGTH = (byte)4; 348 349 private static final byte[] DEFAULT_QOP = new byte[]{NO_PROTECTION}; 350 private static final String[] QOP_TOKENS = {"auth-conf", 351 "auth-int", 352 "auth"}; 353 private static final byte[] QOP_MASKS = {PRIVACY_PROTECTION, 354 INTEGRITY_ONLY_PROTECTION, 355 NO_PROTECTION}; 356 357 private static final byte[] DEFAULT_STRENGTH = new byte[]{ 358 HIGH_STRENGTH, MEDIUM_STRENGTH, LOW_STRENGTH}; 359 private static final String[] STRENGTH_TOKENS = {"low", 360 "medium", 361 "high"}; 362 private static final byte[] STRENGTH_MASKS = {LOW_STRENGTH, 363 MEDIUM_STRENGTH, 364 HIGH_STRENGTH}; 365 }