Path: blob/master/src/java.security.jgss/share/classes/sun/security/jgss/krb5/MessageToken.java
41161 views
/*1* Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.jgss.krb5;2627import org.ietf.jgss.*;28import sun.security.jgss.*;29import java.io.InputStream;30import java.io.OutputStream;31import java.io.IOException;32import java.io.ByteArrayInputStream;33import java.security.MessageDigest;3435/**36* This class is a base class for other token definitions that pertain to37* per-message GSS-API calls. Conceptually GSS-API has two types of38* per-message tokens: WrapToken and MicToken. They differ in the respect39* that a WrapToken carries additional plaintext or ciphertext application40* data besides just the sequence number and checksum. This class41* encapsulates the commonality in the structure of the WrapToken and the42* MicToken. This structure can be represented as:43* <p>44* <pre>45* 0..1 TOK_ID Identification field.46* 01 01 - Mic token47* 02 01 - Wrap token48* 2..3 SGN_ALG Checksum algorithm indicator.49* 00 00 - DES MAC MD550* 01 00 - MD2.551* 02 00 - DES MAC52* 04 00 - HMAC SHA1 DES3-KD53* 11 00 - RC4-HMAC54* 4..5 SEAL_ALG ff ff - none55* 00 00 - DES56* 02 00 - DES3-KD57* 10 00 - RC4-HMAC58* 6..7 Filler Contains ff ff59* 8..15 SND_SEQ Encrypted sequence number field.60* 16..s+15 SGN_CKSUM Checksum of plaintext padded data,61* calculated according to algorithm62* specified in SGN_ALG field.63* s+16..last Data encrypted or plaintext padded data64* </pre>65* Where "s" indicates the size of the checksum.66* <p>67* As always, this is preceeded by a GSSHeader.68*69* @author Mayank Upadhyay70* @author Ram Marti71* @see sun.security.jgss.GSSHeader72*/7374abstract class MessageToken extends Krb5Token {75/* Fields in header minus checksum size */76private static final int TOKEN_NO_CKSUM_SIZE = 16;7778/**79* Filler data as defined in the specification of the Kerberos v5 GSS-API80* Mechanism.81*/82private static final int FILLER = 0xffff;8384// Signing algorithm values (for the SNG_ALG field)8586// From RFC 196487/* Use a DES MAC MD5 checksum */88static final int SGN_ALG_DES_MAC_MD5 = 0x0000;8990/* Use DES MAC checksum. */91static final int SGN_ALG_DES_MAC = 0x0200;9293// From draft-raeburn-cat-gssapi-krb5-3des-0094/* Use a HMAC SHA1 DES3 -KD checksum */95static final int SGN_ALG_HMAC_SHA1_DES3_KD = 0x0400;9697// Sealing algorithm values (for the SEAL_ALG field)9899// RFC 1964100/**101* A value for the SEAL_ALG field that indicates that no encryption was102* used.103*/104static final int SEAL_ALG_NONE = 0xffff;105/* Use DES CBC encryption algorithm. */106static final int SEAL_ALG_DES = 0x0000;107108// From draft-raeburn-cat-gssapi-krb5-3des-00109/**110* Use DES3-KD sealing algorithm. (draft-raeburn-cat-gssapi-krb5-3des-00)111* This algorithm uses triple-DES with key derivation, with a usage112* value KG_USAGE_SEAL. Padding is still to 8-byte multiples, and the113* IV for encrypting application data is zero.114*/115static final int SEAL_ALG_DES3_KD = 0x0200;116117// draft draft-brezak-win2k-krb-rc4-hmac-04.txt118static final int SEAL_ALG_ARCFOUR_HMAC = 0x1000;119static final int SGN_ALG_HMAC_MD5_ARCFOUR = 0x1100;120121private static final int TOKEN_ID_POS = 0;122private static final int SIGN_ALG_POS = 2;123private static final int SEAL_ALG_POS = 4;124125private int seqNumber;126127private boolean confState = true;128private boolean initiator = true;129130private int tokenId = 0;131private GSSHeader gssHeader = null;132private MessageTokenHeader tokenHeader = null;133private byte[] checksum = null;134private byte[] encSeqNumber = null;135private byte[] seqNumberData = null;136137/* cipher instance used by the corresponding GSSContext */138CipherHelper cipherHelper = null;139140141/**142* Constructs a MessageToken from a byte array. If there are more bytes143* in the array than needed, the extra bytes are simply ignroed.144*145* @param tokenId the token id that should be contained in this token as146* it is read.147* @param context the Kerberos context associated with this token148* @param tokenBytes the byte array containing the token149* @param tokenOffset the offset where the token begins150* @param tokenLen the length of the token151* @param prop the MessageProp structure in which the properties of the152* token should be stored.153* @throws GSSException if there is a problem parsing the token154*/155MessageToken(int tokenId, Krb5Context context,156byte[] tokenBytes, int tokenOffset, int tokenLen,157MessageProp prop) throws GSSException {158this(tokenId, context,159new ByteArrayInputStream(tokenBytes, tokenOffset, tokenLen),160prop);161}162163/**164* Constructs a MessageToken from an InputStream. Bytes will be read on165* demand and the thread might block if there are not enough bytes to166* complete the token.167*168* @param tokenId the token id that should be contained in this token as169* it is read.170* @param context the Kerberos context associated with this token171* @param is the InputStream from which to read172* @param prop the MessageProp structure in which the properties of the173* token should be stored.174* @throws GSSException if there is a problem reading from the175* InputStream or parsing the token176*/177MessageToken(int tokenId, Krb5Context context, InputStream is,178MessageProp prop) throws GSSException {179init(tokenId, context);180181try {182gssHeader = new GSSHeader(is);183184if (!gssHeader.getOid().equals(OID)) {185throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1,186getTokenName(tokenId));187}188if (!confState) {189prop.setPrivacy(false);190}191192tokenHeader = new MessageTokenHeader(is, prop);193194encSeqNumber = new byte[8];195readFully(is, encSeqNumber);196197// debug("\n\tRead EncSeq#=" +198// getHexBytes(encSeqNumber, encSeqNumber.length));199200checksum = new byte[cipherHelper.getChecksumLength()];201readFully(is, checksum);202203// debug("\n\tRead checksum=" +204// getHexBytes(checksum, checksum.length));205// debug("\nLeaving MessageToken.Cons\n");206207} catch (IOException e) {208throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1,209getTokenName(tokenId) + ":" + e.getMessage());210}211}212213/**214* Used to obtain the GSSHeader that was at the start of this215* token.216*/217public final GSSHeader getGSSHeader() {218return gssHeader;219}220221/**222* Used to obtain the token id that was contained in this token.223* @return the token id in the token224*/225public final int getTokenId() {226return tokenId;227}228229/**230* Used to obtain the encrypted sequence number in this token.231* @return the encrypted sequence number in the token232*/233public final byte[] getEncSeqNumber() {234return encSeqNumber;235}236237/**238* Used to obtain the checksum that was contained in this token.239* @return the checksum in the token240*/241public final byte[] getChecksum() {242return checksum;243}244245/**246* Used to determine if this token contains any encrypted data.247* @return true if it contains any encrypted data, false if there is only248* plaintext data or if there is no data.249*/250public final boolean getConfState() {251return confState;252}253254/**255* Generates the checksum field and the encrypted sequence number256* field. The encrypted sequence number uses the 8 bytes of the checksum257* as an initial vector in a fixed DesCbc algorithm.258*259* @param prop the MessageProp structure that determines what sort of260* checksum and sealing algorithm should be used. The lower byte261* of qop determines the checksum algorithm while the upper byte262* determines the signing algorithm.263* Checksum values are:264* 0 - default (DES_MAC)265* 1 - MD5266* 2 - DES_MD5267* 3 - DES_MAC268* 4 - HMAC_SHA1269* Sealing values are:270* 0 - default (DES)271* 1 - DES272* 2 - DES3-KD273*274* @param optionalHeader an optional header that will be processed first275* during checksum calculation276*277* @param data the application data to checksum278* @param offset the offset where the data starts279* @param len the length of the data280*281* @param optionalTrailer an optional trailer that will be processed282* last during checksum calculation. e.g., padding that should be283* appended to the application data284*285* @throws GSSException if an error occurs in the checksum calculation or286* encryption sequence number calculation.287*/288public void genSignAndSeqNumber(MessageProp prop,289byte[] optionalHeader,290byte[] data, int offset, int len,291byte[] optionalTrailer)292throws GSSException {293294// debug("Inside MessageToken.genSignAndSeqNumber:\n");295296int qop = prop.getQOP();297if (qop != 0) {298qop = 0;299prop.setQOP(qop);300}301302if (!confState) {303prop.setPrivacy(false);304}305306// Create a token header with the correct sign and seal algorithm307// values.308tokenHeader =309new MessageTokenHeader(tokenId, prop.getPrivacy(), qop);310311// Calculate SGN_CKSUM312313checksum =314getChecksum(optionalHeader, data, offset, len, optionalTrailer);315316// debug("\n\tCalc checksum=" +317// getHexBytes(checksum, checksum.length));318319// Calculate SND_SEQ320321seqNumberData = new byte[8];322323// When using this RC4 based encryption type, the sequence number is324// always sent in big-endian rather than little-endian order.325if (cipherHelper.isArcFour()) {326writeBigEndian(seqNumber, seqNumberData);327} else {328// for all other etypes329writeLittleEndian(seqNumber, seqNumberData);330}331if (!initiator) {332seqNumberData[4] = (byte)0xff;333seqNumberData[5] = (byte)0xff;334seqNumberData[6] = (byte)0xff;335seqNumberData[7] = (byte)0xff;336}337338encSeqNumber = cipherHelper.encryptSeq(checksum, seqNumberData, 0, 8);339340// debug("\n\tCalc seqNum=" +341// getHexBytes(seqNumberData, seqNumberData.length));342// debug("\n\tCalc encSeqNum=" +343// getHexBytes(encSeqNumber, encSeqNumber.length));344}345346/**347* Verifies that the checksum field and sequence number direction bytes348* are valid and consistent with the application data.349*350* @param optionalHeader an optional header that will be processed first351* during checksum calculation.352*353* @param data the application data354* @param offset the offset where the data begins355* @param len the length of the application data356*357* @param optionalTrailer an optional trailer that will be processed last358* during checksum calculation. e.g., padding that should be appended to359* the application data360*361* @throws GSSException if an error occurs in the checksum calculation or362* encryption sequence number calculation.363*/364public final boolean verifySignAndSeqNumber(byte[] optionalHeader,365byte[] data, int offset, int len,366byte[] optionalTrailer)367throws GSSException {368// debug("\tIn verifySign:\n");369370// debug("\t\tchecksum: [" + getHexBytes(checksum) + "]\n");371372byte[] myChecksum =373getChecksum(optionalHeader, data, offset, len, optionalTrailer);374375// debug("\t\tmychecksum: [" + getHexBytes(myChecksum) +"]\n");376// debug("\t\tchecksum: [" + getHexBytes(checksum) + "]\n");377378if (MessageDigest.isEqual(checksum, myChecksum)) {379380seqNumberData = cipherHelper.decryptSeq(381checksum, encSeqNumber, 0, 8);382383// debug("\t\tencSeqNumber: [" + getHexBytes(encSeqNumber)384// + "]\n");385// debug("\t\tseqNumberData: [" + getHexBytes(seqNumberData)386// + "]\n");387388/*389* The token from the initiator has direction bytes 0x00 and390* the token from the acceptor has direction bytes 0xff.391*/392byte directionByte = 0;393if (initiator)394directionByte = (byte) 0xff; // Received token from acceptor395396if ((seqNumberData[4] == directionByte) &&397(seqNumberData[5] == directionByte) &&398(seqNumberData[6] == directionByte) &&399(seqNumberData[7] == directionByte))400return true;401}402403return false;404405}406407public final int getSequenceNumber() {408int sequenceNum = 0;409if (cipherHelper.isArcFour()) {410sequenceNum = readBigEndian(seqNumberData, 0, 4);411} else {412sequenceNum = readLittleEndian(seqNumberData, 0, 4);413}414return sequenceNum;415}416417/**418* Computes the checksum based on the algorithm stored in the419* tokenHeader.420*421* @param optionalHeader an optional header that will be processed first422* during checksum calculation.423*424* @param data the application data425* @param offset the offset where the data begins426* @param len the length of the application data427*428* @param optionalTrailer an optional trailer that will be processed last429* during checksum calculation. e.g., padding that should be appended to430* the application data431*432* @throws GSSException if an error occurs in the checksum calculation.433*/434private byte[] getChecksum(byte[] optionalHeader,435byte[] data, int offset, int len,436byte[] optionalTrailer)437throws GSSException {438439// debug("Will do getChecksum:\n");440441/*442* For checksum calculation the token header bytes i.e., the first 8443* bytes following the GSSHeader, are logically prepended to the444* application data to bind the data to this particular token.445*446* Note: There is no such requirement wrt adding padding to the447* application data for checksumming, although the cryptographic448* algorithm used might itself apply some padding.449*/450451byte[] tokenHeaderBytes = tokenHeader.getBytes();452byte[] existingHeader = optionalHeader;453byte[] checksumDataHeader = tokenHeaderBytes;454455if (existingHeader != null) {456checksumDataHeader = new byte[tokenHeaderBytes.length +457existingHeader.length];458System.arraycopy(tokenHeaderBytes, 0,459checksumDataHeader, 0, tokenHeaderBytes.length);460System.arraycopy(existingHeader, 0,461checksumDataHeader, tokenHeaderBytes.length,462existingHeader.length);463}464465return cipherHelper.calculateChecksum(tokenHeader.getSignAlg(),466checksumDataHeader, optionalTrailer, data, offset, len, tokenId);467}468469470/**471* Constructs an empty MessageToken for the local context to send to472* the peer. It also increments the local sequence number in the473* Krb5Context instance it uses after obtaining the object lock for474* it.475*476* @param tokenId the token id that should be contained in this token477* @param context the Kerberos context associated with this token478*/479MessageToken(int tokenId, Krb5Context context) throws GSSException {480/*481debug("\n============================");482debug("\nMySessionKey=" +483getHexBytes(context.getMySessionKey().getBytes()));484debug("\nPeerSessionKey=" +485getHexBytes(context.getPeerSessionKey().getBytes()));486debug("\n============================\n");487*/488init(tokenId, context);489this.seqNumber = context.incrementMySequenceNumber();490}491492private void init(int tokenId, Krb5Context context) throws GSSException {493this.tokenId = tokenId;494// Just for consistency check in Wrap495this.confState = context.getConfState();496497this.initiator = context.isInitiator();498499this.cipherHelper = context.getCipherHelper(null);500// debug("In MessageToken.Cons");501}502503/**504* Encodes a GSSHeader and this token onto an OutputStream.505*506* @param os the OutputStream to which this should be written507* @throws GSSException if an error occurs while writing to the OutputStream508*/509public void encode(OutputStream os) throws IOException, GSSException {510gssHeader = new GSSHeader(OID, getKrb5TokenSize());511gssHeader.encode(os);512tokenHeader.encode(os);513// debug("Writing seqNumber: " + getHexBytes(encSeqNumber));514os.write(encSeqNumber);515// debug("Writing checksum: " + getHexBytes(checksum));516os.write(checksum);517}518519/**520* Obtains the size of this token. Note that this excludes the size of521* the GSSHeader.522* @return token size523*/524protected int getKrb5TokenSize() throws GSSException {525return getTokenSize();526}527528protected final int getTokenSize() throws GSSException {529return TOKEN_NO_CKSUM_SIZE + cipherHelper.getChecksumLength();530}531532protected static final int getTokenSize(CipherHelper ch)533throws GSSException {534return TOKEN_NO_CKSUM_SIZE + ch.getChecksumLength();535}536537/**538* Obtains the conext key that is associated with this token.539* @return the context key540*/541/*542public final byte[] getContextKey() {543return contextKey;544}545*/546547/**548* Obtains the encryption algorithm that should be used in this token549* given the state of confidentiality the application requested.550* Requested qop must be consistent with negotiated session key.551* @param confRequested true if the application desired confidentiality552* on this token, false otherwise553* @param qop the qop requested by the application554* @throws GSSException if qop is incompatible with the negotiated555* session key556*/557protected abstract int getSealAlg(boolean confRequested, int qop)558throws GSSException;559560// ******************************************* //561// I N N E R C L A S S E S F O L L O W562// ******************************************* //563564/**565* This inner class represents the initial portion of the message token566* and contains information about the checksum and encryption algorithms567* that are in use. It constitutes the first 8 bytes of the568* message token:569* <pre>570* 0..1 TOK_ID Identification field.571* 01 01 - Mic token572* 02 01 - Wrap token573* 2..3 SGN_ALG Checksum algorithm indicator.574* 00 00 - DES MAC MD5575* 01 00 - MD2.5576* 02 00 - DES MAC577* 04 00 - HMAC SHA1 DES3-KD578* 11 00 - RC4-HMAC579* 4..5 SEAL_ALG ff ff - none580* 00 00 - DES581* 02 00 - DES3-KD582* 10 00 - RC4-HMAC583* 6..7 Filler Contains ff ff584* </pre>585*/586class MessageTokenHeader {587588private int tokenId;589private int signAlg;590private int sealAlg;591592private byte[] bytes = new byte[8];593594/**595* Constructs a MessageTokenHeader for the specified token type with596* appropriate checksum and encryption algorithms fields.597*598* @param tokenId the token id for this message token599* @param conf true if confidentiality will be resuested with this600* message token, false otherwise.601* @param qop the value of the quality of protection that will be602* desired.603*/604public MessageTokenHeader(int tokenId, boolean conf, int qop)605throws GSSException {606607this.tokenId = tokenId;608609signAlg = MessageToken.this.getSgnAlg(qop);610611sealAlg = MessageToken.this.getSealAlg(conf, qop);612613bytes[0] = (byte) (tokenId >>> 8);614bytes[1] = (byte) (tokenId);615616bytes[2] = (byte) (signAlg >>> 8);617bytes[3] = (byte) (signAlg);618619bytes[4] = (byte) (sealAlg >>> 8);620bytes[5] = (byte) (sealAlg);621622bytes[6] = (byte) (MessageToken.FILLER >>> 8);623bytes[7] = (byte) (MessageToken.FILLER);624}625626/**627* Constructs a MessageTokenHeader by reading it from an InputStream628* and sets the appropriate confidentiality and quality of protection629* values in a MessageProp structure.630*631* @param is the InputStream to read from632* @param prop the MessageProp to populate633* @throws IOException is an error occurs while reading from the634* InputStream635*/636public MessageTokenHeader(InputStream is, MessageProp prop)637throws IOException {638readFully(is, bytes);639tokenId = readInt(bytes, TOKEN_ID_POS);640signAlg = readInt(bytes, SIGN_ALG_POS);641sealAlg = readInt(bytes, SEAL_ALG_POS);642// debug("\nMessageTokenHeader read tokenId=" +643// getHexBytes(bytes) + "\n");644// XXX compare to FILLER645int temp = readInt(bytes, SEAL_ALG_POS + 2);646647// debug("SIGN_ALG=" + signAlg);648649switch (sealAlg) {650case SEAL_ALG_DES:651case SEAL_ALG_DES3_KD:652case SEAL_ALG_ARCFOUR_HMAC:653prop.setPrivacy(true);654break;655656default:657prop.setPrivacy(false);658}659660prop.setQOP(0); // default661}662663/**664* Encodes this MessageTokenHeader onto an OutputStream665* @param os the OutputStream to write to666* @throws IOException is an error occurs while writing667*/668public final void encode(OutputStream os) throws IOException {669os.write(bytes);670}671672673/**674* Returns the token id for the message token.675* @return the token id676* @see sun.security.jgss.krb5.Krb5Token#MIC_ID677* @see sun.security.jgss.krb5.Krb5Token#WRAP_ID678*/679public final int getTokenId() {680return tokenId;681}682683/**684* Returns the sign algorithm for the message token.685* @return the sign algorithm686* @see sun.security.jgss.krb5.MessageToken#SIGN_DES_MAC687* @see sun.security.jgss.krb5.MessageToken#SIGN_DES_MAC_MD5688*/689public final int getSignAlg() {690return signAlg;691}692693/**694* Returns the seal algorithm for the message token.695* @return the seal algorithm696* @see sun.security.jgss.krb5.MessageToken#SEAL_ALG_DES697* @see sun.security.jgss.krb5.MessageToken#SEAL_ALG_NONE698*/699public final int getSealAlg() {700return sealAlg;701}702703/**704* Returns the bytes of this header.705* @return 8 bytes that form this header706*/707public final byte[] getBytes() {708return bytes;709}710} // end of class MessageTokenHeader711712713/**714* Determine signing algorithm based on QOP.715*/716protected int getSgnAlg(int qop) throws GSSException {717// QOP ignored718return cipherHelper.getSgnAlg();719}720}721722723