001: /*
002: * Copyright 2000-2003 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.security.sasl.util;
027:
028: import javax.security.sasl.*;
029: import java.io.*;
030: import java.util.Map;
031: import java.util.StringTokenizer;
032: import java.security.AccessController;
033: import java.security.PrivilegedAction;
034:
035: import java.util.logging.Logger;
036: import java.util.logging.Level;
037:
038: import sun.misc.HexDumpEncoder;
039:
040: /**
041: * The base class used by client and server implementations of SASL
042: * mechanisms to process properties passed in the props argument
043: * and strings with the same format (e.g., used in digest-md5).
044: *
045: * Also contains utilities for doing int to network-byte-order
046: * transformations.
047: *
048: * @author Rosanna Lee
049: */
050: public abstract class AbstractSaslImpl {
051: /**
052: * Logger for debug messages
053: */
054: protected static Logger logger; // set in initLogger(); lazily loads logger
055:
056: protected boolean completed = false;
057: protected boolean privacy = false;
058: protected boolean integrity = false;
059: protected byte[] qop; // ordered list of qops
060: protected byte allQop; // a mask indicating which QOPs are requested
061: protected byte[] strength; // ordered list of cipher strengths
062:
063: // These are relevant only when privacy or integray have been negotiated
064: protected int sendMaxBufSize = 0; // specified by peer but can override
065: protected int recvMaxBufSize = 65536; // optionally specified by self
066: protected int rawSendSize; // derived from sendMaxBufSize
067:
068: protected String myClassName;
069:
070: protected AbstractSaslImpl(Map props, String className)
071: throws SaslException {
072: initLogger();
073: myClassName = className;
074:
075: // Parse properties to set desired context options
076: if (props != null) {
077: String prop;
078:
079: // "auth", "auth-int", "auth-conf"
080: qop = parseQop(prop = (String) props.get(Sasl.QOP));
081: logger.logp(Level.FINE, myClassName, "constructor",
082: "SASLIMPL01:Preferred qop property: {0}", prop);
083: allQop = combineMasks(qop);
084:
085: if (logger.isLoggable(Level.FINE)) {
086: logger.logp(Level.FINE, myClassName, "constructor",
087: "SASLIMPL02:Preferred qop mask: {0}", new Byte(
088: allQop));
089:
090: if (qop.length > 0) {
091: StringBuffer qopbuf = new StringBuffer();
092: for (int i = 0; i < qop.length; i++) {
093: qopbuf.append(Byte.toString(qop[i]));
094: qopbuf.append(' ');
095: }
096: logger.logp(Level.FINE, myClassName, "constructor",
097: "SASLIMPL03:Preferred qops : {0}", qopbuf
098: .toString());
099: }
100: }
101:
102: // "low", "medium", "high"
103: strength = parseStrength(prop = (String) props
104: .get(Sasl.STRENGTH));
105: logger
106: .logp(
107: Level.FINE,
108: myClassName,
109: "constructor",
110: "SASLIMPL04:Preferred strength property: {0}",
111: prop);
112: if (logger.isLoggable(Level.FINE) && strength.length > 0) {
113: StringBuffer strbuf = new StringBuffer();
114: for (int i = 0; i < strength.length; i++) {
115: strbuf.append(Byte.toString(strength[i]));
116: strbuf.append(' ');
117: }
118: logger.logp(Level.FINE, myClassName, "constructor",
119: "SASLIMPL05:Cipher strengths: {0}", strbuf
120: .toString());
121: }
122:
123: // Max receive buffer size
124: prop = (String) props.get(Sasl.MAX_BUFFER);
125: if (prop != null) {
126: try {
127: logger.logp(Level.FINE, myClassName, "constructor",
128: "SASLIMPL06:Max receive buffer size: {0}",
129: prop);
130: recvMaxBufSize = Integer.parseInt(prop);
131: } catch (NumberFormatException e) {
132: throw new SaslException(
133: "Property must be string representation of integer: "
134: + Sasl.MAX_BUFFER);
135: }
136: }
137:
138: // Max send buffer size
139: prop = (String) props.get(MAX_SEND_BUF);
140: if (prop != null) {
141: try {
142: logger.logp(Level.FINE, myClassName, "constructor",
143: "SASLIMPL07:Max send buffer size: {0}",
144: prop);
145: sendMaxBufSize = Integer.parseInt(prop);
146: } catch (NumberFormatException e) {
147: throw new SaslException(
148: "Property must be string representation of integer: "
149: + MAX_SEND_BUF);
150: }
151: }
152: } else {
153: qop = DEFAULT_QOP;
154: allQop = NO_PROTECTION;
155: strength = STRENGTH_MASKS;
156: }
157: }
158:
159: /**
160: * Determines whether this mechanism has completed.
161: *
162: * @return true if has completed; false otherwise;
163: */
164: public boolean isComplete() {
165: return completed;
166: }
167:
168: /**
169: * Retrieves the negotiated property.
170: * @exception SaslException if this authentication exchange has not completed
171: */
172: public Object getNegotiatedProperty(String propName) {
173: if (!completed) {
174: throw new IllegalStateException(
175: "SASL authentication not completed");
176: }
177:
178: if (propName.equals(Sasl.QOP)) {
179: if (privacy) {
180: return "auth-conf";
181: } else if (integrity) {
182: return "auth-int";
183: } else {
184: return "auth";
185: }
186: } else if (propName.equals(Sasl.MAX_BUFFER)) {
187: return Integer.toString(recvMaxBufSize);
188: } else if (propName.equals(Sasl.RAW_SEND_SIZE)) {
189: return Integer.toString(rawSendSize);
190: } else if (propName.equals(MAX_SEND_BUF)) {
191: return Integer.toString(sendMaxBufSize);
192: } else {
193: return null;
194: }
195: }
196:
197: protected static final byte combineMasks(byte[] in) {
198: byte answer = 0;
199: for (int i = 0; i < in.length; i++) {
200: answer |= in[i];
201: }
202: return answer;
203: }
204:
205: protected static final byte findPreferredMask(byte pref, byte[] in) {
206: for (int i = 0; i < in.length; i++) {
207: if ((in[i] & pref) != 0) {
208: return in[i];
209: }
210: }
211: return (byte) 0;
212: }
213:
214: private static final byte[] parseQop(String qop)
215: throws SaslException {
216: return parseQop(qop, null, false);
217: }
218:
219: protected static final byte[] parseQop(String qop,
220: String[] saveTokens, boolean ignore) throws SaslException {
221: if (qop == null) {
222: return DEFAULT_QOP; // default
223: }
224:
225: return parseProp(Sasl.QOP, qop, QOP_TOKENS, QOP_MASKS,
226: saveTokens, ignore);
227: }
228:
229: private static final byte[] parseStrength(String strength)
230: throws SaslException {
231: if (strength == null) {
232: return DEFAULT_STRENGTH; // default
233: }
234:
235: return parseProp(Sasl.STRENGTH, strength, STRENGTH_TOKENS,
236: STRENGTH_MASKS, null, false);
237: }
238:
239: private static final byte[] parseProp(String propName,
240: String propVal, String[] vals, byte[] masks,
241: String[] tokens, boolean ignore) throws SaslException {
242:
243: StringTokenizer parser = new StringTokenizer(propVal, ", \t\n");
244: String token;
245: byte[] answer = new byte[vals.length];
246: int i = 0;
247: boolean found;
248:
249: while (parser.hasMoreTokens() && i < answer.length) {
250: token = parser.nextToken();
251: found = false;
252: for (int j = 0; !found && j < vals.length; j++) {
253: if (token.equalsIgnoreCase(vals[j])) {
254: found = true;
255: answer[i++] = masks[j];
256: if (tokens != null) {
257: tokens[j] = token; // save what was parsed
258: }
259: }
260: }
261: if (!found && !ignore) {
262: throw new SaslException("Invalid token in " + propName
263: + ": " + propVal);
264: }
265: }
266: // Initialize rest of array with 0
267: for (int j = i; j < answer.length; j++) {
268: answer[j] = 0;
269: }
270: return answer;
271: }
272:
273: /**
274: * Outputs a byte array and converts
275: */
276: protected static final void traceOutput(String srcClass,
277: String srcMethod, String traceTag, byte[] output) {
278: traceOutput(srcClass, srcMethod, traceTag, output, 0,
279: output.length);
280: }
281:
282: protected static final void traceOutput(String srcClass,
283: String srcMethod, String traceTag, byte[] output,
284: int offset, int len) {
285: try {
286: int origlen = len;
287: Level lev;
288:
289: if (!logger.isLoggable(Level.FINEST)) {
290: len = Math.min(16, len);
291: lev = Level.FINER;
292: } else {
293: lev = Level.FINEST;
294: }
295:
296: ByteArrayOutputStream out = new ByteArrayOutputStream(len);
297: new HexDumpEncoder().encodeBuffer(new ByteArrayInputStream(
298: output, offset, len), out);
299:
300: // Message id supplied by caller as part of traceTag
301: logger.logp(lev, srcClass, srcMethod, "{0} ( {1} ): {2}",
302: new Object[] { traceTag, new Integer(origlen),
303: out.toString() });
304: } catch (Exception e) {
305: logger.logp(Level.WARNING, srcClass, srcMethod,
306: "SASLIMPL09:Error generating trace output: {0}", e);
307: }
308: }
309:
310: /**
311: * Returns the integer represented by 4 bytes in network byte order.
312: */
313: protected static final int networkByteOrderToInt(byte[] buf,
314: int start, int count) {
315: if (count > 4) {
316: throw new IllegalArgumentException(
317: "Cannot handle more than 4 bytes");
318: }
319:
320: int answer = 0;
321:
322: for (int i = 0; i < count; i++) {
323: answer <<= 8;
324: answer |= ((int) buf[start + i] & 0xff);
325: }
326: return answer;
327: }
328:
329: /**
330: * Encodes an integer into 4 bytes in network byte order in the buffer
331: * supplied.
332: */
333: protected static final void intToNetworkByteOrder(int num,
334: byte[] buf, int start, int count) {
335: if (count > 4) {
336: throw new IllegalArgumentException(
337: "Cannot handle more than 4 bytes");
338: }
339:
340: for (int i = count - 1; i >= 0; i--) {
341: buf[start + i] = (byte) (num & 0xff);
342: num >>>= 8;
343: }
344: }
345:
346: /**
347: * Sets logger field.
348: */
349: private static synchronized void initLogger() {
350: if (logger == null) {
351: logger = Logger.getLogger(SASL_LOGGER_NAME);
352: }
353: }
354:
355: // ---------------- Constants -----------------
356: private static final String SASL_LOGGER_NAME = "javax.security.sasl";
357: protected static final String MAX_SEND_BUF = "javax.security.sasl.sendmaxbuffer";
358:
359: // default 0 (no protection); 1 (integrity only)
360: protected static final byte NO_PROTECTION = (byte) 1;
361: protected static final byte INTEGRITY_ONLY_PROTECTION = (byte) 2;
362: protected static final byte PRIVACY_PROTECTION = (byte) 4;
363:
364: protected static final byte LOW_STRENGTH = (byte) 1;
365: protected static final byte MEDIUM_STRENGTH = (byte) 2;
366: protected static final byte HIGH_STRENGTH = (byte) 4;
367:
368: private static final byte[] DEFAULT_QOP = new byte[] { NO_PROTECTION };
369: private static final String[] QOP_TOKENS = { "auth-conf",
370: "auth-int", "auth" };
371: private static final byte[] QOP_MASKS = { PRIVACY_PROTECTION,
372: INTEGRITY_ONLY_PROTECTION, NO_PROTECTION };
373:
374: private static final byte[] DEFAULT_STRENGTH = new byte[] {
375: HIGH_STRENGTH, MEDIUM_STRENGTH, LOW_STRENGTH };
376: private static final String[] STRENGTH_TOKENS = { "low", "medium",
377: "high" };
378: private static final byte[] STRENGTH_MASKS = { LOW_STRENGTH,
379: MEDIUM_STRENGTH, HIGH_STRENGTH };
380: }
|