J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 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 | |
| 26 | package sun.security.jgss.krb5; |
| 27 | |
| 28 | import org.ietf.jgss.*; |
| 29 | import sun.security.jgss.*; |
| 30 | import java.security.GeneralSecurityException; |
| 31 | import java.io.InputStream; |
| 32 | import java.io.OutputStream; |
| 33 | import java.io.IOException; |
| 34 | import java.io.ByteArrayInputStream; |
| 35 | import java.io.ByteArrayOutputStream; |
| 36 | import sun.security.krb5.Confounder; |
| 37 | import 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 | */ |
| 52 | class 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 | } |