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