1/*
2 * Copyright (c) 2000, 2006, 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 sun.security.jgss;
27
28import org.ietf.jgss.GSSException;
29import java.io.InputStream;
30import java.io.OutputStream;
31import java.io.IOException;
32import sun.security.util.*;
33
34/**
35 * This class represents the mechanism independent part of a GSS-API
36 * context establishment token. Some mechanisms may choose to encode
37 * all subsequent tokens as well such that they start with an encoding
38 * of an instance of this class. e.g., The Kerberos v5 GSS-API Mechanism
39 * uses this header for all GSS-API tokens.
40 * <p>
41 * The format is specified in RFC 2743 section 3.1.
42 *
43 * @author Mayank Upadhyay
44 */
45
46/*
47 * The RFC states that implementations should explicitly follow the
48 * encoding scheme descibed in this section rather than use ASN.1
49 * compilers. However, we should consider removing duplicate ASN.1
50 * like code from here and depend on sun.security.util if possible.
51 */
52
53public class GSSHeader {
54
55    private ObjectIdentifier mechOid = null;
56    private byte[] mechOidBytes = null;
57    private int mechTokenLength = 0;
58
59    /**
60     * The tag defined in the GSS-API mechanism independent token
61     * format.
62     */
63    public static final int TOKEN_ID=0x60;
64
65    /**
66     * Creates a GSSHeader instance whose encoding can be used as the
67     * prefix for a particular mechanism token.
68     * @param mechOid the Oid of the mechanism which generated the token
69     * @param mechTokenLength the length of the subsequent portion that
70     * the mechanism will be adding.
71     */
72    public GSSHeader(ObjectIdentifier mechOid, int mechTokenLength)
73        throws IOException {
74
75        this.mechOid = mechOid;
76        DerOutputStream temp = new DerOutputStream();
77        temp.putOID(mechOid);
78        mechOidBytes = temp.toByteArray();
79        this.mechTokenLength = mechTokenLength;
80    }
81
82    /**
83     * Reads in a GSSHeader from an InputStream. Typically this would be
84     * used as part of reading the complete token from an InputStream
85     * that is obtained from a socket.
86     */
87    public GSSHeader(InputStream is)
88        throws IOException, GSSException {
89
90        //      debug("Parsing GSS token: ");
91
92        int tag = is.read();
93
94        //      debug("tag=" + tag);
95
96        if (tag != TOKEN_ID)
97            throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1,
98                                   "GSSHeader did not find the right tag");
99
100        int length = getLength(is);
101
102        DerValue temp = new DerValue(is);
103        mechOidBytes = temp.toByteArray();
104        mechOid = temp.getOID();
105        //      debug (" oid=" + mechOid);
106
107        //      debug (" len starting with oid=" + length);
108        mechTokenLength = length - mechOidBytes.length;
109
110        //      debug("  mechToken length=" + mechTokenLength);
111
112    }
113
114    /**
115     * Used to obtain the Oid stored in this GSSHeader instance.
116     * @return the Oid of the mechanism.
117     */
118    public ObjectIdentifier getOid() {
119        return mechOid;
120    }
121
122    /**
123     * Used to obtain the length of the mechanism specific token that
124     * will follow the encoding of this GSSHeader instance.
125     * @return the length of the mechanism specific token portion that
126     * will follow this GSSHeader.
127     */
128    public int getMechTokenLength() {
129        return mechTokenLength;
130    }
131
132    /**
133     * Used to obtain the length of the encoding of this GSSHeader.
134     * @return the lenght of the encoding of this GSSHeader instance.
135     */
136    public int getLength() {
137        int lenField = mechOidBytes.length + mechTokenLength;
138        return (1 + getLenFieldSize(lenField) + mechOidBytes.length);
139    }
140
141    /**
142     * Used to determine what the maximum possible mechanism token
143     * size is if the complete GSSToken returned to the application
144     * (including a GSSHeader) is not to exceed some pre-determined
145     * value in size.
146     * @param mechOid the Oid of the mechanism that will generate
147     * this GSS-API token
148     * @param maxTotalSize the pre-determined value that serves as a
149     * maximum size for the complete GSS-API token (including a
150     * GSSHeader)
151     * @return the maximum size of mechanism token that can be used
152     * so as to not exceed maxTotalSize with the GSS-API token
153     */
154    public static int getMaxMechTokenSize(ObjectIdentifier mechOid,
155                                          int maxTotalSize) {
156
157        int mechOidBytesSize = 0;
158        try {
159            DerOutputStream temp = new DerOutputStream();
160            temp.putOID(mechOid);
161            mechOidBytesSize = temp.toByteArray().length;
162        } catch (IOException e) {
163        }
164
165        // Subtract bytes needed for 0x60 tag and mechOidBytes
166        maxTotalSize -= (1 + mechOidBytesSize);
167
168        // Subtract maximum len bytes
169        maxTotalSize -= 5;
170
171        return maxTotalSize;
172
173        /*
174         * Len field and mechanism token must fit in remaining
175         * space. The range of the len field that we allow is
176         * 1 through 5.
177         *
178
179         int mechTokenSize = 0;
180         for (int lenFieldSize = 1; lenFieldSize <= 5;
181         lenFieldSize++) {
182         mechTokenSize = maxTotalSize - lenFieldSize;
183         if (getLenFieldSize(mechTokenSize + mechOidBytesSize +
184         lenFieldSize) <= lenFieldSize)
185         break;
186         }
187
188         return mechTokenSize;
189        */
190
191
192    }
193
194    /**
195     * Used to determine the number of bytes that will be need to encode
196     * the length field of the GSSHeader.
197     */
198    private int getLenFieldSize(int len) {
199        int retVal = 1;
200        if (len < 128) {
201            retVal=1;
202        } else if (len < (1 << 8)) {
203            retVal=2;
204        } else if (len < (1 << 16)) {
205            retVal=3;
206        } else if (len < (1 << 24)) {
207            retVal=4;
208        } else {
209            retVal=5; // See getMaxMechTokenSize
210        }
211        return retVal;
212    }
213
214    /**
215     * Encodes this GSSHeader instance onto the provided OutputStream.
216     * @param os the OutputStream to which the token should be written.
217     * @return the number of bytes that are output as a result of this
218     * encoding
219     */
220    public int encode(OutputStream os) throws IOException {
221        int retVal = 1 + mechOidBytes.length;
222        os.write(TOKEN_ID);
223        int length = mechOidBytes.length + mechTokenLength;
224        retVal += putLength(length, os);
225        os.write(mechOidBytes);
226        return retVal;
227    }
228
229    /**
230     * Get a length from the input stream, allowing for at most 32 bits of
231     * encoding to be used. (Not the same as getting a tagged integer!)
232     *
233     * @return the length or -1 if indefinite length found.
234     * @exception IOException on parsing error or unsupported lengths.
235     */
236    // shameless lifted from sun.security.util.DerInputStream.
237    private int getLength(InputStream in) throws IOException {
238        return getLength(in.read(), in);
239    }
240
241    /**
242     * Get a length from the input stream, allowing for at most 32 bits of
243     * encoding to be used. (Not the same as getting a tagged integer!)
244     *
245     * @return the length or -1 if indefinite length found.
246     * @exception IOException on parsing error or unsupported lengths.
247     */
248    // shameless lifted from sun.security.util.DerInputStream.
249    private int getLength(int lenByte, InputStream in) throws IOException {
250        int value, tmp;
251
252        tmp = lenByte;
253        if ((tmp & 0x080) == 0x00) { // short form, 1 byte datum
254            value = tmp;
255        } else {                                         // long form or indefinite
256            tmp &= 0x07f;
257
258            /*
259             * NOTE:  tmp == 0 indicates indefinite length encoded data.
260             * tmp > 4 indicates more than 4Gb of data.
261             */
262            if (tmp == 0)
263                return -1;
264            if (tmp < 0 || tmp > 4)
265                throw new IOException("DerInputStream.getLength(): lengthTag="
266                                      + tmp + ", "
267                                      + ((tmp < 0) ? "incorrect DER encoding." : "too big."));
268
269            for (value = 0; tmp > 0; tmp --) {
270                value <<= 8;
271                value += 0x0ff & in.read();
272            }
273            if (value < 0) {
274                throw new IOException("Invalid length bytes");
275            }
276        }
277        return value;
278    }
279
280    /**
281     * Put the encoding of the length in the specified stream.
282     *
283     * @params len the length of the attribute.
284     * @param out the outputstream to write the length to
285     * @return the number of bytes written
286     * @exception IOException on writing errors.
287     */
288    // Shameless lifted from sun.security.util.DerOutputStream.
289    private int putLength(int len, OutputStream out) throws IOException {
290        int retVal = 0;
291        if (len < 128) {
292            out.write((byte)len);
293            retVal=1;
294
295        } else if (len < (1 << 8)) {
296            out.write((byte)0x081);
297            out.write((byte)len);
298            retVal=2;
299
300        } else if (len < (1 << 16)) {
301            out.write((byte)0x082);
302            out.write((byte)(len >> 8));
303            out.write((byte)len);
304            retVal=3;
305
306        } else if (len < (1 << 24)) {
307            out.write((byte)0x083);
308            out.write((byte)(len >> 16));
309            out.write((byte)(len >> 8));
310            out.write((byte)len);
311            retVal=4;
312
313        } else {
314            out.write((byte)0x084);
315            out.write((byte)(len >> 24));
316            out.write((byte)(len >> 16));
317            out.write((byte)(len >> 8));
318            out.write((byte)len);
319            retVal=5;
320        }
321
322        return retVal;
323    }
324
325    // XXX Call these two in some central class
326    private void debug(String str) {
327        System.err.print(str);
328    }
329
330    private  String getHexBytes(byte[] bytes, int len)
331        throws IOException {
332
333        StringBuilder sb = new StringBuilder();
334        for (int i = 0; i < len; i++) {
335
336            int b1 = (bytes[i]>>4) & 0x0f;
337            int b2 = bytes[i] & 0x0f;
338
339            sb.append(Integer.toHexString(b1));
340            sb.append(Integer.toHexString(b2));
341            sb.append(' ');
342        }
343        return sb.toString();
344    }
345}
346