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 }