| /* |
| * Copyright (c) 2000, 2007, 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 java.security.GeneralSecurityException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.IOException; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import sun.security.krb5.Confounder; |
| import sun.security.krb5.KrbException; |
| |
| /** |
| * This class represents a token emitted by the GSSContext.wrap() |
| * call. It is a MessageToken except that it also contains plaintext |
| * or encrypted data at the end. A wrapToken has certain other rules |
| * that are peculiar to it and different from a MICToken, which is |
| * another type of MessageToken. All data in a WrapToken is prepended |
| * by a random counfounder of 8 bytes. All data in a WrapToken is |
| * also padded with one to eight bytes where all bytes are equal in |
| * value to the number of bytes being padded. Thus, all application |
| * data is replaced by (confounder || data || padding). |
| * |
| * @author Mayank Upadhyay |
| */ |
| class WrapToken extends MessageToken { |
| /** |
| * The size of the random confounder used in a WrapToken. |
| */ |
| static final int CONFOUNDER_SIZE = 8; |
| |
| /* |
| * The padding used with a WrapToken. All data is padded to the |
| * next multiple of 8 bytes, even if its length is already |
| * multiple of 8. |
| * Use this table as a quick way to obtain padding bytes by |
| * indexing it with the number of padding bytes required. |
| */ |
| static final byte[][] pads = { |
| null, // No, no one escapes padding |
| {0x01}, |
| {0x02, 0x02}, |
| {0x03, 0x03, 0x03}, |
| {0x04, 0x04, 0x04, 0x04}, |
| {0x05, 0x05, 0x05, 0x05, 0x05}, |
| {0x06, 0x06, 0x06, 0x06, 0x06, 0x06}, |
| {0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07}, |
| {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08} |
| }; |
| |
| /* |
| * A token may come in either in an InputStream or as a |
| * byte[]. Store a reference to it in either case and process |
| * it's data only later when getData() is called and |
| * decryption/copying is needed to be done. Note that JCE can |
| * decrypt both from a byte[] and from an InputStream. |
| */ |
| private boolean readTokenFromInputStream = true; |
| private InputStream is = null; |
| private byte[] tokenBytes = null; |
| private int tokenOffset = 0; |
| private int tokenLen = 0; |
| |
| /* |
| * Application data may come from an InputStream or from a |
| * byte[]. However, it will always be stored and processed as a |
| * byte[] since |
| * (a) the MessageDigest class only accepts a byte[] as input and |
| * (b) It allows writing to an OuputStream via a CipherOutputStream. |
| */ |
| private byte[] dataBytes = null; |
| private int dataOffset = 0; |
| private int dataLen = 0; |
| |
| // the len of the token data: (confounder || data || padding) |
| private int dataSize = 0; |
| |
| // Accessed by CipherHelper |
| byte[] confounder = null; |
| byte[] padding = null; |
| |
| private boolean privacy = false; |
| |
| /** |
| * Constructs a WrapToken from token bytes obtained from the |
| * peer. |
| * @param context the mechanism context associated with this |
| * token |
| * @param tokenBytes the bytes of the token |
| * @param tokenOffset the offset of the token |
| * @param tokenLen the length of the token |
| * @param prop the MessageProp into which characteristics of the |
| * parsed token will be stored. |
| * @throws GSSException if the token is defective |
| */ |
| public WrapToken(Krb5Context context, |
| byte[] tokenBytes, int tokenOffset, int tokenLen, |
| MessageProp prop) throws GSSException { |
| |
| // Just parse the MessageToken part first |
| super(Krb5Token.WRAP_ID, context, |
| tokenBytes, tokenOffset, tokenLen, prop); |
| |
| this.readTokenFromInputStream = false; |
| |
| // Will need the token bytes again when extracting data |
| this.tokenBytes = tokenBytes; |
| this.tokenOffset = tokenOffset; |
| this.tokenLen = tokenLen; |
| this.privacy = prop.getPrivacy(); |
| dataSize = |
| getGSSHeader().getMechTokenLength() - getKrb5TokenSize(); |
| } |
| |
| /** |
| * Constructs a WrapToken from token bytes read on the fly from |
| * an InputStream. |
| * @param context the mechanism context associated with this |
| * token |
| * @param is the InputStream containing the token bytes |
| * @param prop the MessageProp into which characteristics of the |
| * parsed token will be stored. |
| * @throws GSSException if the token is defective or if there is |
| * a problem reading from the InputStream |
| */ |
| public WrapToken(Krb5Context context, |
| InputStream is, MessageProp prop) |
| throws GSSException { |
| |
| // Just parse the MessageToken part first |
| super(Krb5Token.WRAP_ID, context, is, prop); |
| |
| // Will need the token bytes again when extracting data |
| this.is = is; |
| this.privacy = prop.getPrivacy(); |
| /* |
| debug("WrapToken Cons: gssHeader.getMechTokenLength=" + |
| getGSSHeader().getMechTokenLength()); |
| debug("\n token size=" |
| + getTokenSize()); |
| */ |
| |
| dataSize = |
| getGSSHeader().getMechTokenLength() - getTokenSize(); |
| // debug("\n dataSize=" + dataSize); |
| // debug("\n"); |
| } |
| |
| /** |
| * Obtains the application data that was transmitted in this |
| * WrapToken. |
| * @return a byte array containing the application data |
| * @throws GSSException if an error occurs while decrypting any |
| * cipher text and checking for validity |
| */ |
| public byte[] getData() throws GSSException { |
| |
| byte[] temp = new byte[dataSize]; |
| getData(temp, 0); |
| |
| // Remove the confounder and the padding |
| byte[] retVal = new byte[dataSize - confounder.length - |
| padding.length]; |
| System.arraycopy(temp, 0, retVal, 0, retVal.length); |
| |
| return retVal; |
| } |
| |
| /** |
| * Obtains the application data that was transmitted in this |
| * WrapToken, writing it into an application provided output |
| * array. |
| * @param dataBuf the output buffer into which the data must be |
| * written |
| * @param dataBufOffset the offset at which to write the data |
| * @return the size of the data written |
| * @throws GSSException if an error occurs while decrypting any |
| * cipher text and checking for validity |
| */ |
| public int getData(byte[] dataBuf, int dataBufOffset) |
| throws GSSException { |
| |
| if (readTokenFromInputStream) |
| getDataFromStream(dataBuf, dataBufOffset); |
| else |
| getDataFromBuffer(dataBuf, dataBufOffset); |
| |
| return (dataSize - confounder.length - padding.length); |
| } |
| |
| /** |
| * Helper routine to obtain the application data transmitted in |
| * this WrapToken. It is called if the WrapToken was constructed |
| * with a byte array as input. |
| * @param dataBuf the output buffer into which the data must be |
| * written |
| * @param dataBufOffset the offset at which to write the data |
| * @throws GSSException if an error occurs while decrypting any |
| * cipher text and checking for validity |
| */ |
| private void getDataFromBuffer(byte[] dataBuf, int dataBufOffset) |
| throws GSSException { |
| |
| GSSHeader gssHeader = getGSSHeader(); |
| int dataPos = tokenOffset + |
| gssHeader.getLength() + getTokenSize(); |
| |
| if (dataPos + dataSize > tokenOffset + tokenLen) |
| throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, |
| "Insufficient data in " |
| + getTokenName(getTokenId())); |
| |
| // debug("WrapToken cons: data is token is [" + |
| // getHexBytes(tokenBytes, tokenOffset, tokenLen) + "]\n"); |
| |
| confounder = new byte[CONFOUNDER_SIZE]; |
| |
| // Do decryption if this token was privacy protected. |
| |
| if (privacy) { |
| cipherHelper.decryptData(this, |
| tokenBytes, dataPos, dataSize, dataBuf, dataBufOffset); |
| /* |
| debug("\t\tDecrypted data is [" + |
| getHexBytes(confounder) + " " + |
| getHexBytes(dataBuf, dataBufOffset, |
| dataSize - CONFOUNDER_SIZE - padding.length) + |
| getHexBytes(padding) + |
| "]\n"); |
| */ |
| |
| } else { |
| |
| // Token data is in cleartext |
| // debug("\t\tNo encryption was performed by peer.\n"); |
| System.arraycopy(tokenBytes, dataPos, |
| confounder, 0, CONFOUNDER_SIZE); |
| int padSize = tokenBytes[dataPos + dataSize - 1]; |
| if (padSize < 0) |
| padSize = 0; |
| if (padSize > 8) |
| padSize %= 8; |
| |
| padding = pads[padSize]; |
| // debug("\t\tPadding applied was: " + padSize + "\n"); |
| |
| System.arraycopy(tokenBytes, dataPos + CONFOUNDER_SIZE, |
| dataBuf, dataBufOffset, dataSize - |
| CONFOUNDER_SIZE - padSize); |
| |
| // byte[] debugbuf = new byte[dataSize - CONFOUNDER_SIZE - padSize]; |
| // System.arraycopy(tokenBytes, dataPos + CONFOUNDER_SIZE, |
| // debugbuf, 0, debugbuf.length); |
| // debug("\t\tData is: " + getHexBytes(debugbuf, debugbuf.length)); |
| } |
| |
| /* |
| * Make sure sign and sequence number are not corrupt |
| */ |
| |
| if (!verifySignAndSeqNumber(confounder, |
| dataBuf, dataBufOffset, |
| dataSize - CONFOUNDER_SIZE |
| - padding.length, |
| padding)) |
| throw new GSSException(GSSException.BAD_MIC, -1, |
| "Corrupt checksum or sequence number in Wrap token"); |
| } |
| |
| /** |
| * Helper routine to obtain the application data transmitted in |
| * this WrapToken. It is called if the WrapToken was constructed |
| * with an Inputstream. |
| * @param dataBuf the output buffer into which the data must be |
| * written |
| * @param dataBufOffset the offset at which to write the data |
| * @throws GSSException if an error occurs while decrypting any |
| * cipher text and checking for validity |
| */ |
| private void getDataFromStream(byte[] dataBuf, int dataBufOffset) |
| throws GSSException { |
| |
| GSSHeader gssHeader = getGSSHeader(); |
| |
| // Don't check the token length. Data will be read on demand from |
| // the InputStream. |
| |
| // debug("WrapToken cons: data will be read from InputStream.\n"); |
| |
| confounder = new byte[CONFOUNDER_SIZE]; |
| |
| try { |
| |
| // Do decryption if this token was privacy protected. |
| |
| if (privacy) { |
| cipherHelper.decryptData(this, is, dataSize, |
| dataBuf, dataBufOffset); |
| |
| // debug("\t\tDecrypted data is [" + |
| // getHexBytes(confounder) + " " + |
| // getHexBytes(dataBuf, dataBufOffset, |
| // dataSize - CONFOUNDER_SIZE - padding.length) + |
| // getHexBytes(padding) + |
| // "]\n"); |
| |
| } else { |
| |
| // Token data is in cleartext |
| // debug("\t\tNo encryption was performed by peer.\n"); |
| readFully(is, confounder); |
| |
| // Data is always a multiple of 8 with this GSS Mech |
| // Copy all but last block as they are |
| int numBlocks = (dataSize - CONFOUNDER_SIZE)/8 - 1; |
| int offset = dataBufOffset; |
| for (int i = 0; i < numBlocks; i++) { |
| readFully(is, dataBuf, offset, 8); |
| offset += 8; |
| } |
| |
| byte[] finalBlock = new byte[8]; |
| readFully(is, finalBlock); |
| |
| int padSize = finalBlock[7]; |
| padding = pads[padSize]; |
| |
| // debug("\t\tPadding applied was: " + padSize + "\n"); |
| System.arraycopy(finalBlock, 0, dataBuf, offset, |
| finalBlock.length - padSize); |
| } |
| } catch (IOException e) { |
| throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, |
| getTokenName(getTokenId()) |
| + ": " + e.getMessage()); |
| } |
| |
| /* |
| * Make sure sign and sequence number are not corrupt |
| */ |
| |
| if (!verifySignAndSeqNumber(confounder, |
| dataBuf, dataBufOffset, |
| dataSize - CONFOUNDER_SIZE |
| - padding.length, |
| padding)) |
| throw new GSSException(GSSException.BAD_MIC, -1, |
| "Corrupt checksum or sequence number in Wrap token"); |
| } |
| |
| |
| /** |
| * Helper routine to pick the right padding for a certain length |
| * of application data. Every application message has some |
| * padding between 1 and 8 bytes. |
| * @param len the length of the application data |
| * @return the padding to be applied |
| */ |
| private byte[] getPadding(int len) { |
| int padSize = 0; |
| // For RC4-HMAC, all padding is rounded up to 1 byte. |
| // One byte is needed to say that there is 1 byte of padding. |
| if (cipherHelper.isArcFour()) { |
| padSize = 1; |
| } else { |
| padSize = len % 8; |
| padSize = 8 - padSize; |
| } |
| return pads[padSize]; |
| } |
| |
| public WrapToken(Krb5Context context, MessageProp prop, |
| byte[] dataBytes, int dataOffset, int dataLen) |
| throws GSSException { |
| |
| super(Krb5Token.WRAP_ID, context); |
| |
| confounder = Confounder.bytes(CONFOUNDER_SIZE); |
| |
| padding = getPadding(dataLen); |
| dataSize = confounder.length + dataLen + padding.length; |
| this.dataBytes = dataBytes; |
| this.dataOffset = dataOffset; |
| this.dataLen = dataLen; |
| |
| /* |
| debug("\nWrapToken cons: data to wrap is [" + |
| getHexBytes(confounder) + " " + |
| getHexBytes(dataBytes, dataOffset, dataLen) + " " + |
| // padding is never null for Wrap |
| getHexBytes(padding) + "]\n"); |
| */ |
| |
| genSignAndSeqNumber(prop, |
| confounder, |
| dataBytes, dataOffset, dataLen, |
| padding); |
| |
| /* |
| * If the application decides to ask for privacy when the context |
| * did not negotiate for it, do not provide it. The peer might not |
| * have support for it. The app will realize this with a call to |
| * pop.getPrivacy() after wrap(). |
| */ |
| if (!context.getConfState()) |
| prop.setPrivacy(false); |
| |
| privacy = prop.getPrivacy(); |
| } |
| |
| public void encode(OutputStream os) throws IOException, GSSException { |
| |
| super.encode(os); |
| |
| // debug("Writing data: ["); |
| if (!privacy) { |
| |
| // debug(getHexBytes(confounder, confounder.length)); |
| os.write(confounder); |
| |
| // debug(" " + getHexBytes(dataBytes, dataOffset, dataLen)); |
| os.write(dataBytes, dataOffset, dataLen); |
| |
| // debug(" " + getHexBytes(padding, padding.length)); |
| os.write(padding); |
| |
| } else { |
| |
| cipherHelper.encryptData(this, confounder, |
| dataBytes, dataOffset, dataLen, padding, os); |
| } |
| // debug("]\n"); |
| } |
| |
| public byte[] encode() throws IOException, GSSException { |
| // XXX Fine tune this initial size |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(dataSize + 50); |
| encode(bos); |
| return bos.toByteArray(); |
| } |
| |
| public int encode(byte[] outToken, int offset) |
| throws IOException, GSSException { |
| |
| // Token header is small |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| super.encode(bos); |
| byte[] header = bos.toByteArray(); |
| System.arraycopy(header, 0, outToken, offset, header.length); |
| offset += header.length; |
| |
| // debug("WrapToken.encode: Writing data: ["); |
| if (!privacy) { |
| |
| // debug(getHexBytes(confounder, confounder.length)); |
| System.arraycopy(confounder, 0, outToken, offset, |
| confounder.length); |
| offset += confounder.length; |
| |
| // debug(" " + getHexBytes(dataBytes, dataOffset, dataLen)); |
| System.arraycopy(dataBytes, dataOffset, outToken, offset, |
| dataLen); |
| offset += dataLen; |
| |
| // debug(" " + getHexBytes(padding, padding.length)); |
| System.arraycopy(padding, 0, outToken, offset, padding.length); |
| |
| } else { |
| |
| cipherHelper.encryptData(this, confounder, dataBytes, |
| dataOffset, dataLen, padding, outToken, offset); |
| |
| // debug(getHexBytes(outToken, offset, dataSize)); |
| } |
| |
| // debug("]\n"); |
| |
| // %%% assume that plaintext length == ciphertext len |
| return (header.length + confounder.length + dataLen + padding.length); |
| |
| } |
| |
| protected int getKrb5TokenSize() throws GSSException { |
| return (getTokenSize() + dataSize); |
| } |
| |
| protected int getSealAlg(boolean conf, int qop) throws GSSException { |
| if (!conf) { |
| return SEAL_ALG_NONE; |
| } |
| |
| // ignore QOP |
| return cipherHelper.getSealAlg(); |
| } |
| |
| // This implementation is way too conservative. And it certainly |
| // doesn't return the maximum limit. |
| static int getSizeLimit(int qop, boolean confReq, int maxTokenSize, |
| CipherHelper ch) throws GSSException { |
| return (GSSHeader.getMaxMechTokenSize(OID, maxTokenSize) - |
| (getTokenSize(ch) + CONFOUNDER_SIZE) - 8); /* safety */ |
| } |
| |
| } |