blob: c69dde04757cbf60b212495cc2d2f2b00d9f6607 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2000-2007 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 sun.security.jgss.krb5;
27
28import org.ietf.jgss.*;
29import sun.security.jgss.*;
30import java.security.GeneralSecurityException;
31import java.io.InputStream;
32import java.io.OutputStream;
33import java.io.IOException;
34import java.io.ByteArrayInputStream;
35import java.io.ByteArrayOutputStream;
36import sun.security.krb5.Confounder;
37import sun.security.krb5.KrbException;
38
39/**
40 * This class represents a token emitted by the GSSContext.wrap()
41 * call. It is a MessageToken except that it also contains plaintext
42 * or encrypted data at the end. A wrapToken has certain other rules
43 * that are peculiar to it and different from a MICToken, which is
44 * another type of MessageToken. All data in a WrapToken is prepended
45 * by a random counfounder of 8 bytes. All data in a WrapToken is
46 * also padded with one to eight bytes where all bytes are equal in
47 * value to the number of bytes being padded. Thus, all application
48 * data is replaced by (confounder || data || padding).
49 *
50 * @author Mayank Upadhyay
51 */
52class WrapToken extends MessageToken {
53 /**
54 * The size of the random confounder used in a WrapToken.
55 */
56 static final int CONFOUNDER_SIZE = 8;
57
58 /*
59 * The padding used with a WrapToken. All data is padded to the
60 * next multiple of 8 bytes, even if its length is already
61 * multiple of 8.
62 * Use this table as a quick way to obtain padding bytes by
63 * indexing it with the number of padding bytes required.
64 */
65 static final byte[][] pads = {
66 null, // No, no one escapes padding
67 {0x01},
68 {0x02, 0x02},
69 {0x03, 0x03, 0x03},
70 {0x04, 0x04, 0x04, 0x04},
71 {0x05, 0x05, 0x05, 0x05, 0x05},
72 {0x06, 0x06, 0x06, 0x06, 0x06, 0x06},
73 {0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07},
74 {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}
75 };
76
77 /*
78 * A token may come in either in an InputStream or as a
79 * byte[]. Store a reference to it in either case and process
80 * it's data only later when getData() is called and
81 * decryption/copying is needed to be done. Note that JCE can
82 * decrypt both from a byte[] and from an InputStream.
83 */
84 private boolean readTokenFromInputStream = true;
85 private InputStream is = null;
86 private byte[] tokenBytes = null;
87 private int tokenOffset = 0;
88 private int tokenLen = 0;
89
90 /*
91 * Application data may come from an InputStream or from a
92 * byte[]. However, it will always be stored and processed as a
93 * byte[] since
94 * (a) the MessageDigest class only accepts a byte[] as input and
95 * (b) It allows writing to an OuputStream via a CipherOutputStream.
96 */
97 private byte[] dataBytes = null;
98 private int dataOffset = 0;
99 private int dataLen = 0;
100
101 // the len of the token data: (confounder || data || padding)
102 private int dataSize = 0;
103
104 // Accessed by CipherHelper
105 byte[] confounder = null;
106 byte[] padding = null;
107
108 private boolean privacy = false;
109
110 /**
111 * Constructs a WrapToken from token bytes obtained from the
112 * peer.
113 * @param context the mechanism context associated with this
114 * token
115 * @param tokenBytes the bytes of the token
116 * @param tokenOffset the offset of the token
117 * @param tokenLen the length of the token
118 * @param prop the MessageProp into which characteristics of the
119 * parsed token will be stored.
120 * @throws GSSException if the token is defective
121 */
122 public WrapToken(Krb5Context context,
123 byte[] tokenBytes, int tokenOffset, int tokenLen,
124 MessageProp prop) throws GSSException {
125
126 // Just parse the MessageToken part first
127 super(Krb5Token.WRAP_ID, context,
128 tokenBytes, tokenOffset, tokenLen, prop);
129
130 this.readTokenFromInputStream = false;
131
132 // Will need the token bytes again when extracting data
133 this.tokenBytes = tokenBytes;
134 this.tokenOffset = tokenOffset;
135 this.tokenLen = tokenLen;
136 this.privacy = prop.getPrivacy();
137 dataSize =
138 getGSSHeader().getMechTokenLength() - getKrb5TokenSize();
139 }
140
141 /**
142 * Constructs a WrapToken from token bytes read on the fly from
143 * an InputStream.
144 * @param context the mechanism context associated with this
145 * token
146 * @param is the InputStream containing the token bytes
147 * @param prop the MessageProp into which characteristics of the
148 * parsed token will be stored.
149 * @throws GSSException if the token is defective or if there is
150 * a problem reading from the InputStream
151 */
152 public WrapToken(Krb5Context context,
153 InputStream is, MessageProp prop)
154 throws GSSException {
155
156 // Just parse the MessageToken part first
157 super(Krb5Token.WRAP_ID, context, is, prop);
158
159 // Will need the token bytes again when extracting data
160 this.is = is;
161 this.privacy = prop.getPrivacy();
162 /*
163 debug("WrapToken Cons: gssHeader.getMechTokenLength=" +
164 getGSSHeader().getMechTokenLength());
165 debug("\n token size="
166 + getTokenSize());
167 */
168
169 dataSize =
170 getGSSHeader().getMechTokenLength() - getTokenSize();
171 // debug("\n dataSize=" + dataSize);
172 // debug("\n");
173 }
174
175 /**
176 * Obtains the application data that was transmitted in this
177 * WrapToken.
178 * @return a byte array containing the application data
179 * @throws GSSException if an error occurs while decrypting any
180 * cipher text and checking for validity
181 */
182 public byte[] getData() throws GSSException {
183
184 byte[] temp = new byte[dataSize];
185 getData(temp, 0);
186
187 // Remove the confounder and the padding
188 byte[] retVal = new byte[dataSize - confounder.length -
189 padding.length];
190 System.arraycopy(temp, 0, retVal, 0, retVal.length);
191
192 return retVal;
193 }
194
195 /**
196 * Obtains the application data that was transmitted in this
197 * WrapToken, writing it into an application provided output
198 * array.
199 * @param dataBuf the output buffer into which the data must be
200 * written
201 * @param dataBufOffset the offset at which to write the data
202 * @return the size of the data written
203 * @throws GSSException if an error occurs while decrypting any
204 * cipher text and checking for validity
205 */
206 public int getData(byte[] dataBuf, int dataBufOffset)
207 throws GSSException {
208
209 if (readTokenFromInputStream)
210 getDataFromStream(dataBuf, dataBufOffset);
211 else
212 getDataFromBuffer(dataBuf, dataBufOffset);
213
214 return (dataSize - confounder.length - padding.length);
215 }
216
217 /**
218 * Helper routine to obtain the application data transmitted in
219 * this WrapToken. It is called if the WrapToken was constructed
220 * with a byte array as input.
221 * @param dataBuf the output buffer into which the data must be
222 * written
223 * @param dataBufOffset the offset at which to write the data
224 * @throws GSSException if an error occurs while decrypting any
225 * cipher text and checking for validity
226 */
227 private void getDataFromBuffer(byte[] dataBuf, int dataBufOffset)
228 throws GSSException {
229
230 GSSHeader gssHeader = getGSSHeader();
231 int dataPos = tokenOffset +
232 gssHeader.getLength() + getTokenSize();
233
234 if (dataPos + dataSize > tokenOffset + tokenLen)
235 throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1,
236 "Insufficient data in "
237 + getTokenName(getTokenId()));
238
239 // debug("WrapToken cons: data is token is [" +
240 // getHexBytes(tokenBytes, tokenOffset, tokenLen) + "]\n");
241
242 confounder = new byte[CONFOUNDER_SIZE];
243
244 // Do decryption if this token was privacy protected.
245
246 if (privacy) {
247 cipherHelper.decryptData(this,
248 tokenBytes, dataPos, dataSize, dataBuf, dataBufOffset);
249 /*
250 debug("\t\tDecrypted data is [" +
251 getHexBytes(confounder) + " " +
252 getHexBytes(dataBuf, dataBufOffset,
253 dataSize - CONFOUNDER_SIZE - padding.length) +
254 getHexBytes(padding) +
255 "]\n");
256 */
257
258 } else {
259
260 // Token data is in cleartext
261 // debug("\t\tNo encryption was performed by peer.\n");
262 System.arraycopy(tokenBytes, dataPos,
263 confounder, 0, CONFOUNDER_SIZE);
264 int padSize = tokenBytes[dataPos + dataSize - 1];
265 if (padSize < 0)
266 padSize = 0;
267 if (padSize > 8)
268 padSize %= 8;
269
270 padding = pads[padSize];
271 // debug("\t\tPadding applied was: " + padSize + "\n");
272
273 System.arraycopy(tokenBytes, dataPos + CONFOUNDER_SIZE,
274 dataBuf, dataBufOffset, dataSize -
275 CONFOUNDER_SIZE - padSize);
276
277 // byte[] debugbuf = new byte[dataSize - CONFOUNDER_SIZE - padSize];
278 // System.arraycopy(tokenBytes, dataPos + CONFOUNDER_SIZE,
279 // debugbuf, 0, debugbuf.length);
280 // debug("\t\tData is: " + getHexBytes(debugbuf, debugbuf.length));
281 }
282
283 /*
284 * Make sure sign and sequence number are not corrupt
285 */
286
287 if (!verifySignAndSeqNumber(confounder,
288 dataBuf, dataBufOffset,
289 dataSize - CONFOUNDER_SIZE
290 - padding.length,
291 padding))
292 throw new GSSException(GSSException.BAD_MIC, -1,
293 "Corrupt checksum or sequence number in Wrap token");
294 }
295
296 /**
297 * Helper routine to obtain the application data transmitted in
298 * this WrapToken. It is called if the WrapToken was constructed
299 * with an Inputstream.
300 * @param dataBuf the output buffer into which the data must be
301 * written
302 * @param dataBufOffset the offset at which to write the data
303 * @throws GSSException if an error occurs while decrypting any
304 * cipher text and checking for validity
305 */
306 private void getDataFromStream(byte[] dataBuf, int dataBufOffset)
307 throws GSSException {
308
309 GSSHeader gssHeader = getGSSHeader();
310
311 // Don't check the token length. Data will be read on demand from
312 // the InputStream.
313
314 // debug("WrapToken cons: data will be read from InputStream.\n");
315
316 confounder = new byte[CONFOUNDER_SIZE];
317
318 try {
319
320 // Do decryption if this token was privacy protected.
321
322 if (privacy) {
323 cipherHelper.decryptData(this, is, dataSize,
324 dataBuf, dataBufOffset);
325
326 // debug("\t\tDecrypted data is [" +
327 // getHexBytes(confounder) + " " +
328 // getHexBytes(dataBuf, dataBufOffset,
329 // dataSize - CONFOUNDER_SIZE - padding.length) +
330 // getHexBytes(padding) +
331 // "]\n");
332
333 } else {
334
335 // Token data is in cleartext
336 // debug("\t\tNo encryption was performed by peer.\n");
337 readFully(is, confounder);
338
339 // Data is always a multiple of 8 with this GSS Mech
340 // Copy all but last block as they are
341 int numBlocks = (dataSize - CONFOUNDER_SIZE)/8 - 1;
342 int offset = dataBufOffset;
343 for (int i = 0; i < numBlocks; i++) {
344 readFully(is, dataBuf, offset, 8);
345 offset += 8;
346 }
347
348 byte[] finalBlock = new byte[8];
349 readFully(is, finalBlock);
350
351 int padSize = finalBlock[7];
352 padding = pads[padSize];
353
354 // debug("\t\tPadding applied was: " + padSize + "\n");
355 System.arraycopy(finalBlock, 0, dataBuf, offset,
356 finalBlock.length - padSize);
357 }
358 } catch (IOException e) {
359 throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1,
360 getTokenName(getTokenId())
361 + ": " + e.getMessage());
362 }
363
364 /*
365 * Make sure sign and sequence number are not corrupt
366 */
367
368 if (!verifySignAndSeqNumber(confounder,
369 dataBuf, dataBufOffset,
370 dataSize - CONFOUNDER_SIZE
371 - padding.length,
372 padding))
373 throw new GSSException(GSSException.BAD_MIC, -1,
374 "Corrupt checksum or sequence number in Wrap token");
375 }
376
377
378 /**
379 * Helper routine to pick the right padding for a certain length
380 * of application data. Every application message has some
381 * padding between 1 and 8 bytes.
382 * @param len the length of the application data
383 * @return the padding to be applied
384 */
385 private byte[] getPadding(int len) {
386 int padSize = 0;
387 // For RC4-HMAC, all padding is rounded up to 1 byte.
388 // One byte is needed to say that there is 1 byte of padding.
389 if (cipherHelper.isArcFour()) {
390 padSize = 1;
391 } else {
392 padSize = len % 8;
393 padSize = 8 - padSize;
394 }
395 return pads[padSize];
396 }
397
398 public WrapToken(Krb5Context context, MessageProp prop,
399 byte[] dataBytes, int dataOffset, int dataLen)
400 throws GSSException {
401
402 super(Krb5Token.WRAP_ID, context);
403
404 confounder = Confounder.bytes(CONFOUNDER_SIZE);
405
406 padding = getPadding(dataLen);
407 dataSize = confounder.length + dataLen + padding.length;
408 this.dataBytes = dataBytes;
409 this.dataOffset = dataOffset;
410 this.dataLen = dataLen;
411
412 /*
413 debug("\nWrapToken cons: data to wrap is [" +
414 getHexBytes(confounder) + " " +
415 getHexBytes(dataBytes, dataOffset, dataLen) + " " +
416 // padding is never null for Wrap
417 getHexBytes(padding) + "]\n");
418 */
419
420 genSignAndSeqNumber(prop,
421 confounder,
422 dataBytes, dataOffset, dataLen,
423 padding);
424
425 /*
426 * If the application decides to ask for privacy when the context
427 * did not negotiate for it, do not provide it. The peer might not
428 * have support for it. The app will realize this with a call to
429 * pop.getPrivacy() after wrap().
430 */
431 if (!context.getConfState())
432 prop.setPrivacy(false);
433
434 privacy = prop.getPrivacy();
435 }
436
437 public void encode(OutputStream os) throws IOException, GSSException {
438
439 super.encode(os);
440
441 // debug("Writing data: [");
442 if (!privacy) {
443
444 // debug(getHexBytes(confounder, confounder.length));
445 os.write(confounder);
446
447 // debug(" " + getHexBytes(dataBytes, dataOffset, dataLen));
448 os.write(dataBytes, dataOffset, dataLen);
449
450 // debug(" " + getHexBytes(padding, padding.length));
451 os.write(padding);
452
453 } else {
454
455 cipherHelper.encryptData(this, confounder,
456 dataBytes, dataOffset, dataLen, padding, os);
457 }
458 // debug("]\n");
459 }
460
461 public byte[] encode() throws IOException, GSSException {
462 // XXX Fine tune this initial size
463 ByteArrayOutputStream bos = new ByteArrayOutputStream(dataSize + 50);
464 encode(bos);
465 return bos.toByteArray();
466 }
467
468 public int encode(byte[] outToken, int offset)
469 throws IOException, GSSException {
470
471 // Token header is small
472 ByteArrayOutputStream bos = new ByteArrayOutputStream();
473 super.encode(bos);
474 byte[] header = bos.toByteArray();
475 System.arraycopy(header, 0, outToken, offset, header.length);
476 offset += header.length;
477
478 // debug("WrapToken.encode: Writing data: [");
479 if (!privacy) {
480
481 // debug(getHexBytes(confounder, confounder.length));
482 System.arraycopy(confounder, 0, outToken, offset,
483 confounder.length);
484 offset += confounder.length;
485
486 // debug(" " + getHexBytes(dataBytes, dataOffset, dataLen));
487 System.arraycopy(dataBytes, dataOffset, outToken, offset,
488 dataLen);
489 offset += dataLen;
490
491 // debug(" " + getHexBytes(padding, padding.length));
492 System.arraycopy(padding, 0, outToken, offset, padding.length);
493
494 } else {
495
496 cipherHelper.encryptData(this, confounder, dataBytes,
497 dataOffset, dataLen, padding, outToken, offset);
498
499 // debug(getHexBytes(outToken, offset, dataSize));
500 }
501
502 // debug("]\n");
503
504 // %%% assume that plaintext length == ciphertext len
505 return (header.length + confounder.length + dataLen + padding.length);
506
507 }
508
509 protected int getKrb5TokenSize() throws GSSException {
510 return (getTokenSize() + dataSize);
511 }
512
513 protected int getSealAlg(boolean conf, int qop) throws GSSException {
514 if (!conf) {
515 return SEAL_ALG_NONE;
516 }
517
518 // ignore QOP
519 return cipherHelper.getSealAlg();
520 }
521
522 // This implementation is way too conservative. And it certainly
523 // doesn't return the maximum limit.
524 static int getSizeLimit(int qop, boolean confReq, int maxTokenSize,
525 CipherHelper ch) throws GSSException {
526 return (GSSHeader.getMaxMechTokenSize(OID, maxTokenSize) -
527 (getTokenSize(ch) + CONFOUNDER_SIZE) - 8); /* safety */
528 }
529
530}