| /* |
| * Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.security.jgss.krb5; |
| |
| import org.ietf.jgss.*; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.IOException; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.security.MessageDigest; |
| import java.util.Arrays; |
| |
| /** |
| * This class is a base class for new GSS token definitions, as defined |
| * in RFC 4121, that pertain to per-message GSS-API calls. Conceptually |
| * GSS-API has two types of per-message tokens: WrapToken and MicToken. |
| * They differ in the respect that a WrapToken carries additional plaintext |
| * or ciphertext application data besides just the sequence number and |
| * checksum. This class encapsulates the commonality in the structure of |
| * the WrapToken and the MicToken. This structure can be represented as: |
| * <p> |
| * <pre> |
| * Wrap Tokens |
| * |
| * Octet no Name Description |
| * --------------------------------------------------------------- |
| * 0..1 TOK_ID Identification field. Tokens emitted by |
| * GSS_Wrap() contain the hex value 05 04 |
| * expressed in big-endian order in this field. |
| * 2 Flags Attributes field, as described in section |
| * 4.2.2. |
| * 3 Filler Contains the hex value FF. |
| * 4..5 EC Contains the "extra count" field, in big- |
| * endian order as described in section 4.2.3. |
| * 6..7 RRC Contains the "right rotation count" in big |
| * endian order, as described in section 4.2.5. |
| * 8..15 SND_SEQ Sequence number field in clear text, |
| * expressed in big-endian order. |
| * 16..last Data Encrypted data for Wrap tokens with |
| * confidentiality, or plaintext data followed |
| * by the checksum for Wrap tokens without |
| * confidentiality, as described in section |
| * 4.2.4. |
| * MIC Tokens |
| * |
| * Octet no Name Description |
| * ----------------------------------------------------------------- |
| * 0..1 TOK_ID Identification field. Tokens emitted by |
| * GSS_GetMIC() contain the hex value 04 04 |
| * expressed in big-endian order in this field. |
| * 2 Flags Attributes field, as described in section |
| * 4.2.2. |
| * 3..7 Filler Contains five octets of hex value FF. |
| * 8..15 SND_SEQ Sequence number field in clear text, |
| * expressed in big-endian order. |
| * 16..last SGN_CKSUM Checksum of the "to-be-signed" data and |
| * octet 0..15, as described in section 4.2.4. |
| * |
| * </pre> |
| * <p> |
| * This class is the super class of WrapToken_v2 and MicToken_v2. The token's |
| * header (bytes[0..15]) and data (byte[16..]) are saved in tokenHeader and |
| * tokenData fields. Since there is no easy way to find out the exact length |
| * of a WrapToken_v2 token from any header info, in the case of reading from |
| * stream, we read all available() bytes into the token. |
| * <p> |
| * All read actions are performed in this super class. On the write part, the |
| * super class only write the tokenHeader, and the content writing is inside |
| * child classes. |
| * |
| * @author Seema Malkani |
| */ |
| |
| abstract class MessageToken_v2 extends Krb5Token { |
| |
| protected static final int TOKEN_HEADER_SIZE = 16; |
| private static final int TOKEN_ID_POS = 0; |
| private static final int TOKEN_FLAG_POS = 2; |
| private static final int TOKEN_EC_POS = 4; |
| private static final int TOKEN_RRC_POS = 6; |
| |
| /** |
| * The size of the random confounder used in a WrapToken. |
| */ |
| protected static final int CONFOUNDER_SIZE = 16; |
| |
| // RFC 4121, key usage values |
| static final int KG_USAGE_ACCEPTOR_SEAL = 22; |
| static final int KG_USAGE_ACCEPTOR_SIGN = 23; |
| static final int KG_USAGE_INITIATOR_SEAL = 24; |
| static final int KG_USAGE_INITIATOR_SIGN = 25; |
| |
| // RFC 4121, Flags Field |
| private static final int FLAG_SENDER_IS_ACCEPTOR = 1; |
| private static final int FLAG_WRAP_CONFIDENTIAL = 2; |
| private static final int FLAG_ACCEPTOR_SUBKEY = 4; |
| private static final int FILLER = 0xff; |
| |
| private MessageTokenHeader tokenHeader = null; |
| |
| // Common field |
| private int tokenId = 0; |
| private int seqNumber; |
| protected byte[] tokenData; // content of token, without the header |
| protected int tokenDataLen; |
| |
| // Key usage number for crypto action |
| private int key_usage = 0; |
| |
| // EC and RRC fields, WrapToken only |
| private int ec = 0; |
| private int rrc = 0; |
| |
| // Checksum. Always in MicToken, might be in WrapToken |
| byte[] checksum = null; |
| |
| // Context properties |
| private boolean confState = true; |
| private boolean initiator = true; |
| private boolean have_acceptor_subkey = false; |
| |
| /* cipher instance used by the corresponding GSSContext */ |
| CipherHelper cipherHelper = null; |
| |
| /** |
| * Constructs a MessageToken from a byte array. |
| * |
| * @param tokenId the token id that should be contained in this token as |
| * it is read. |
| * @param context the Kerberos context associated with this token |
| * @param tokenBytes the byte array containing the token |
| * @param tokenOffset the offset where the token begins |
| * @param tokenLen the length of the token |
| * @param prop the MessageProp structure in which the properties of the |
| * token should be stored. |
| * @throws GSSException if there is a problem parsing the token |
| */ |
| MessageToken_v2(int tokenId, Krb5Context context, |
| byte[] tokenBytes, int tokenOffset, int tokenLen, |
| MessageProp prop) throws GSSException { |
| this(tokenId, context, |
| new ByteArrayInputStream(tokenBytes, tokenOffset, tokenLen), |
| prop); |
| } |
| |
| /** |
| * Constructs a MessageToken from an InputStream. Bytes will be read on |
| * demand and the thread might block if there are not enough bytes to |
| * complete the token. Please note there is no accurate way to find out |
| * the size of a token, but we try our best to make sure there is |
| * enough bytes to construct one. |
| * |
| * @param tokenId the token id that should be contained in this token as |
| * it is read. |
| * @param context the Kerberos context associated with this token |
| * @param is the InputStream from which to read |
| * @param prop the MessageProp structure in which the properties of the |
| * token should be stored. |
| * @throws GSSException if there is a problem reading from the |
| * InputStream or parsing the token |
| */ |
| MessageToken_v2(int tokenId, Krb5Context context, InputStream is, |
| MessageProp prop) throws GSSException { |
| init(tokenId, context); |
| |
| try { |
| if (!confState) { |
| prop.setPrivacy(false); |
| } |
| tokenHeader = new MessageTokenHeader(is, prop, tokenId); |
| |
| // set key_usage |
| if (tokenId == Krb5Token.WRAP_ID_v2) { |
| key_usage = (!initiator ? KG_USAGE_INITIATOR_SEAL |
| : KG_USAGE_ACCEPTOR_SEAL); |
| } else if (tokenId == Krb5Token.MIC_ID_v2) { |
| key_usage = (!initiator ? KG_USAGE_INITIATOR_SIGN |
| : KG_USAGE_ACCEPTOR_SIGN); |
| } |
| |
| int minSize = 0; // minimal size for token data |
| if (tokenId == Krb5Token.WRAP_ID_v2 && prop.getPrivacy()) { |
| minSize = CONFOUNDER_SIZE + |
| TOKEN_HEADER_SIZE + cipherHelper.getChecksumLength(); |
| } else { |
| minSize = cipherHelper.getChecksumLength(); |
| } |
| |
| // Read token data |
| if (tokenId == Krb5Token.MIC_ID_v2) { |
| // The only case we can precisely predict the token data length |
| tokenDataLen = minSize; |
| tokenData = new byte[minSize]; |
| readFully(is, tokenData); |
| } else { |
| tokenDataLen = is.available(); |
| if (tokenDataLen >= minSize) { // read in one shot |
| tokenData = new byte[tokenDataLen]; |
| readFully(is, tokenData); |
| } else { |
| byte[] tmp = new byte[minSize]; |
| readFully(is, tmp); |
| // Hope while blocked in the read above, more data would |
| // come and is.available() below contains the whole token. |
| int more = is.available(); |
| tokenDataLen = minSize + more; |
| tokenData = Arrays.copyOf(tmp, tokenDataLen); |
| readFully(is, tokenData, minSize, more); |
| } |
| } |
| |
| if (tokenId == Krb5Token.WRAP_ID_v2) { |
| rotate(); |
| } |
| |
| if (tokenId == Krb5Token.MIC_ID_v2 || |
| (tokenId == Krb5Token.WRAP_ID_v2 && !prop.getPrivacy())) { |
| // Read checksum |
| int chkLen = cipherHelper.getChecksumLength(); |
| checksum = new byte[chkLen]; |
| System.arraycopy(tokenData, tokenDataLen-chkLen, |
| checksum, 0, chkLen); |
| |
| // validate EC for Wrap tokens without confidentiality |
| if (tokenId == Krb5Token.WRAP_ID_v2 && !prop.getPrivacy()) { |
| if (chkLen != ec) { |
| throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, |
| getTokenName(tokenId) + ":" + "EC incorrect!"); |
| } |
| } |
| } |
| } catch (IOException e) { |
| throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, |
| getTokenName(tokenId) + ":" + e.getMessage()); |
| } |
| } |
| |
| /** |
| * Used to obtain the token id that was contained in this token. |
| * @return the token id in the token |
| */ |
| public final int getTokenId() { |
| return tokenId; |
| } |
| |
| /** |
| * Used to obtain the key_usage type for this token. |
| * @return the key_usage for the token |
| */ |
| public final int getKeyUsage() { |
| return key_usage; |
| } |
| |
| /** |
| * Used to determine if this token contains any encrypted data. |
| * @return true if it contains any encrypted data, false if there is only |
| * plaintext data or if there is no data. |
| */ |
| public final boolean getConfState() { |
| return confState; |
| } |
| |
| /** |
| * Generates the checksum field and the sequence number field. |
| * |
| * @param prop the MessageProp structure |
| * @param data the application data to checksum |
| * @param offset the offset where the data starts |
| * @param len the length of the data |
| * |
| * @throws GSSException if an error occurs in the checksum calculation or |
| * sequence number calculation. |
| */ |
| public void genSignAndSeqNumber(MessageProp prop, |
| byte[] data, int offset, int len) |
| throws GSSException { |
| |
| // debug("Inside MessageToken.genSignAndSeqNumber:\n"); |
| |
| int qop = prop.getQOP(); |
| if (qop != 0) { |
| qop = 0; |
| prop.setQOP(qop); |
| } |
| |
| if (!confState) { |
| prop.setPrivacy(false); |
| } |
| |
| // Create a new gss token header as defined in RFC 4121 |
| tokenHeader = new MessageTokenHeader(tokenId, prop.getPrivacy()); |
| // debug("\n\t Message Header = " + |
| // getHexBytes(tokenHeader.getBytes(), tokenHeader.getBytes().length)); |
| |
| // set key_usage |
| if (tokenId == Krb5Token.WRAP_ID_v2) { |
| key_usage = (initiator ? KG_USAGE_INITIATOR_SEAL |
| : KG_USAGE_ACCEPTOR_SEAL); |
| } else if (tokenId == Krb5Token.MIC_ID_v2) { |
| key_usage = (initiator ? KG_USAGE_INITIATOR_SIGN |
| : KG_USAGE_ACCEPTOR_SIGN); |
| } |
| |
| // Calculate SGN_CKSUM |
| if ((tokenId == MIC_ID_v2) || |
| (!prop.getPrivacy() && (tokenId == WRAP_ID_v2))) { |
| checksum = getChecksum(data, offset, len); |
| // debug("\n\tCalc checksum=" + |
| // getHexBytes(checksum, checksum.length)); |
| } |
| |
| // In Wrap tokens without confidentiality, the EC field SHALL be used |
| // to encode the number of octets in the trailing checksum |
| if (!prop.getPrivacy() && (tokenId == WRAP_ID_v2)) { |
| byte[] tok_header = tokenHeader.getBytes(); |
| tok_header[4] = (byte) (checksum.length >>> 8); |
| tok_header[5] = (byte) (checksum.length); |
| } |
| } |
| |
| /** |
| * Verifies the validity of checksum field |
| * |
| * @param data the application data |
| * @param offset the offset where the data begins |
| * @param len the length of the application data |
| * |
| * @throws GSSException if an error occurs in the checksum calculation |
| */ |
| public final boolean verifySign(byte[] data, int offset, int len) |
| throws GSSException { |
| |
| // debug("\t====In verifySign:====\n"); |
| // debug("\t\t checksum: [" + getHexBytes(checksum) + "]\n"); |
| // debug("\t\t data = [" + getHexBytes(data) + "]\n"); |
| |
| byte[] myChecksum = getChecksum(data, offset, len); |
| // debug("\t\t mychecksum: [" + getHexBytes(myChecksum) +"]\n"); |
| |
| if (MessageDigest.isEqual(checksum, myChecksum)) { |
| // debug("\t\t====Checksum PASS:====\n"); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Rotate bytes as per the "RRC" (Right Rotation Count) received. |
| * Our implementation does not do any rotates when sending, only |
| * when receiving, we rotate left as per the RRC count, to revert it. |
| */ |
| private void rotate() { |
| if (rrc % tokenDataLen != 0) { |
| rrc = rrc % tokenDataLen; |
| byte[] newBytes = new byte[tokenDataLen]; |
| |
| System.arraycopy(tokenData, rrc, newBytes, 0, tokenDataLen-rrc); |
| System.arraycopy(tokenData, 0, newBytes, tokenDataLen-rrc, rrc); |
| |
| tokenData = newBytes; |
| } |
| } |
| |
| public final int getSequenceNumber() { |
| return seqNumber; |
| } |
| |
| /** |
| * Computes the checksum based on the algorithm stored in the |
| * tokenHeader. |
| * |
| * @param data the application data |
| * @param offset the offset where the data begins |
| * @param len the length of the application data |
| * |
| * @throws GSSException if an error occurs in the checksum calculation. |
| */ |
| byte[] getChecksum(byte[] data, int offset, int len) |
| throws GSSException { |
| |
| // debug("Will do getChecksum:\n"); |
| |
| /* |
| * For checksum calculation the token header bytes i.e., the first 16 |
| * bytes following the GSSHeader, are logically prepended to the |
| * application data to bind the data to this particular token. |
| * |
| * Note: There is no such requirement wrt adding padding to the |
| * application data for checksumming, although the cryptographic |
| * algorithm used might itself apply some padding. |
| */ |
| |
| byte[] tokenHeaderBytes = tokenHeader.getBytes(); |
| |
| // check confidentiality |
| int conf_flag = tokenHeaderBytes[TOKEN_FLAG_POS] & |
| FLAG_WRAP_CONFIDENTIAL; |
| |
| // clear EC and RRC in token header for checksum calculation |
| if ((conf_flag == 0) && (tokenId == WRAP_ID_v2)) { |
| tokenHeaderBytes[4] = 0; |
| tokenHeaderBytes[5] = 0; |
| tokenHeaderBytes[6] = 0; |
| tokenHeaderBytes[7] = 0; |
| } |
| return cipherHelper.calculateChecksum(tokenHeaderBytes, data, |
| offset, len, key_usage); |
| } |
| |
| |
| /** |
| * Constructs an empty MessageToken for the local context to send to |
| * the peer. It also increments the local sequence number in the |
| * Krb5Context instance it uses after obtaining the object lock for |
| * it. |
| * |
| * @param tokenId the token id that should be contained in this token |
| * @param context the Kerberos context associated with this token |
| */ |
| MessageToken_v2(int tokenId, Krb5Context context) throws GSSException { |
| /* |
| debug("\n============================"); |
| debug("\nMySessionKey=" + |
| getHexBytes(context.getMySessionKey().getBytes())); |
| debug("\nPeerSessionKey=" + |
| getHexBytes(context.getPeerSessionKey().getBytes())); |
| debug("\n============================\n"); |
| */ |
| init(tokenId, context); |
| this.seqNumber = context.incrementMySequenceNumber(); |
| } |
| |
| private void init(int tokenId, Krb5Context context) throws GSSException { |
| this.tokenId = tokenId; |
| // Just for consistency check in Wrap |
| this.confState = context.getConfState(); |
| |
| this.initiator = context.isInitiator(); |
| |
| this.have_acceptor_subkey = context.getKeySrc() == Krb5Context.ACCEPTOR_SUBKEY; |
| |
| this.cipherHelper = context.getCipherHelper(null); |
| // debug("In MessageToken.Cons"); |
| } |
| |
| /** |
| * Encodes a MessageTokenHeader onto an OutputStream. |
| * |
| * @param os the OutputStream to which this should be written |
| * @throws IOException is an error occurs while writing to the OutputStream |
| */ |
| protected void encodeHeader(OutputStream os) throws IOException { |
| tokenHeader.encode(os); |
| } |
| |
| /** |
| * Encodes a MessageToken_v2 onto an OutputStream. |
| * |
| * @param os the OutputStream to which this should be written |
| * @throws IOException is an error occurs while encoding the token |
| */ |
| public abstract void encode(OutputStream os) throws IOException; |
| |
| protected final byte[] getTokenHeader() { |
| return (tokenHeader.getBytes()); |
| } |
| |
| // ******************************************* // |
| // I N N E R C L A S S E S F O L L O W |
| // ******************************************* // |
| |
| /** |
| * This inner class represents the initial portion of the message token. |
| * It constitutes the first 16 bytes of the message token. |
| */ |
| class MessageTokenHeader { |
| |
| private int tokenId; |
| private byte[] bytes = new byte[TOKEN_HEADER_SIZE]; |
| |
| // Writes a new token header |
| public MessageTokenHeader(int tokenId, boolean conf) throws GSSException { |
| |
| this.tokenId = tokenId; |
| |
| bytes[0] = (byte) (tokenId >>> 8); |
| bytes[1] = (byte) (tokenId); |
| |
| // Flags (Note: MIT impl requires subkey) |
| int flags = 0; |
| flags = (initiator ? 0 : FLAG_SENDER_IS_ACCEPTOR) | |
| ((conf && tokenId != MIC_ID_v2) ? |
| FLAG_WRAP_CONFIDENTIAL : 0) | |
| (have_acceptor_subkey ? FLAG_ACCEPTOR_SUBKEY : 0); |
| bytes[2] = (byte) flags; |
| |
| // filler |
| bytes[3] = (byte) FILLER; |
| |
| if (tokenId == WRAP_ID_v2) { |
| // EC field |
| bytes[4] = (byte) 0; |
| bytes[5] = (byte) 0; |
| // RRC field |
| bytes[6] = (byte) 0; |
| bytes[7] = (byte) 0; |
| } else if (tokenId == MIC_ID_v2) { |
| // more filler for MicToken |
| for (int i = 4; i < 8; i++) { |
| bytes[i] = (byte) FILLER; |
| } |
| } |
| |
| // Calculate SND_SEQ, only write 4 bytes from the 12th position |
| writeBigEndian(seqNumber, bytes, 12); |
| } |
| |
| /** |
| * Reads a MessageTokenHeader from an InputStream and sets the |
| * appropriate confidentiality and quality of protection |
| * values in a MessageProp structure. |
| * |
| * @param is the InputStream to read from |
| * @param prop the MessageProp to populate |
| * @throws IOException is an error occurs while reading from the |
| * InputStream |
| */ |
| public MessageTokenHeader(InputStream is, MessageProp prop, int tokId) |
| throws IOException, GSSException { |
| |
| readFully(is, bytes, 0, TOKEN_HEADER_SIZE); |
| tokenId = readInt(bytes, TOKEN_ID_POS); |
| |
| // validate Token ID |
| if (tokenId != tokId) { |
| throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, |
| getTokenName(tokenId) + ":" + "Defective Token ID!"); |
| } |
| |
| /* |
| * Validate new GSS TokenHeader |
| */ |
| |
| // valid acceptor_flag |
| // If I am initiator, the received token should have ACCEPTOR on |
| int acceptor_flag = (initiator ? FLAG_SENDER_IS_ACCEPTOR : 0); |
| int flag = bytes[TOKEN_FLAG_POS] & FLAG_SENDER_IS_ACCEPTOR; |
| if (flag != acceptor_flag) { |
| throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, |
| getTokenName(tokenId) + ":" + "Acceptor Flag Error!"); |
| } |
| |
| // check for confidentiality |
| int conf_flag = bytes[TOKEN_FLAG_POS] & FLAG_WRAP_CONFIDENTIAL; |
| if ((conf_flag == FLAG_WRAP_CONFIDENTIAL) && |
| (tokenId == WRAP_ID_v2)) { |
| prop.setPrivacy(true); |
| } else { |
| prop.setPrivacy(false); |
| } |
| |
| if (tokenId == WRAP_ID_v2) { |
| // validate filler |
| if ((bytes[3] & 0xff) != FILLER) { |
| throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, |
| getTokenName(tokenId) + ":" + "Defective Token Filler!"); |
| } |
| |
| // read EC field |
| ec = readBigEndian(bytes, TOKEN_EC_POS, 2); |
| |
| // read RRC field |
| rrc = readBigEndian(bytes, TOKEN_RRC_POS, 2); |
| } else if (tokenId == MIC_ID_v2) { |
| for (int i = 3; i < 8; i++) { |
| if ((bytes[i] & 0xff) != FILLER) { |
| throw new GSSException(GSSException.DEFECTIVE_TOKEN, |
| -1, getTokenName(tokenId) + ":" + |
| "Defective Token Filler!"); |
| } |
| } |
| } |
| |
| // set default QOP |
| prop.setQOP(0); |
| |
| // sequence number |
| seqNumber = readBigEndian(bytes, 0, 8); |
| } |
| |
| /** |
| * Encodes this MessageTokenHeader onto an OutputStream |
| * @param os the OutputStream to write to |
| * @throws IOException is an error occurs while writing |
| */ |
| public final void encode(OutputStream os) throws IOException { |
| os.write(bytes); |
| } |
| |
| |
| /** |
| * Returns the token id for the message token. |
| * @return the token id |
| * @see sun.security.jgss.krb5.Krb5Token#MIC_ID_v2 |
| * @see sun.security.jgss.krb5.Krb5Token#WRAP_ID_v2 |
| */ |
| public final int getTokenId() { |
| return tokenId; |
| } |
| |
| /** |
| * Returns the bytes of this header. |
| * @return 8 bytes that form this header |
| */ |
| public final byte[] getBytes() { |
| return bytes; |
| } |
| } // end of class MessageTokenHeader |
| } |