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