/* * Copyright (c) 2000, 2006, 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 sun.security.jgss.*; import sun.security.krb5.*; import java.io.InputStream; import java.io.OutputStream; import java.io.IOException; import java.io.ByteArrayInputStream; import java.security.GeneralSecurityException; import java.security.MessageDigest; /** * This class is a base class for other token definitions 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> * 0..1 TOK_ID Identification field. * 01 01 - Mic token * 02 01 - Wrap token * 2..3 SGN_ALG Checksum algorithm indicator. * 00 00 - DES MAC MD5 * 01 00 - MD2.5 * 02 00 - DES MAC * 04 00 - HMAC SHA1 DES3-KD * 11 00 - RC4-HMAC * 4..5 SEAL_ALG ff ff - none * 00 00 - DES * 02 00 - DES3-KD * 10 00 - RC4-HMAC * 6..7 Filler Contains ff ff * 8..15 SND_SEQ Encrypted sequence number field. * 16..s+15 SGN_CKSUM Checksum of plaintext padded data, * calculated according to algorithm * specified in SGN_ALG field. * s+16..last Data encrypted or plaintext padded data * </pre> * Where "s" indicates the size of the checksum. * <p> * As always, this is preceeded by a GSSHeader. * * @author Mayank Upadhyay * @author Ram Marti * @see sun.security.jgss.GSSHeader */ abstract class MessageToken extends Krb5Token { /* Fields in header minus checksum size */ private static final int TOKEN_NO_CKSUM_SIZE = 16; /** * Filler data as defined in the specification of the Kerberos v5 GSS-API * Mechanism. */ private static final int FILLER = 0xffff; // Signing algorithm values (for the SNG_ALG field) // From RFC 1964 /* Use a DES MAC MD5 checksum */ static final int SGN_ALG_DES_MAC_MD5 = 0x0000; /* Use DES MAC checksum. */ static final int SGN_ALG_DES_MAC = 0x0200; // From draft-raeburn-cat-gssapi-krb5-3des-00 /* Use a HMAC SHA1 DES3 -KD checksum */ static final int SGN_ALG_HMAC_SHA1_DES3_KD = 0x0400; // Sealing algorithm values (for the SEAL_ALG field) // RFC 1964 /** * A value for the SEAL_ALG field that indicates that no encryption was * used. */ static final int SEAL_ALG_NONE = 0xffff; /* Use DES CBC encryption algorithm. */ static final int SEAL_ALG_DES = 0x0000; // From draft-raeburn-cat-gssapi-krb5-3des-00 /** * Use DES3-KD sealing algorithm. (draft-raeburn-cat-gssapi-krb5-3des-00) * This algorithm uses triple-DES with key derivation, with a usage * value KG_USAGE_SEAL. Padding is still to 8-byte multiples, and the * IV for encrypting application data is zero. */ static final int SEAL_ALG_DES3_KD = 0x0200; // draft draft-brezak-win2k-krb-rc4-hmac-04.txt static final int SEAL_ALG_ARCFOUR_HMAC = 0x1000; static final int SGN_ALG_HMAC_MD5_ARCFOUR = 0x1100; private static final int TOKEN_ID_POS = 0; private static final int SIGN_ALG_POS = 2; private static final int SEAL_ALG_POS = 4; private int seqNumber; private boolean confState = true; private boolean initiator = true; private int tokenId = 0; private GSSHeader gssHeader = null; private MessageTokenHeader tokenHeader = null; private byte[] checksum = null; private byte[] encSeqNumber = null; private byte[] seqNumberData = null; /* cipher instance used by the corresponding GSSContext */ CipherHelper cipherHelper = null; /** * Constructs a MessageToken from a byte array. If there are more bytes * in the array than needed, the extra bytes are simply ignroed. * * @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(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. * * @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(int tokenId, Krb5Context context, InputStream is, MessageProp prop) throws GSSException { init(tokenId, context); try { gssHeader = new GSSHeader(is); if (!gssHeader.getOid().equals(OID)) { throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, getTokenName(tokenId)); } if (!confState) { prop.setPrivacy(false); } tokenHeader = new MessageTokenHeader(is, prop); encSeqNumber = new byte[8]; readFully(is, encSeqNumber); // debug("\n\tRead EncSeq#=" + // getHexBytes(encSeqNumber, encSeqNumber.length)); checksum = new byte[cipherHelper.getChecksumLength()]; readFully(is, checksum); // debug("\n\tRead checksum=" + // getHexBytes(checksum, checksum.length)); // debug("\nLeaving MessageToken.Cons\n"); } catch (IOException e) { throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, getTokenName(tokenId) + ":" + e.getMessage()); } } /** * Used to obtain the GSSHeader that was at the start of this * token. */ public final GSSHeader getGSSHeader() { return gssHeader; } /** * 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 encrypted sequence number in this token. * @return the encrypted sequence number in the token */ public final byte[] getEncSeqNumber() { return encSeqNumber; } /** * Used to obtain the checksum that was contained in this token. * @return the checksum in the token */ public final byte[] getChecksum() { return checksum; } /** * 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 encrypted sequence number * field. The encrypted sequence number uses the 8 bytes of the checksum * as an initial vector in a fixed DesCbc algorithm. * * @param prop the MessageProp structure that determines what sort of * checksum and sealing algorithm should be used. The lower byte * of qop determines the checksum algorithm while the upper byte * determines the signing algorithm. * Checksum values are: * 0 - default (DES_MAC) * 1 - MD5 * 2 - DES_MD5 * 3 - DES_MAC * 4 - HMAC_SHA1 * Sealing values are: * 0 - default (DES) * 1 - DES * 2 - DES3-KD * * @param optionalHeader an optional header that will be processed first * during checksum calculation * * @param data the application data to checksum * @param offset the offset where the data starts * @param len the length of the data * * @param optionalTrailer an optional trailer that will be processed * last during checksum calculation. e.g., padding that should be * appended to the application data * * @throws GSSException if an error occurs in the checksum calculation or * encryption sequence number calculation. */ public void genSignAndSeqNumber(MessageProp prop, byte[] optionalHeader, byte[] data, int offset, int len, byte[] optionalTrailer) 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 token header with the correct sign and seal algorithm // values. tokenHeader = new MessageTokenHeader(tokenId, prop.getPrivacy(), qop); // Calculate SGN_CKSUM checksum = getChecksum(optionalHeader, data, offset, len, optionalTrailer); // debug("\n\tCalc checksum=" + // getHexBytes(checksum, checksum.length)); // Calculate SND_SEQ seqNumberData = new byte[8]; // When using this RC4 based encryption type, the sequence number is // always sent in big-endian rather than little-endian order. if (cipherHelper.isArcFour()) { writeBigEndian(seqNumber, seqNumberData); } else { // for all other etypes writeLittleEndian(seqNumber, seqNumberData); } if (!initiator) { seqNumberData[4] = (byte)0xff; seqNumberData[5] = (byte)0xff; seqNumberData[6] = (byte)0xff; seqNumberData[7] = (byte)0xff; } encSeqNumber = cipherHelper.encryptSeq(checksum, seqNumberData, 0, 8); // debug("\n\tCalc seqNum=" + // getHexBytes(seqNumberData, seqNumberData.length)); // debug("\n\tCalc encSeqNum=" + // getHexBytes(encSeqNumber, encSeqNumber.length)); } /** * Verifies that the checksum field and sequence number direction bytes * are valid and consistent with the application data. * * @param optionalHeader an optional header that will be processed first * during checksum calculation. * * @param data the application data * @param offset the offset where the data begins * @param len the length of the application data * * @param optionalTrailer an optional trailer that will be processed last * during checksum calculation. e.g., padding that should be appended to * the application data * * @throws GSSException if an error occurs in the checksum calculation or * encryption sequence number calculation. */ public final boolean verifySignAndSeqNumber(byte[] optionalHeader, byte[] data, int offset, int len, byte[] optionalTrailer) throws GSSException { // debug("\tIn verifySign:\n"); // debug("\t\tchecksum: [" + getHexBytes(checksum) + "]\n"); byte[] myChecksum = getChecksum(optionalHeader, data, offset, len, optionalTrailer); // debug("\t\tmychecksum: [" + getHexBytes(myChecksum) +"]\n"); // debug("\t\tchecksum: [" + getHexBytes(checksum) + "]\n"); if (MessageDigest.isEqual(checksum, myChecksum)) { seqNumberData = cipherHelper.decryptSeq( checksum, encSeqNumber, 0, 8); // debug("\t\tencSeqNumber: [" + getHexBytes(encSeqNumber) // + "]\n"); // debug("\t\tseqNumberData: [" + getHexBytes(seqNumberData) // + "]\n"); /* * The token from the initiator has direction bytes 0x00 and * the token from the acceptor has direction bytes 0xff. */ byte directionByte = 0; if (initiator) directionByte = (byte) 0xff; // Received token from acceptor if ((seqNumberData[4] == directionByte) && (seqNumberData[5] == directionByte) && (seqNumberData[6] == directionByte) && (seqNumberData[7] == directionByte)) return true; } return false; } public final int getSequenceNumber() { int sequenceNum = 0; if (cipherHelper.isArcFour()) { sequenceNum = readBigEndian(seqNumberData, 0, 4); } else { sequenceNum = readLittleEndian(seqNumberData, 0, 4); } return sequenceNum; } /** * Computes the checksum based on the algorithm stored in the * tokenHeader. * * @param optionalHeader an optional header that will be processed first * during checksum calculation. * * @param data the application data * @param offset the offset where the data begins * @param len the length of the application data * * @param optionalTrailer an optional trailer that will be processed last * during checksum calculation. e.g., padding that should be appended to * the application data * * @throws GSSException if an error occurs in the checksum calculation. */ private byte[] getChecksum(byte[] optionalHeader, byte[] data, int offset, int len, byte[] optionalTrailer) throws GSSException { // debug("Will do getChecksum:\n"); /* * For checksum calculation the token header bytes i.e., the first 8 * 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(); byte[] existingHeader = optionalHeader; byte[] checksumDataHeader = tokenHeaderBytes; if (existingHeader != null) { checksumDataHeader = new byte[tokenHeaderBytes.length + existingHeader.length]; System.arraycopy(tokenHeaderBytes, 0, checksumDataHeader, 0, tokenHeaderBytes.length); System.arraycopy(existingHeader, 0, checksumDataHeader, tokenHeaderBytes.length, existingHeader.length); } return cipherHelper.calculateChecksum(tokenHeader.getSignAlg(), checksumDataHeader, optionalTrailer, data, offset, len, tokenId); } /** * 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(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.cipherHelper = context.getCipherHelper(null); // debug("In MessageToken.Cons"); } /** * Encodes a GSSHeader and this token onto an OutputStream. * * @param os the OutputStream to which this should be written * @throws GSSException if an error occurs while writing to the OutputStream */ public void encode(OutputStream os) throws IOException, GSSException { gssHeader = new GSSHeader(OID, getKrb5TokenSize()); gssHeader.encode(os); tokenHeader.encode(os); // debug("Writing seqNumber: " + getHexBytes(encSeqNumber)); os.write(encSeqNumber); // debug("Writing checksum: " + getHexBytes(checksum)); os.write(checksum); } /** * Obtains the size of this token. Note that this excludes the size of * the GSSHeader. * @return token size */ protected int getKrb5TokenSize() throws GSSException { return getTokenSize(); } protected final int getTokenSize() throws GSSException { return TOKEN_NO_CKSUM_SIZE + cipherHelper.getChecksumLength(); } protected static final int getTokenSize(CipherHelper ch) throws GSSException { return TOKEN_NO_CKSUM_SIZE + ch.getChecksumLength(); } /** * Obtains the conext key that is associated with this token. * @return the context key */ /* public final byte[] getContextKey() { return contextKey; } */ /** * Obtains the encryption algorithm that should be used in this token * given the state of confidentiality the application requested. * Requested qop must be consistent with negotiated session key. * @param confRequested true if the application desired confidentiality * on this token, false otherwise * @param qop the qop requested by the application * @throws GSSException if qop is incompatible with the negotiated * session key */ protected abstract int getSealAlg(boolean confRequested, int qop) throws GSSException; // ******************************************* // // 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 * and contains information about the checksum and encryption algorithms * that are in use. It constitutes the first 8 bytes of the * message token: * <pre> * 0..1 TOK_ID Identification field. * 01 01 - Mic token * 02 01 - Wrap token * 2..3 SGN_ALG Checksum algorithm indicator. * 00 00 - DES MAC MD5 * 01 00 - MD2.5 * 02 00 - DES MAC * 04 00 - HMAC SHA1 DES3-KD * 11 00 - RC4-HMAC * 4..5 SEAL_ALG ff ff - none * 00 00 - DES * 02 00 - DES3-KD * 10 00 - RC4-HMAC * 6..7 Filler Contains ff ff * </pre> */ class MessageTokenHeader { private int tokenId; private int signAlg; private int sealAlg; private byte[] bytes = new byte[8]; /** * Constructs a MessageTokenHeader for the specified token type with * appropriate checksum and encryption algorithms fields. * * @param tokenId the token id for this mesage token * @param conf true if confidentiality will be resuested with this * message token, false otherwise. * @param qop the value of the quality of protection that will be * desired. */ public MessageTokenHeader(int tokenId, boolean conf, int qop) throws GSSException { this.tokenId = tokenId; signAlg = MessageToken.this.getSgnAlg(qop); sealAlg = MessageToken.this.getSealAlg(conf, qop); bytes[0] = (byte) (tokenId >>> 8); bytes[1] = (byte) (tokenId); bytes[2] = (byte) (signAlg >>> 8); bytes[3] = (byte) (signAlg); bytes[4] = (byte) (sealAlg >>> 8); bytes[5] = (byte) (sealAlg); bytes[6] = (byte) (MessageToken.FILLER >>> 8); bytes[7] = (byte) (MessageToken.FILLER); } /** * Constructs a MessageTokenHeader by reading it 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) throws IOException { readFully(is, bytes); tokenId = readInt(bytes, TOKEN_ID_POS); signAlg = readInt(bytes, SIGN_ALG_POS); sealAlg = readInt(bytes, SEAL_ALG_POS); // debug("\nMessageTokenHeader read tokenId=" + // getHexBytes(bytes) + "\n"); // XXX compare to FILLER int temp = readInt(bytes, SEAL_ALG_POS + 2); // debug("SIGN_ALG=" + signAlg); switch (sealAlg) { case SEAL_ALG_DES: case SEAL_ALG_DES3_KD: case SEAL_ALG_ARCFOUR_HMAC: prop.setPrivacy(true); break; default: prop.setPrivacy(false); } prop.setQOP(0); // default } /** * 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 * @see sun.security.jgss.krb5.Krb5Token#WRAP_ID */ public final int getTokenId() { return tokenId; } /** * Returns the sign algorithm for the message token. * @return the sign algorithm * @see sun.security.jgss.krb5.MessageToken#SIGN_DES_MAC * @see sun.security.jgss.krb5.MessageToken#SIGN_DES_MAC_MD5 */ public final int getSignAlg() { return signAlg; } /** * Returns the seal algorithm for the message token. * @return the seal algorithm * @see sun.security.jgss.krb5.MessageToken#SEAL_ALG_DES * @see sun.security.jgss.krb5.MessageToken#SEAL_ALG_NONE */ public final int getSealAlg() { return sealAlg; } /** * Returns the bytes of this header. * @return 8 bytes that form this header */ public final byte[] getBytes() { return bytes; } } // end of class MessageTokenHeader /** * Determine signing algorithm based on QOP. */ protected int getSgnAlg(int qop) throws GSSException { // QOP ignored return cipherHelper.getSgnAlg(); } }