1 /* 2 * Copyright (c) 2004, 2018, 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 26 package sun.security.jgss.krb5; 27 28 import org.ietf.jgss.*; 29 import java.io.InputStream; 30 import java.io.OutputStream; 31 import java.io.IOException; 32 import java.io.ByteArrayInputStream; 33 import java.io.ByteArrayOutputStream; 34 import java.security.MessageDigest; 35 import java.util.Arrays; 36 37 /** 38 * This class is a base class for new GSS token definitions, as defined 39 * in RFC 4121, that pertain to per-message GSS-API calls. Conceptually 40 * GSS-API has two types of per-message tokens: WrapToken and MicToken. 41 * They differ in the respect that a WrapToken carries additional plaintext 42 * or ciphertext application data besides just the sequence number and 43 * checksum. This class encapsulates the commonality in the structure of 44 * the WrapToken and the MicToken. This structure can be represented as: 45 * <p> 46 * <pre> 47 * Wrap Tokens 48 * 49 * Octet no Name Description 50 * --------------------------------------------------------------- 51 * 0..1 TOK_ID Identification field. Tokens emitted by 52 * GSS_Wrap() contain the hex value 05 04 53 * expressed in big-endian order in this field. 54 * 2 Flags Attributes field, as described in section 55 * 4.2.2. 56 * 3 Filler Contains the hex value FF. 57 * 4..5 EC Contains the "extra count" field, in big- 58 * endian order as described in section 4.2.3. 59 * 6..7 RRC Contains the "right rotation count" in big 60 * endian order, as described in section 4.2.5. 61 * 8..15 SND_SEQ Sequence number field in clear text, 62 * expressed in big-endian order. 63 * 16..last Data Encrypted data for Wrap tokens with 64 * confidentiality, or plaintext data followed 65 * by the checksum for Wrap tokens without 66 * confidentiality, as described in section 67 * 4.2.4. 68 * MIC Tokens 69 * 70 * Octet no Name Description 71 * ----------------------------------------------------------------- 72 * 0..1 TOK_ID Identification field. Tokens emitted by 73 * GSS_GetMIC() contain the hex value 04 04 74 * expressed in big-endian order in this field. 75 * 2 Flags Attributes field, as described in section 76 * 4.2.2. 77 * 3..7 Filler Contains five octets of hex value FF. 78 * 8..15 SND_SEQ Sequence number field in clear text, 79 * expressed in big-endian order. 80 * 16..last SGN_CKSUM Checksum of the "to-be-signed" data and 81 * octet 0..15, as described in section 4.2.4. 82 * 83 * </pre> 84 * <p> 85 * This class is the super class of WrapToken_v2 and MicToken_v2. The token's 86 * header (bytes[0..15]) and data (byte[16..]) are saved in tokenHeader and 87 * tokenData fields. Since there is no easy way to find out the exact length 88 * of a WrapToken_v2 token from any header info, in the case of reading from 89 * stream, we read all available() bytes into the token. 90 * <p> 91 * All read actions are performed in this super class. On the write part, the 92 * super class only write the tokenHeader, and the content writing is inside 93 * child classes. 94 * 95 * @author Seema Malkani 96 */ 97 98 abstract class MessageToken_v2 extends Krb5Token { 99 100 protected static final int TOKEN_HEADER_SIZE = 16; 101 private static final int TOKEN_ID_POS = 0; 102 private static final int TOKEN_FLAG_POS = 2; 103 private static final int TOKEN_EC_POS = 4; 104 private static final int TOKEN_RRC_POS = 6; 105 106 /** 107 * The size of the random confounder used in a WrapToken. 108 */ 109 protected static final int CONFOUNDER_SIZE = 16; 110 111 // RFC 4121, key usage values 112 static final int KG_USAGE_ACCEPTOR_SEAL = 22; 113 static final int KG_USAGE_ACCEPTOR_SIGN = 23; 114 static final int KG_USAGE_INITIATOR_SEAL = 24; 115 static final int KG_USAGE_INITIATOR_SIGN = 25; 116 117 // RFC 4121, Flags Field 118 private static final int FLAG_SENDER_IS_ACCEPTOR = 1; 119 private static final int FLAG_WRAP_CONFIDENTIAL = 2; 120 private static final int FLAG_ACCEPTOR_SUBKEY = 4; 121 private static final int FILLER = 0xff; 122 123 private MessageTokenHeader tokenHeader = null; 124 125 // Common field 126 private int tokenId = 0; 127 private int seqNumber; 128 protected byte[] tokenData; // content of token, without the header 129 protected int tokenDataLen; 130 131 // Key usage number for crypto action 132 private int key_usage = 0; 133 134 // EC and RRC fields, WrapToken only 135 private int ec = 0; 136 private int rrc = 0; 137 138 // Checksum. Always in MicToken, might be in WrapToken 139 byte[] checksum = null; 140 141 // Context properties 142 private boolean confState = true; 143 private boolean initiator = true; 144 private boolean have_acceptor_subkey = false; 145 146 /* cipher instance used by the corresponding GSSContext */ 147 CipherHelper cipherHelper = null; 148 149 /** 150 * Constructs a MessageToken from a byte array. 151 * 152 * @param tokenId the token id that should be contained in this token as 153 * it is read. 154 * @param context the Kerberos context associated with this token 155 * @param tokenBytes the byte array containing the token 156 * @param tokenOffset the offset where the token begins 157 * @param tokenLen the length of the token 158 * @param prop the MessageProp structure in which the properties of the 159 * token should be stored. 160 * @throws GSSException if there is a problem parsing the token 161 */ MessageToken_v2(int tokenId, Krb5Context context, byte[] tokenBytes, int tokenOffset, int tokenLen, MessageProp prop)162 MessageToken_v2(int tokenId, Krb5Context context, 163 byte[] tokenBytes, int tokenOffset, int tokenLen, 164 MessageProp prop) throws GSSException { 165 this(tokenId, context, 166 new ByteArrayInputStream(tokenBytes, tokenOffset, tokenLen), 167 prop); 168 } 169 170 /** 171 * Constructs a MessageToken from an InputStream. Bytes will be read on 172 * demand and the thread might block if there are not enough bytes to 173 * complete the token. Please note there is no accurate way to find out 174 * the size of a token, but we try our best to make sure there is 175 * enough bytes to construct one. 176 * 177 * @param tokenId the token id that should be contained in this token as 178 * it is read. 179 * @param context the Kerberos context associated with this token 180 * @param is the InputStream from which to read 181 * @param prop the MessageProp structure in which the properties of the 182 * token should be stored. 183 * @throws GSSException if there is a problem reading from the 184 * InputStream or parsing the token 185 */ MessageToken_v2(int tokenId, Krb5Context context, InputStream is, MessageProp prop)186 MessageToken_v2(int tokenId, Krb5Context context, InputStream is, 187 MessageProp prop) throws GSSException { 188 init(tokenId, context); 189 190 try { 191 if (!confState) { 192 prop.setPrivacy(false); 193 } 194 tokenHeader = new MessageTokenHeader(is, prop, tokenId); 195 196 // set key_usage 197 if (tokenId == Krb5Token.WRAP_ID_v2) { 198 key_usage = (!initiator ? KG_USAGE_INITIATOR_SEAL 199 : KG_USAGE_ACCEPTOR_SEAL); 200 } else if (tokenId == Krb5Token.MIC_ID_v2) { 201 key_usage = (!initiator ? KG_USAGE_INITIATOR_SIGN 202 : KG_USAGE_ACCEPTOR_SIGN); 203 } 204 205 int minSize = 0; // minimal size for token data 206 if (tokenId == Krb5Token.WRAP_ID_v2 && prop.getPrivacy()) { 207 minSize = CONFOUNDER_SIZE + 208 TOKEN_HEADER_SIZE + cipherHelper.getChecksumLength(); 209 } else { 210 minSize = cipherHelper.getChecksumLength(); 211 } 212 213 // Read token data 214 if (tokenId == Krb5Token.MIC_ID_v2) { 215 // The only case we can precisely predict the token data length 216 tokenDataLen = minSize; 217 tokenData = new byte[minSize]; 218 readFully(is, tokenData); 219 } else { 220 tokenDataLen = is.available(); 221 if (tokenDataLen >= minSize) { // read in one shot 222 tokenData = new byte[tokenDataLen]; 223 readFully(is, tokenData); 224 } else { 225 byte[] tmp = new byte[minSize]; 226 readFully(is, tmp); 227 // Hope while blocked in the read above, more data would 228 // come and is.available() below contains the whole token. 229 int more = is.available(); 230 tokenDataLen = minSize + more; 231 tokenData = Arrays.copyOf(tmp, tokenDataLen); 232 readFully(is, tokenData, minSize, more); 233 } 234 } 235 236 if (tokenId == Krb5Token.WRAP_ID_v2) { 237 rotate(); 238 } 239 240 if (tokenId == Krb5Token.MIC_ID_v2 || 241 (tokenId == Krb5Token.WRAP_ID_v2 && !prop.getPrivacy())) { 242 // Read checksum 243 int chkLen = cipherHelper.getChecksumLength(); 244 checksum = new byte[chkLen]; 245 System.arraycopy(tokenData, tokenDataLen-chkLen, 246 checksum, 0, chkLen); 247 248 // validate EC for Wrap tokens without confidentiality 249 if (tokenId == Krb5Token.WRAP_ID_v2 && !prop.getPrivacy()) { 250 if (chkLen != ec) { 251 throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, 252 getTokenName(tokenId) + ":" + "EC incorrect!"); 253 } 254 } 255 } 256 } catch (IOException e) { 257 throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, 258 getTokenName(tokenId) + ":" + e.getMessage()); 259 } 260 } 261 262 /** 263 * Used to obtain the token id that was contained in this token. 264 * @return the token id in the token 265 */ getTokenId()266 public final int getTokenId() { 267 return tokenId; 268 } 269 270 /** 271 * Used to obtain the key_usage type for this token. 272 * @return the key_usage for the token 273 */ getKeyUsage()274 public final int getKeyUsage() { 275 return key_usage; 276 } 277 278 /** 279 * Used to determine if this token contains any encrypted data. 280 * @return true if it contains any encrypted data, false if there is only 281 * plaintext data or if there is no data. 282 */ getConfState()283 public final boolean getConfState() { 284 return confState; 285 } 286 287 /** 288 * Generates the checksum field and the sequence number field. 289 * 290 * @param prop the MessageProp structure 291 * @param data the application data to checksum 292 * @param offset the offset where the data starts 293 * @param len the length of the data 294 * 295 * @throws GSSException if an error occurs in the checksum calculation or 296 * sequence number calculation. 297 */ genSignAndSeqNumber(MessageProp prop, byte[] data, int offset, int len)298 public void genSignAndSeqNumber(MessageProp prop, 299 byte[] data, int offset, int len) 300 throws GSSException { 301 302 // debug("Inside MessageToken.genSignAndSeqNumber:\n"); 303 304 int qop = prop.getQOP(); 305 if (qop != 0) { 306 qop = 0; 307 prop.setQOP(qop); 308 } 309 310 if (!confState) { 311 prop.setPrivacy(false); 312 } 313 314 // Create a new gss token header as defined in RFC 4121 315 tokenHeader = new MessageTokenHeader(tokenId, prop.getPrivacy()); 316 // debug("\n\t Message Header = " + 317 // getHexBytes(tokenHeader.getBytes(), tokenHeader.getBytes().length)); 318 319 // set key_usage 320 if (tokenId == Krb5Token.WRAP_ID_v2) { 321 key_usage = (initiator ? KG_USAGE_INITIATOR_SEAL 322 : KG_USAGE_ACCEPTOR_SEAL); 323 } else if (tokenId == Krb5Token.MIC_ID_v2) { 324 key_usage = (initiator ? KG_USAGE_INITIATOR_SIGN 325 : KG_USAGE_ACCEPTOR_SIGN); 326 } 327 328 // Calculate SGN_CKSUM 329 if ((tokenId == MIC_ID_v2) || 330 (!prop.getPrivacy() && (tokenId == WRAP_ID_v2))) { 331 checksum = getChecksum(data, offset, len); 332 // debug("\n\tCalc checksum=" + 333 // getHexBytes(checksum, checksum.length)); 334 } 335 336 // In Wrap tokens without confidentiality, the EC field SHALL be used 337 // to encode the number of octets in the trailing checksum 338 if (!prop.getPrivacy() && (tokenId == WRAP_ID_v2)) { 339 byte[] tok_header = tokenHeader.getBytes(); 340 tok_header[4] = (byte) (checksum.length >>> 8); 341 tok_header[5] = (byte) (checksum.length); 342 } 343 } 344 345 /** 346 * Verifies the validity of checksum field 347 * 348 * @param data the application data 349 * @param offset the offset where the data begins 350 * @param len the length of the application data 351 * 352 * @throws GSSException if an error occurs in the checksum calculation 353 */ verifySign(byte[] data, int offset, int len)354 public final boolean verifySign(byte[] data, int offset, int len) 355 throws GSSException { 356 357 // debug("\t====In verifySign:====\n"); 358 // debug("\t\t checksum: [" + getHexBytes(checksum) + "]\n"); 359 // debug("\t\t data = [" + getHexBytes(data) + "]\n"); 360 361 byte[] myChecksum = getChecksum(data, offset, len); 362 // debug("\t\t mychecksum: [" + getHexBytes(myChecksum) +"]\n"); 363 364 if (MessageDigest.isEqual(checksum, myChecksum)) { 365 // debug("\t\t====Checksum PASS:====\n"); 366 return true; 367 } 368 return false; 369 } 370 371 /** 372 * Rotate bytes as per the "RRC" (Right Rotation Count) received. 373 * Our implementation does not do any rotates when sending, only 374 * when receiving, we rotate left as per the RRC count, to revert it. 375 */ rotate()376 private void rotate() { 377 if (rrc % tokenDataLen != 0) { 378 rrc = rrc % tokenDataLen; 379 byte[] newBytes = new byte[tokenDataLen]; 380 381 System.arraycopy(tokenData, rrc, newBytes, 0, tokenDataLen-rrc); 382 System.arraycopy(tokenData, 0, newBytes, tokenDataLen-rrc, rrc); 383 384 tokenData = newBytes; 385 } 386 } 387 getSequenceNumber()388 public final int getSequenceNumber() { 389 return seqNumber; 390 } 391 392 /** 393 * Computes the checksum based on the algorithm stored in the 394 * tokenHeader. 395 * 396 * @param data the application data 397 * @param offset the offset where the data begins 398 * @param len the length of the application data 399 * 400 * @throws GSSException if an error occurs in the checksum calculation. 401 */ getChecksum(byte[] data, int offset, int len)402 byte[] getChecksum(byte[] data, int offset, int len) 403 throws GSSException { 404 405 // debug("Will do getChecksum:\n"); 406 407 /* 408 * For checksum calculation the token header bytes i.e., the first 16 409 * bytes following the GSSHeader, are logically prepended to the 410 * application data to bind the data to this particular token. 411 * 412 * Note: There is no such requirement wrt adding padding to the 413 * application data for checksumming, although the cryptographic 414 * algorithm used might itself apply some padding. 415 */ 416 417 byte[] tokenHeaderBytes = tokenHeader.getBytes(); 418 419 // check confidentiality 420 int conf_flag = tokenHeaderBytes[TOKEN_FLAG_POS] & 421 FLAG_WRAP_CONFIDENTIAL; 422 423 // clear EC and RRC in token header for checksum calculation 424 if ((conf_flag == 0) && (tokenId == WRAP_ID_v2)) { 425 tokenHeaderBytes[4] = 0; 426 tokenHeaderBytes[5] = 0; 427 tokenHeaderBytes[6] = 0; 428 tokenHeaderBytes[7] = 0; 429 } 430 return cipherHelper.calculateChecksum(tokenHeaderBytes, data, 431 offset, len, key_usage); 432 } 433 434 435 /** 436 * Constructs an empty MessageToken for the local context to send to 437 * the peer. It also increments the local sequence number in the 438 * Krb5Context instance it uses after obtaining the object lock for 439 * it. 440 * 441 * @param tokenId the token id that should be contained in this token 442 * @param context the Kerberos context associated with this token 443 */ MessageToken_v2(int tokenId, Krb5Context context)444 MessageToken_v2(int tokenId, Krb5Context context) throws GSSException { 445 /* 446 debug("\n============================"); 447 debug("\nMySessionKey=" + 448 getHexBytes(context.getMySessionKey().getBytes())); 449 debug("\nPeerSessionKey=" + 450 getHexBytes(context.getPeerSessionKey().getBytes())); 451 debug("\n============================\n"); 452 */ 453 init(tokenId, context); 454 this.seqNumber = context.incrementMySequenceNumber(); 455 } 456 init(int tokenId, Krb5Context context)457 private void init(int tokenId, Krb5Context context) throws GSSException { 458 this.tokenId = tokenId; 459 // Just for consistency check in Wrap 460 this.confState = context.getConfState(); 461 462 this.initiator = context.isInitiator(); 463 464 this.have_acceptor_subkey = context.getKeySrc() == Krb5Context.ACCEPTOR_SUBKEY; 465 466 this.cipherHelper = context.getCipherHelper(null); 467 // debug("In MessageToken.Cons"); 468 } 469 470 /** 471 * Encodes a MessageTokenHeader onto an OutputStream. 472 * 473 * @param os the OutputStream to which this should be written 474 * @throws IOException is an error occurs while writing to the OutputStream 475 */ encodeHeader(OutputStream os)476 protected void encodeHeader(OutputStream os) throws IOException { 477 tokenHeader.encode(os); 478 } 479 480 /** 481 * Encodes a MessageToken_v2 onto an OutputStream. 482 * 483 * @param os the OutputStream to which this should be written 484 * @throws IOException is an error occurs while encoding the token 485 */ encode(OutputStream os)486 public abstract void encode(OutputStream os) throws IOException; 487 getTokenHeader()488 protected final byte[] getTokenHeader() { 489 return (tokenHeader.getBytes()); 490 } 491 492 // ******************************************* // 493 // I N N E R C L A S S E S F O L L O W 494 // ******************************************* // 495 496 /** 497 * This inner class represents the initial portion of the message token. 498 * It constitutes the first 16 bytes of the message token. 499 */ 500 class MessageTokenHeader { 501 502 private int tokenId; 503 private byte[] bytes = new byte[TOKEN_HEADER_SIZE]; 504 505 // Writes a new token header MessageTokenHeader(int tokenId, boolean conf)506 public MessageTokenHeader(int tokenId, boolean conf) throws GSSException { 507 508 this.tokenId = tokenId; 509 510 bytes[0] = (byte) (tokenId >>> 8); 511 bytes[1] = (byte) (tokenId); 512 513 // Flags (Note: MIT impl requires subkey) 514 int flags = 0; 515 flags = (initiator ? 0 : FLAG_SENDER_IS_ACCEPTOR) | 516 ((conf && tokenId != MIC_ID_v2) ? 517 FLAG_WRAP_CONFIDENTIAL : 0) | 518 (have_acceptor_subkey ? FLAG_ACCEPTOR_SUBKEY : 0); 519 bytes[2] = (byte) flags; 520 521 // filler 522 bytes[3] = (byte) FILLER; 523 524 if (tokenId == WRAP_ID_v2) { 525 // EC field 526 bytes[4] = (byte) 0; 527 bytes[5] = (byte) 0; 528 // RRC field 529 bytes[6] = (byte) 0; 530 bytes[7] = (byte) 0; 531 } else if (tokenId == MIC_ID_v2) { 532 // more filler for MicToken 533 for (int i = 4; i < 8; i++) { 534 bytes[i] = (byte) FILLER; 535 } 536 } 537 538 // Calculate SND_SEQ, only write 4 bytes from the 12th position 539 writeBigEndian(seqNumber, bytes, 12); 540 } 541 542 /** 543 * Reads a MessageTokenHeader from an InputStream and sets the 544 * appropriate confidentiality and quality of protection 545 * values in a MessageProp structure. 546 * 547 * @param is the InputStream to read from 548 * @param prop the MessageProp to populate 549 * @throws IOException is an error occurs while reading from the 550 * InputStream 551 */ MessageTokenHeader(InputStream is, MessageProp prop, int tokId)552 public MessageTokenHeader(InputStream is, MessageProp prop, int tokId) 553 throws IOException, GSSException { 554 555 readFully(is, bytes, 0, TOKEN_HEADER_SIZE); 556 tokenId = readInt(bytes, TOKEN_ID_POS); 557 558 // validate Token ID 559 if (tokenId != tokId) { 560 throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, 561 getTokenName(tokenId) + ":" + "Defective Token ID!"); 562 } 563 564 /* 565 * Validate new GSS TokenHeader 566 */ 567 568 // valid acceptor_flag 569 // If I am initiator, the received token should have ACCEPTOR on 570 int acceptor_flag = (initiator ? FLAG_SENDER_IS_ACCEPTOR : 0); 571 int flag = bytes[TOKEN_FLAG_POS] & FLAG_SENDER_IS_ACCEPTOR; 572 if (flag != acceptor_flag) { 573 throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, 574 getTokenName(tokenId) + ":" + "Acceptor Flag Error!"); 575 } 576 577 // check for confidentiality 578 int conf_flag = bytes[TOKEN_FLAG_POS] & FLAG_WRAP_CONFIDENTIAL; 579 if ((conf_flag == FLAG_WRAP_CONFIDENTIAL) && 580 (tokenId == WRAP_ID_v2)) { 581 prop.setPrivacy(true); 582 } else { 583 prop.setPrivacy(false); 584 } 585 586 if (tokenId == WRAP_ID_v2) { 587 // validate filler 588 if ((bytes[3] & 0xff) != FILLER) { 589 throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, 590 getTokenName(tokenId) + ":" + "Defective Token Filler!"); 591 } 592 593 // read EC field 594 ec = readBigEndian(bytes, TOKEN_EC_POS, 2); 595 596 // read RRC field 597 rrc = readBigEndian(bytes, TOKEN_RRC_POS, 2); 598 } else if (tokenId == MIC_ID_v2) { 599 for (int i = 3; i < 8; i++) { 600 if ((bytes[i] & 0xff) != FILLER) { 601 throw new GSSException(GSSException.DEFECTIVE_TOKEN, 602 -1, getTokenName(tokenId) + ":" + 603 "Defective Token Filler!"); 604 } 605 } 606 } 607 608 // set default QOP 609 prop.setQOP(0); 610 611 // sequence number 612 seqNumber = readBigEndian(bytes, 12, 4); 613 } 614 615 /** 616 * Encodes this MessageTokenHeader onto an OutputStream 617 * @param os the OutputStream to write to 618 * @throws IOException is an error occurs while writing 619 */ encode(OutputStream os)620 public final void encode(OutputStream os) throws IOException { 621 os.write(bytes); 622 } 623 624 625 /** 626 * Returns the token id for the message token. 627 * @return the token id 628 * @see sun.security.jgss.krb5.Krb5Token#MIC_ID_v2 629 * @see sun.security.jgss.krb5.Krb5Token#WRAP_ID_v2 630 */ getTokenId()631 public final int getTokenId() { 632 return tokenId; 633 } 634 635 /** 636 * Returns the bytes of this header. 637 * @return 8 bytes that form this header 638 */ getBytes()639 public final byte[] getBytes() { 640 return bytes; 641 } 642 } // end of class MessageTokenHeader 643 } 644