| /* | |
| * Copyright (c) 2000, 2022, 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.io.InputStream; | |
| import java.io.OutputStream; | |
| import java.io.IOException; | |
| import java.io.ByteArrayOutputStream; | |
| import sun.security.krb5.Confounder; | |
| /** | |
| * 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 | |
| * its 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 final int dataSize; | |
| // Accessed by CipherHelper | |
| byte[] confounder = null; | |
| byte[] padding = null; | |
| private final boolean privacy; | |
| /** | |
| * 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 { | |
| // 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); | |
| if (cipherHelper.isArcFour()) { | |
| padding = pads[1]; | |
| readFully(is, dataBuf, dataBufOffset, dataSize-CONFOUNDER_SIZE-1); | |
| } else { | |
| // 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; | |
| // 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 */ | |
| } | |
| } |