/* |
|
* Copyright (c) 2000, 2010, 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 |
|
* 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); |
|
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 = 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 */ |
|
} |
|
} |