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