jdk/src/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java
changeset 2 90ce3da70b43
child 3472 ff6f0e6f7c83
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,366 @@
+/*
+ * Copyright 2000-2003 Sun Microsystems, Inc.  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.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.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.security.AccessController;
+import java.security.PrivilegedAction;
+
+import java.util.logging.Logger;
+import java.util.logging.Level;
+
+import sun.misc.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 {
+    /**
+     * Logger for debug messages
+     */
+    protected static Logger logger;  // set in initLogger(); lazily loads logger
+
+    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 props, String className) throws SaslException {
+        initLogger();
+        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}", new Byte(allQop));
+
+                if (qop.length > 0) {
+                    StringBuffer qopbuf = new StringBuffer();
+                    for (int i = 0; i < qop.length; i++) {
+                        qopbuf.append(Byte.toString(qop[i]));
+                        qopbuf.append(' ');
+                    }
+                    logger.logp(Level.FINE, myClassName, "constructor",
+                        "SASLIMPL03:Preferred qops : {0}", qopbuf.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) {
+                StringBuffer strbuf = new StringBuffer();
+                for (int i = 0; i < strength.length; i++) {
+                    strbuf.append(Byte.toString(strength[i]));
+                    strbuf.append(' ');
+                }
+                logger.logp(Level.FINE, myClassName, "constructor",
+                    "SASLIMPL05:Cipher strengths: {0}", strbuf.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 SaslException if this authentication exchange has not completed
+     */
+    public Object getNegotiatedProperty(String propName) {
+        if (!completed) {
+            throw new IllegalStateException("SASL authentication not completed");
+        }
+
+        if (propName.equals(Sasl.QOP)) {
+            if (privacy) {
+                return "auth-conf";
+            } else if (integrity) {
+                return "auth-int";
+            } else {
+                return "auth";
+            }
+        } else if (propName.equals(Sasl.MAX_BUFFER)) {
+            return Integer.toString(recvMaxBufSize);
+        } else if (propName.equals(Sasl.RAW_SEND_SIZE)) {
+            return Integer.toString(rawSendSize);
+        } else if (propName.equals(MAX_SEND_BUF)) {
+            return Integer.toString(sendMaxBufSize);
+        } else {
+            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 and converts
+     */
+    protected static final void traceOutput(String srcClass, String srcMethod,
+        String traceTag, byte[] output) {
+        traceOutput(srcClass, srcMethod, traceTag, output, 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;
+            }
+
+            ByteArrayOutputStream out = new ByteArrayOutputStream(len);
+            new HexDumpEncoder().encodeBuffer(
+                new ByteArrayInputStream(output, offset, len), out);
+
+            // Message id supplied by caller as part of traceTag
+            logger.logp(lev, srcClass, srcMethod, "{0} ( {1} ): {2}",
+                new Object[] {traceTag, new Integer(origlen), out.toString()});
+        } 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;
+        }
+    }
+
+    /**
+     * Sets logger field.
+     */
+    private static synchronized void initLogger() {
+        if (logger == null) {
+            logger = Logger.getLogger(SASL_LOGGER_NAME);
+        }
+    }
+
+    // ---------------- Constants  -----------------
+    private static final String SASL_LOGGER_NAME = "javax.security.sasl";
+    protected static final String MAX_SEND_BUF = "javax.security.sasl.sendmaxbuffer";
+
+    // 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};
+}