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
26package com.sun.security.sasl.util;
27
28import javax.security.sasl.*;
29import java.io.*;
30import java.util.Map;
31import java.util.StringTokenizer;
32
33import java.util.logging.Logger;
34import java.util.logging.Level;
35
36import sun.security.util.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 */
48public 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, 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}
366