0N/A/*
3321N/A * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage sun.security.jgss.krb5;
0N/A
0N/Aimport org.ietf.jgss.*;
0N/Aimport sun.security.jgss.*;
0N/Aimport java.io.InputStream;
0N/Aimport java.io.OutputStream;
0N/Aimport java.io.IOException;
0N/Aimport java.io.ByteArrayOutputStream;
0N/Aimport sun.security.krb5.Confounder;
0N/A
0N/A/**
0N/A * This class represents a token emitted by the GSSContext.wrap()
0N/A * call. It is a MessageToken except that it also contains plaintext
0N/A * or encrypted data at the end. A wrapToken has certain other rules
0N/A * that are peculiar to it and different from a MICToken, which is
0N/A * another type of MessageToken. All data in a WrapToken is prepended
0N/A * by a random counfounder of 8 bytes. All data in a WrapToken is
0N/A * also padded with one to eight bytes where all bytes are equal in
0N/A * value to the number of bytes being padded. Thus, all application
0N/A * data is replaced by (confounder || data || padding).
0N/A *
0N/A * @author Mayank Upadhyay
0N/A */
0N/Aclass WrapToken extends MessageToken {
0N/A /**
0N/A * The size of the random confounder used in a WrapToken.
0N/A */
0N/A static final int CONFOUNDER_SIZE = 8;
0N/A
0N/A /*
0N/A * The padding used with a WrapToken. All data is padded to the
0N/A * next multiple of 8 bytes, even if its length is already
0N/A * multiple of 8.
0N/A * Use this table as a quick way to obtain padding bytes by
0N/A * indexing it with the number of padding bytes required.
0N/A */
0N/A static final byte[][] pads = {
0N/A null, // No, no one escapes padding
0N/A {0x01},
0N/A {0x02, 0x02},
0N/A {0x03, 0x03, 0x03},
0N/A {0x04, 0x04, 0x04, 0x04},
0N/A {0x05, 0x05, 0x05, 0x05, 0x05},
0N/A {0x06, 0x06, 0x06, 0x06, 0x06, 0x06},
0N/A {0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07},
0N/A {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}
0N/A };
0N/A
0N/A /*
0N/A * A token may come in either in an InputStream or as a
0N/A * byte[]. Store a reference to it in either case and process
0N/A * it's data only later when getData() is called and
0N/A * decryption/copying is needed to be done. Note that JCE can
0N/A * decrypt both from a byte[] and from an InputStream.
0N/A */
0N/A private boolean readTokenFromInputStream = true;
0N/A private InputStream is = null;
0N/A private byte[] tokenBytes = null;
0N/A private int tokenOffset = 0;
0N/A private int tokenLen = 0;
0N/A
0N/A /*
0N/A * Application data may come from an InputStream or from a
0N/A * byte[]. However, it will always be stored and processed as a
0N/A * byte[] since
0N/A * (a) the MessageDigest class only accepts a byte[] as input and
0N/A * (b) It allows writing to an OuputStream via a CipherOutputStream.
0N/A */
0N/A private byte[] dataBytes = null;
0N/A private int dataOffset = 0;
0N/A private int dataLen = 0;
0N/A
0N/A // the len of the token data: (confounder || data || padding)
0N/A private int dataSize = 0;
0N/A
0N/A // Accessed by CipherHelper
0N/A byte[] confounder = null;
0N/A byte[] padding = null;
0N/A
0N/A private boolean privacy = false;
0N/A
0N/A /**
0N/A * Constructs a WrapToken from token bytes obtained from the
0N/A * peer.
0N/A * @param context the mechanism context associated with this
0N/A * token
0N/A * @param tokenBytes the bytes of the token
0N/A * @param tokenOffset the offset of the token
0N/A * @param tokenLen the length of the token
0N/A * @param prop the MessageProp into which characteristics of the
0N/A * parsed token will be stored.
0N/A * @throws GSSException if the token is defective
0N/A */
0N/A public WrapToken(Krb5Context context,
0N/A byte[] tokenBytes, int tokenOffset, int tokenLen,
0N/A MessageProp prop) throws GSSException {
0N/A
0N/A // Just parse the MessageToken part first
0N/A super(Krb5Token.WRAP_ID, context,
0N/A tokenBytes, tokenOffset, tokenLen, prop);
0N/A
0N/A this.readTokenFromInputStream = false;
0N/A
0N/A // Will need the token bytes again when extracting data
0N/A this.tokenBytes = tokenBytes;
0N/A this.tokenOffset = tokenOffset;
0N/A this.tokenLen = tokenLen;
0N/A this.privacy = prop.getPrivacy();
0N/A dataSize =
0N/A getGSSHeader().getMechTokenLength() - getKrb5TokenSize();
0N/A }
0N/A
0N/A /**
0N/A * Constructs a WrapToken from token bytes read on the fly from
0N/A * an InputStream.
0N/A * @param context the mechanism context associated with this
0N/A * token
0N/A * @param is the InputStream containing the token bytes
0N/A * @param prop the MessageProp into which characteristics of the
0N/A * parsed token will be stored.
0N/A * @throws GSSException if the token is defective or if there is
0N/A * a problem reading from the InputStream
0N/A */
0N/A public WrapToken(Krb5Context context,
0N/A InputStream is, MessageProp prop)
0N/A throws GSSException {
0N/A
0N/A // Just parse the MessageToken part first
0N/A super(Krb5Token.WRAP_ID, context, is, prop);
0N/A
0N/A // Will need the token bytes again when extracting data
0N/A this.is = is;
0N/A this.privacy = prop.getPrivacy();
0N/A /*
0N/A debug("WrapToken Cons: gssHeader.getMechTokenLength=" +
0N/A getGSSHeader().getMechTokenLength());
0N/A debug("\n token size="
0N/A + getTokenSize());
0N/A */
0N/A
0N/A dataSize =
0N/A getGSSHeader().getMechTokenLength() - getTokenSize();
0N/A // debug("\n dataSize=" + dataSize);
0N/A // debug("\n");
0N/A }
0N/A
0N/A /**
0N/A * Obtains the application data that was transmitted in this
0N/A * WrapToken.
0N/A * @return a byte array containing the application data
0N/A * @throws GSSException if an error occurs while decrypting any
0N/A * cipher text and checking for validity
0N/A */
0N/A public byte[] getData() throws GSSException {
0N/A
0N/A byte[] temp = new byte[dataSize];
0N/A getData(temp, 0);
0N/A
0N/A // Remove the confounder and the padding
0N/A byte[] retVal = new byte[dataSize - confounder.length -
0N/A padding.length];
0N/A System.arraycopy(temp, 0, retVal, 0, retVal.length);
0N/A
0N/A return retVal;
0N/A }
0N/A
0N/A /**
0N/A * Obtains the application data that was transmitted in this
0N/A * WrapToken, writing it into an application provided output
0N/A * array.
0N/A * @param dataBuf the output buffer into which the data must be
0N/A * written
0N/A * @param dataBufOffset the offset at which to write the data
0N/A * @return the size of the data written
0N/A * @throws GSSException if an error occurs while decrypting any
0N/A * cipher text and checking for validity
0N/A */
0N/A public int getData(byte[] dataBuf, int dataBufOffset)
0N/A throws GSSException {
0N/A
0N/A if (readTokenFromInputStream)
0N/A getDataFromStream(dataBuf, dataBufOffset);
0N/A else
0N/A getDataFromBuffer(dataBuf, dataBufOffset);
0N/A
0N/A return (dataSize - confounder.length - padding.length);
0N/A }
0N/A
0N/A /**
0N/A * Helper routine to obtain the application data transmitted in
0N/A * this WrapToken. It is called if the WrapToken was constructed
0N/A * with a byte array as input.
0N/A * @param dataBuf the output buffer into which the data must be
0N/A * written
0N/A * @param dataBufOffset the offset at which to write the data
0N/A * @throws GSSException if an error occurs while decrypting any
0N/A * cipher text and checking for validity
0N/A */
0N/A private void getDataFromBuffer(byte[] dataBuf, int dataBufOffset)
0N/A throws GSSException {
0N/A
0N/A GSSHeader gssHeader = getGSSHeader();
0N/A int dataPos = tokenOffset +
0N/A gssHeader.getLength() + getTokenSize();
0N/A
0N/A if (dataPos + dataSize > tokenOffset + tokenLen)
0N/A throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1,
0N/A "Insufficient data in "
0N/A + getTokenName(getTokenId()));
0N/A
0N/A // debug("WrapToken cons: data is token is [" +
0N/A // getHexBytes(tokenBytes, tokenOffset, tokenLen) + "]\n");
0N/A
0N/A confounder = new byte[CONFOUNDER_SIZE];
0N/A
0N/A // Do decryption if this token was privacy protected.
0N/A
0N/A if (privacy) {
0N/A cipherHelper.decryptData(this,
0N/A tokenBytes, dataPos, dataSize, dataBuf, dataBufOffset);
0N/A /*
0N/A debug("\t\tDecrypted data is [" +
0N/A getHexBytes(confounder) + " " +
0N/A getHexBytes(dataBuf, dataBufOffset,
0N/A dataSize - CONFOUNDER_SIZE - padding.length) +
0N/A getHexBytes(padding) +
0N/A "]\n");
0N/A */
0N/A
0N/A } else {
0N/A
0N/A // Token data is in cleartext
0N/A // debug("\t\tNo encryption was performed by peer.\n");
0N/A System.arraycopy(tokenBytes, dataPos,
0N/A confounder, 0, CONFOUNDER_SIZE);
0N/A int padSize = tokenBytes[dataPos + dataSize - 1];
0N/A if (padSize < 0)
0N/A padSize = 0;
0N/A if (padSize > 8)
0N/A padSize %= 8;
0N/A
0N/A padding = pads[padSize];
0N/A // debug("\t\tPadding applied was: " + padSize + "\n");
0N/A
0N/A System.arraycopy(tokenBytes, dataPos + CONFOUNDER_SIZE,
0N/A dataBuf, dataBufOffset, dataSize -
0N/A CONFOUNDER_SIZE - padSize);
0N/A
0N/A // byte[] debugbuf = new byte[dataSize - CONFOUNDER_SIZE - padSize];
0N/A // System.arraycopy(tokenBytes, dataPos + CONFOUNDER_SIZE,
0N/A // debugbuf, 0, debugbuf.length);
0N/A // debug("\t\tData is: " + getHexBytes(debugbuf, debugbuf.length));
0N/A }
0N/A
0N/A /*
0N/A * Make sure sign and sequence number are not corrupt
0N/A */
0N/A
0N/A if (!verifySignAndSeqNumber(confounder,
0N/A dataBuf, dataBufOffset,
0N/A dataSize - CONFOUNDER_SIZE
0N/A - padding.length,
0N/A padding))
0N/A throw new GSSException(GSSException.BAD_MIC, -1,
0N/A "Corrupt checksum or sequence number in Wrap token");
0N/A }
0N/A
0N/A /**
0N/A * Helper routine to obtain the application data transmitted in
0N/A * this WrapToken. It is called if the WrapToken was constructed
0N/A * with an Inputstream.
0N/A * @param dataBuf the output buffer into which the data must be
0N/A * written
0N/A * @param dataBufOffset the offset at which to write the data
0N/A * @throws GSSException if an error occurs while decrypting any
0N/A * cipher text and checking for validity
0N/A */
0N/A private void getDataFromStream(byte[] dataBuf, int dataBufOffset)
0N/A throws GSSException {
0N/A
0N/A GSSHeader gssHeader = getGSSHeader();
0N/A
0N/A // Don't check the token length. Data will be read on demand from
0N/A // the InputStream.
0N/A
0N/A // debug("WrapToken cons: data will be read from InputStream.\n");
0N/A
0N/A confounder = new byte[CONFOUNDER_SIZE];
0N/A
0N/A try {
0N/A
0N/A // Do decryption if this token was privacy protected.
0N/A
0N/A if (privacy) {
0N/A cipherHelper.decryptData(this, is, dataSize,
0N/A dataBuf, dataBufOffset);
0N/A
0N/A // debug("\t\tDecrypted data is [" +
0N/A // getHexBytes(confounder) + " " +
0N/A // getHexBytes(dataBuf, dataBufOffset,
0N/A // dataSize - CONFOUNDER_SIZE - padding.length) +
0N/A // getHexBytes(padding) +
0N/A // "]\n");
0N/A
0N/A } else {
0N/A
0N/A // Token data is in cleartext
0N/A // debug("\t\tNo encryption was performed by peer.\n");
0N/A readFully(is, confounder);
0N/A
3321N/A if (cipherHelper.isArcFour()) {
3321N/A padding = pads[1];
3321N/A readFully(is, dataBuf, dataBufOffset, dataSize-CONFOUNDER_SIZE-1);
3321N/A } else {
3321N/A // Data is always a multiple of 8 with this GSS Mech
3321N/A // Copy all but last block as they are
3321N/A int numBlocks = (dataSize - CONFOUNDER_SIZE)/8 - 1;
3321N/A int offset = dataBufOffset;
3321N/A for (int i = 0; i < numBlocks; i++) {
3321N/A readFully(is, dataBuf, offset, 8);
3321N/A offset += 8;
3321N/A }
0N/A
3321N/A byte[] finalBlock = new byte[8];
3321N/A readFully(is, finalBlock);
0N/A
3321N/A int padSize = finalBlock[7];
3321N/A padding = pads[padSize];
0N/A
3321N/A // debug("\t\tPadding applied was: " + padSize + "\n");
3321N/A System.arraycopy(finalBlock, 0, dataBuf, offset,
3321N/A finalBlock.length - padSize);
3321N/A }
0N/A }
0N/A } catch (IOException e) {
0N/A throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1,
0N/A getTokenName(getTokenId())
0N/A + ": " + e.getMessage());
0N/A }
0N/A
0N/A /*
0N/A * Make sure sign and sequence number are not corrupt
0N/A */
0N/A
0N/A if (!verifySignAndSeqNumber(confounder,
0N/A dataBuf, dataBufOffset,
0N/A dataSize - CONFOUNDER_SIZE
0N/A - padding.length,
0N/A padding))
0N/A throw new GSSException(GSSException.BAD_MIC, -1,
0N/A "Corrupt checksum or sequence number in Wrap token");
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Helper routine to pick the right padding for a certain length
0N/A * of application data. Every application message has some
0N/A * padding between 1 and 8 bytes.
0N/A * @param len the length of the application data
0N/A * @return the padding to be applied
0N/A */
0N/A private byte[] getPadding(int len) {
0N/A int padSize = 0;
0N/A // For RC4-HMAC, all padding is rounded up to 1 byte.
0N/A // One byte is needed to say that there is 1 byte of padding.
0N/A if (cipherHelper.isArcFour()) {
0N/A padSize = 1;
0N/A } else {
0N/A padSize = len % 8;
0N/A padSize = 8 - padSize;
0N/A }
0N/A return pads[padSize];
0N/A }
0N/A
0N/A public WrapToken(Krb5Context context, MessageProp prop,
0N/A byte[] dataBytes, int dataOffset, int dataLen)
0N/A throws GSSException {
0N/A
0N/A super(Krb5Token.WRAP_ID, context);
0N/A
0N/A confounder = Confounder.bytes(CONFOUNDER_SIZE);
0N/A
0N/A padding = getPadding(dataLen);
0N/A dataSize = confounder.length + dataLen + padding.length;
0N/A this.dataBytes = dataBytes;
0N/A this.dataOffset = dataOffset;
0N/A this.dataLen = dataLen;
0N/A
0N/A /*
0N/A debug("\nWrapToken cons: data to wrap is [" +
0N/A getHexBytes(confounder) + " " +
0N/A getHexBytes(dataBytes, dataOffset, dataLen) + " " +
0N/A // padding is never null for Wrap
0N/A getHexBytes(padding) + "]\n");
0N/A */
0N/A
0N/A genSignAndSeqNumber(prop,
0N/A confounder,
0N/A dataBytes, dataOffset, dataLen,
0N/A padding);
0N/A
0N/A /*
0N/A * If the application decides to ask for privacy when the context
0N/A * did not negotiate for it, do not provide it. The peer might not
0N/A * have support for it. The app will realize this with a call to
0N/A * pop.getPrivacy() after wrap().
0N/A */
0N/A if (!context.getConfState())
0N/A prop.setPrivacy(false);
0N/A
0N/A privacy = prop.getPrivacy();
0N/A }
0N/A
0N/A public void encode(OutputStream os) throws IOException, GSSException {
0N/A
0N/A super.encode(os);
0N/A
0N/A // debug("Writing data: [");
0N/A if (!privacy) {
0N/A
0N/A // debug(getHexBytes(confounder, confounder.length));
0N/A os.write(confounder);
0N/A
0N/A // debug(" " + getHexBytes(dataBytes, dataOffset, dataLen));
0N/A os.write(dataBytes, dataOffset, dataLen);
0N/A
0N/A // debug(" " + getHexBytes(padding, padding.length));
0N/A os.write(padding);
0N/A
0N/A } else {
0N/A
0N/A cipherHelper.encryptData(this, confounder,
0N/A dataBytes, dataOffset, dataLen, padding, os);
0N/A }
0N/A // debug("]\n");
0N/A }
0N/A
0N/A public byte[] encode() throws IOException, GSSException {
0N/A // XXX Fine tune this initial size
0N/A ByteArrayOutputStream bos = new ByteArrayOutputStream(dataSize + 50);
0N/A encode(bos);
0N/A return bos.toByteArray();
0N/A }
0N/A
0N/A public int encode(byte[] outToken, int offset)
0N/A throws IOException, GSSException {
0N/A
0N/A // Token header is small
0N/A ByteArrayOutputStream bos = new ByteArrayOutputStream();
0N/A super.encode(bos);
0N/A byte[] header = bos.toByteArray();
0N/A System.arraycopy(header, 0, outToken, offset, header.length);
0N/A offset += header.length;
0N/A
0N/A // debug("WrapToken.encode: Writing data: [");
0N/A if (!privacy) {
0N/A
0N/A // debug(getHexBytes(confounder, confounder.length));
0N/A System.arraycopy(confounder, 0, outToken, offset,
0N/A confounder.length);
0N/A offset += confounder.length;
0N/A
0N/A // debug(" " + getHexBytes(dataBytes, dataOffset, dataLen));
0N/A System.arraycopy(dataBytes, dataOffset, outToken, offset,
0N/A dataLen);
0N/A offset += dataLen;
0N/A
0N/A // debug(" " + getHexBytes(padding, padding.length));
0N/A System.arraycopy(padding, 0, outToken, offset, padding.length);
0N/A
0N/A } else {
0N/A
0N/A cipherHelper.encryptData(this, confounder, dataBytes,
0N/A dataOffset, dataLen, padding, outToken, offset);
0N/A
0N/A // debug(getHexBytes(outToken, offset, dataSize));
0N/A }
0N/A
0N/A // debug("]\n");
0N/A
0N/A // %%% assume that plaintext length == ciphertext len
0N/A return (header.length + confounder.length + dataLen + padding.length);
0N/A
0N/A }
0N/A
0N/A protected int getKrb5TokenSize() throws GSSException {
0N/A return (getTokenSize() + dataSize);
0N/A }
0N/A
0N/A protected int getSealAlg(boolean conf, int qop) throws GSSException {
0N/A if (!conf) {
0N/A return SEAL_ALG_NONE;
0N/A }
0N/A
0N/A // ignore QOP
0N/A return cipherHelper.getSealAlg();
0N/A }
0N/A
0N/A // This implementation is way too conservative. And it certainly
0N/A // doesn't return the maximum limit.
0N/A static int getSizeLimit(int qop, boolean confReq, int maxTokenSize,
0N/A CipherHelper ch) throws GSSException {
0N/A return (GSSHeader.getMaxMechTokenSize(OID, maxTokenSize) -
0N/A (getTokenSize(ch) + CONFOUNDER_SIZE) - 8); /* safety */
0N/A }
0N/A
0N/A}