| /* |
| * Copyright (c) 2004, 2011, 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 javax.crypto.Cipher; |
| import javax.crypto.SecretKey; |
| import javax.crypto.spec.IvParameterSpec; |
| import javax.crypto.spec.SecretKeySpec; |
| import javax.crypto.CipherInputStream; |
| import javax.crypto.CipherOutputStream; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.IOException; |
| import org.ietf.jgss.*; |
| |
| import java.security.MessageDigest; |
| import java.security.GeneralSecurityException; |
| import java.security.NoSuchAlgorithmException; |
| import sun.security.krb5.*; |
| import sun.security.krb5.internal.crypto.Des3; |
| import sun.security.krb5.internal.crypto.Aes128; |
| import sun.security.krb5.internal.crypto.Aes256; |
| import sun.security.krb5.internal.crypto.ArcFourHmac; |
| |
| class CipherHelper { |
| |
| // From draft-raeburn-cat-gssapi-krb5-3des-00 |
| // Key usage values when deriving keys |
| private static final int KG_USAGE_SEAL = 22; |
| private static final int KG_USAGE_SIGN = 23; |
| private static final int KG_USAGE_SEQ = 24; |
| |
| private static final int DES_CHECKSUM_SIZE = 8; |
| private static final int DES_IV_SIZE = 8; |
| private static final int AES_IV_SIZE = 16; |
| |
| // ARCFOUR-HMAC |
| // Save first 8 octets of HMAC Sgn_Cksum |
| private static final int HMAC_CHECKSUM_SIZE = 8; |
| // key usage for MIC tokens used by MS |
| private static final int KG_USAGE_SIGN_MS = 15; |
| |
| // debug flag |
| private static final boolean DEBUG = Krb5Util.DEBUG; |
| |
| /** |
| * A zero initial vector to be used for checksum calculation and for |
| * DesCbc application data encryption/decryption. |
| */ |
| private static final byte[] ZERO_IV = new byte[DES_IV_SIZE]; |
| private static final byte[] ZERO_IV_AES = new byte[AES_IV_SIZE]; |
| |
| private int etype; |
| private int sgnAlg, sealAlg; |
| private byte[] keybytes; |
| |
| // new token format from draft-ietf-krb-wg-gssapi-cfx-07 |
| // proto is used to determine new GSS token format for "newer" etypes |
| private int proto = 0; |
| |
| CipherHelper(EncryptionKey key) throws GSSException { |
| etype = key.getEType(); |
| keybytes = key.getBytes(); |
| |
| switch (etype) { |
| case EncryptedData.ETYPE_DES_CBC_CRC: |
| case EncryptedData.ETYPE_DES_CBC_MD5: |
| sgnAlg = MessageToken.SGN_ALG_DES_MAC_MD5; |
| sealAlg = MessageToken.SEAL_ALG_DES; |
| break; |
| |
| case EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD: |
| sgnAlg = MessageToken.SGN_ALG_HMAC_SHA1_DES3_KD; |
| sealAlg = MessageToken.SEAL_ALG_DES3_KD; |
| break; |
| |
| case EncryptedData.ETYPE_ARCFOUR_HMAC: |
| sgnAlg = MessageToken.SGN_ALG_HMAC_MD5_ARCFOUR; |
| sealAlg = MessageToken.SEAL_ALG_ARCFOUR_HMAC; |
| break; |
| |
| case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: |
| case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: |
| sgnAlg = -1; |
| sealAlg = -1; |
| proto = 1; |
| break; |
| |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported encryption type: " + etype); |
| } |
| } |
| |
| int getSgnAlg() { |
| return sgnAlg; |
| } |
| |
| int getSealAlg() { |
| return sealAlg; |
| } |
| |
| int getProto() { |
| return proto; |
| } |
| |
| int getEType() { |
| return etype; |
| } |
| |
| boolean isArcFour() { |
| boolean flag = false; |
| if (etype == EncryptedData.ETYPE_ARCFOUR_HMAC) { |
| flag = true; |
| } |
| return flag; |
| } |
| |
| @SuppressWarnings("fallthrough") |
| byte[] calculateChecksum(int alg, byte[] header, byte[] trailer, |
| byte[] data, int start, int len, int tokenId) throws GSSException { |
| |
| switch (alg) { |
| case MessageToken.SGN_ALG_DES_MAC_MD5: |
| /* |
| * With this sign algorithm, first an MD5 hash is computed on the |
| * application data. The 16 byte hash is then DesCbc encrypted. |
| */ |
| try { |
| MessageDigest md5 = MessageDigest.getInstance("MD5"); |
| |
| // debug("\t\tdata=["); |
| |
| // debug(getHexBytes(checksumDataHeader, |
| // checksumDataHeader.length) + " "); |
| md5.update(header); |
| |
| // debug(getHexBytes(data, start, len)); |
| md5.update(data, start, len); |
| |
| if (trailer != null) { |
| // debug(" " + |
| // getHexBytes(trailer, |
| // optionalTrailer.length)); |
| md5.update(trailer); |
| } |
| // debug("]\n"); |
| |
| data = md5.digest(); |
| start = 0; |
| len = data.length; |
| // System.out.println("\tMD5 Checksum is [" + |
| // getHexBytes(data) + "]\n"); |
| header = null; |
| trailer = null; |
| } catch (NoSuchAlgorithmException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not get MD5 Message Digest - " + e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| // fall through to encrypt checksum |
| |
| case MessageToken.SGN_ALG_DES_MAC: |
| return getDesCbcChecksum(keybytes, header, data, start, len); |
| |
| case MessageToken.SGN_ALG_HMAC_SHA1_DES3_KD: |
| byte[] buf; |
| int offset, total; |
| if (header == null && trailer == null) { |
| buf = data; |
| total = len; |
| offset = start; |
| } else { |
| total = ((header != null ? header.length : 0) + len + |
| (trailer != null ? trailer.length : 0)); |
| |
| buf = new byte[total]; |
| int pos = 0; |
| if (header != null) { |
| System.arraycopy(header, 0, buf, 0, header.length); |
| pos = header.length; |
| } |
| System.arraycopy(data, start, buf, pos, len); |
| pos += len; |
| if (trailer != null) { |
| System.arraycopy(trailer, 0, buf, pos, trailer.length); |
| } |
| |
| offset = 0; |
| } |
| |
| try { |
| |
| /* |
| Krb5Token.debug("\nkeybytes: " + |
| Krb5Token.getHexBytes(keybytes)); |
| Krb5Token.debug("\nheader: " + (header == null ? "NONE" : |
| Krb5Token.getHexBytes(header))); |
| Krb5Token.debug("\ntrailer: " + (trailer == null ? "NONE" : |
| Krb5Token.getHexBytes(trailer))); |
| Krb5Token.debug("\ndata: " + |
| Krb5Token.getHexBytes(data, start, len)); |
| Krb5Token.debug("\nbuf: " + Krb5Token.getHexBytes(buf, offset, |
| total)); |
| */ |
| |
| byte[] answer = Des3.calculateChecksum(keybytes, |
| KG_USAGE_SIGN, buf, offset, total); |
| // Krb5Token.debug("\nanswer: " + |
| // Krb5Token.getHexBytes(answer)); |
| return answer; |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use HMAC-SHA1-DES3-KD signing algorithm - " + |
| e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| case MessageToken.SGN_ALG_HMAC_MD5_ARCFOUR: |
| byte[] buffer; |
| int off, tot; |
| if (header == null && trailer == null) { |
| buffer = data; |
| tot = len; |
| off = start; |
| } else { |
| tot = ((header != null ? header.length : 0) + len + |
| (trailer != null ? trailer.length : 0)); |
| |
| buffer = new byte[tot]; |
| int pos = 0; |
| |
| if (header != null) { |
| System.arraycopy(header, 0, buffer, 0, header.length); |
| pos = header.length; |
| } |
| System.arraycopy(data, start, buffer, pos, len); |
| pos += len; |
| if (trailer != null) { |
| System.arraycopy(trailer, 0, buffer, pos, trailer.length); |
| } |
| |
| off = 0; |
| } |
| |
| try { |
| |
| /* |
| Krb5Token.debug("\nkeybytes: " + |
| Krb5Token.getHexBytes(keybytes)); |
| Krb5Token.debug("\nheader: " + (header == null ? "NONE" : |
| Krb5Token.getHexBytes(header))); |
| Krb5Token.debug("\ntrailer: " + (trailer == null ? "NONE" : |
| Krb5Token.getHexBytes(trailer))); |
| Krb5Token.debug("\ndata: " + |
| Krb5Token.getHexBytes(data, start, len)); |
| Krb5Token.debug("\nbuffer: " + |
| Krb5Token.getHexBytes(buffer, off, tot)); |
| */ |
| |
| // for MIC tokens, key derivation salt is 15 |
| // NOTE: Required for interoperability. The RC4-HMAC spec |
| // defines key_usage of 23, however all Kerberos impl. |
| // MS/Solaris/MIT all use key_usage of 15 for MIC tokens |
| int key_usage = KG_USAGE_SIGN; |
| if (tokenId == Krb5Token.MIC_ID) { |
| key_usage = KG_USAGE_SIGN_MS; |
| } |
| byte[] answer = ArcFourHmac.calculateChecksum(keybytes, |
| key_usage, buffer, off, tot); |
| // Krb5Token.debug("\nanswer: " + |
| // Krb5Token.getHexBytes(answer)); |
| |
| // Save first 8 octets of HMAC Sgn_Cksum |
| byte[] output = new byte[getChecksumLength()]; |
| System.arraycopy(answer, 0, output, 0, output.length); |
| // Krb5Token.debug("\nanswer (trimmed): " + |
| // Krb5Token.getHexBytes(output)); |
| return output; |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use HMAC_MD5_ARCFOUR signing algorithm - " + |
| e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported signing algorithm: " + sgnAlg); |
| } |
| } |
| |
| // calculate Checksum for the new GSS tokens |
| byte[] calculateChecksum(byte[] header, byte[] data, int start, int len, |
| int key_usage) throws GSSException { |
| |
| // total length |
| int total = ((header != null ? header.length : 0) + len); |
| |
| // get_mic("plaintext-data" | "header") |
| byte[] buf = new byte[total]; |
| |
| // data |
| System.arraycopy(data, start, buf, 0, len); |
| |
| // token header |
| if (header != null) { |
| System.arraycopy(header, 0, buf, len, header.length); |
| } |
| |
| // Krb5Token.debug("\nAES calculate checksum on: " + |
| // Krb5Token.getHexBytes(buf)); |
| switch (etype) { |
| case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: |
| try { |
| byte[] answer = Aes128.calculateChecksum(keybytes, key_usage, |
| buf, 0, total); |
| // Krb5Token.debug("\nAES128 checksum: " + |
| // Krb5Token.getHexBytes(answer)); |
| return answer; |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use AES128 signing algorithm - " + |
| e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: |
| try { |
| byte[] answer = Aes256.calculateChecksum(keybytes, key_usage, |
| buf, 0, total); |
| // Krb5Token.debug("\nAES256 checksum: " + |
| // Krb5Token.getHexBytes(answer)); |
| return answer; |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use AES256 signing algorithm - " + |
| e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported encryption type: " + etype); |
| } |
| } |
| |
| byte[] encryptSeq(byte[] ivec, byte[] plaintext, int start, int len) |
| throws GSSException { |
| |
| switch (sgnAlg) { |
| case MessageToken.SGN_ALG_DES_MAC_MD5: |
| case MessageToken.SGN_ALG_DES_MAC: |
| try { |
| Cipher des = getInitializedDes(true, keybytes, ivec); |
| return des.doFinal(plaintext, start, len); |
| |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not encrypt sequence number using DES - " + |
| e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| case MessageToken.SGN_ALG_HMAC_SHA1_DES3_KD: |
| byte[] iv; |
| if (ivec.length == DES_IV_SIZE) { |
| iv = ivec; |
| } else { |
| iv = new byte[DES_IV_SIZE]; |
| System.arraycopy(ivec, 0, iv, 0, DES_IV_SIZE); |
| } |
| try { |
| return Des3.encryptRaw(keybytes, KG_USAGE_SEQ, iv, |
| plaintext, start, len); |
| } catch (Exception e) { |
| // GeneralSecurityException, KrbCryptoException |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not encrypt sequence number using DES3-KD - " + |
| e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| case MessageToken.SGN_ALG_HMAC_MD5_ARCFOUR: |
| // ivec passed is the checksum |
| byte[] checksum; |
| if (ivec.length == HMAC_CHECKSUM_SIZE) { |
| checksum = ivec; |
| } else { |
| checksum = new byte[HMAC_CHECKSUM_SIZE]; |
| System.arraycopy(ivec, 0, checksum, 0, HMAC_CHECKSUM_SIZE); |
| } |
| |
| try { |
| return ArcFourHmac.encryptSeq(keybytes, KG_USAGE_SEQ, checksum, |
| plaintext, start, len); |
| } catch (Exception e) { |
| // GeneralSecurityException, KrbCryptoException |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not encrypt sequence number using RC4-HMAC - " + |
| e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported signing algorithm: " + sgnAlg); |
| } |
| } |
| |
| byte[] decryptSeq(byte[] ivec, byte[] ciphertext, int start, int len) |
| throws GSSException { |
| |
| switch (sgnAlg) { |
| case MessageToken.SGN_ALG_DES_MAC_MD5: |
| case MessageToken.SGN_ALG_DES_MAC: |
| try { |
| Cipher des = getInitializedDes(false, keybytes, ivec); |
| return des.doFinal(ciphertext, start, len); |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not decrypt sequence number using DES - " + |
| e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| case MessageToken.SGN_ALG_HMAC_SHA1_DES3_KD: |
| byte[] iv; |
| if (ivec.length == DES_IV_SIZE) { |
| iv = ivec; |
| } else { |
| iv = new byte[8]; |
| System.arraycopy(ivec, 0, iv, 0, DES_IV_SIZE); |
| } |
| |
| try { |
| return Des3.decryptRaw(keybytes, KG_USAGE_SEQ, iv, |
| ciphertext, start, len); |
| } catch (Exception e) { |
| // GeneralSecurityException, KrbCryptoException |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not decrypt sequence number using DES3-KD - " + |
| e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| case MessageToken.SGN_ALG_HMAC_MD5_ARCFOUR: |
| // ivec passed is the checksum |
| byte[] checksum; |
| if (ivec.length == HMAC_CHECKSUM_SIZE) { |
| checksum = ivec; |
| } else { |
| checksum = new byte[HMAC_CHECKSUM_SIZE]; |
| System.arraycopy(ivec, 0, checksum, 0, HMAC_CHECKSUM_SIZE); |
| } |
| |
| try { |
| return ArcFourHmac.decryptSeq(keybytes, KG_USAGE_SEQ, checksum, |
| ciphertext, start, len); |
| } catch (Exception e) { |
| // GeneralSecurityException, KrbCryptoException |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not decrypt sequence number using RC4-HMAC - " + |
| e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported signing algorithm: " + sgnAlg); |
| } |
| } |
| |
| int getChecksumLength() throws GSSException { |
| switch (etype) { |
| case EncryptedData.ETYPE_DES_CBC_CRC: |
| case EncryptedData.ETYPE_DES_CBC_MD5: |
| return DES_CHECKSUM_SIZE; |
| |
| case EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD: |
| return Des3.getChecksumLength(); |
| |
| case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: |
| return Aes128.getChecksumLength(); |
| case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: |
| return Aes256.getChecksumLength(); |
| |
| case EncryptedData.ETYPE_ARCFOUR_HMAC: |
| // only first 8 octets of HMAC Sgn_Cksum are used |
| return HMAC_CHECKSUM_SIZE; |
| |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported encryption type: " + etype); |
| } |
| } |
| |
| void decryptData(WrapToken token, byte[] ciphertext, int cStart, int cLen, |
| byte[] plaintext, int pStart) throws GSSException { |
| |
| /* |
| Krb5Token.debug("decryptData : ciphertext = " + |
| Krb5Token.getHexBytes(ciphertext)); |
| */ |
| |
| switch (sealAlg) { |
| case MessageToken.SEAL_ALG_DES: |
| desCbcDecrypt(token, getDesEncryptionKey(keybytes), |
| ciphertext, cStart, cLen, plaintext, pStart); |
| break; |
| |
| case MessageToken.SEAL_ALG_DES3_KD: |
| des3KdDecrypt(token, ciphertext, cStart, cLen, plaintext, pStart); |
| break; |
| |
| case MessageToken.SEAL_ALG_ARCFOUR_HMAC: |
| arcFourDecrypt(token, ciphertext, cStart, cLen, plaintext, pStart); |
| break; |
| |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported seal algorithm: " + sealAlg); |
| } |
| } |
| |
| // decrypt data in the new GSS tokens |
| void decryptData(WrapToken_v2 token, byte[] ciphertext, int cStart, |
| int cLen, byte[] plaintext, int pStart, int key_usage) |
| throws GSSException { |
| |
| /* |
| Krb5Token.debug("decryptData : ciphertext = " + |
| Krb5Token.getHexBytes(ciphertext)); |
| */ |
| |
| switch (etype) { |
| case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: |
| aes128Decrypt(token, ciphertext, cStart, cLen, |
| plaintext, pStart, key_usage); |
| break; |
| case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: |
| aes256Decrypt(token, ciphertext, cStart, cLen, |
| plaintext, pStart, key_usage); |
| break; |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported etype: " + etype); |
| } |
| } |
| |
| void decryptData(WrapToken token, InputStream cipherStream, int cLen, |
| byte[] plaintext, int pStart) |
| throws GSSException, IOException { |
| |
| switch (sealAlg) { |
| case MessageToken.SEAL_ALG_DES: |
| desCbcDecrypt(token, getDesEncryptionKey(keybytes), |
| cipherStream, cLen, plaintext, pStart); |
| break; |
| |
| case MessageToken.SEAL_ALG_DES3_KD: |
| |
| // Read encrypted data from stream |
| byte[] ciphertext = new byte[cLen]; |
| try { |
| Krb5Token.readFully(cipherStream, ciphertext, 0, cLen); |
| } catch (IOException e) { |
| GSSException ge = new GSSException( |
| GSSException.DEFECTIVE_TOKEN, -1, |
| "Cannot read complete token"); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| des3KdDecrypt(token, ciphertext, 0, cLen, plaintext, pStart); |
| break; |
| |
| case MessageToken.SEAL_ALG_ARCFOUR_HMAC: |
| |
| // Read encrypted data from stream |
| byte[] ctext = new byte[cLen]; |
| try { |
| Krb5Token.readFully(cipherStream, ctext, 0, cLen); |
| } catch (IOException e) { |
| GSSException ge = new GSSException( |
| GSSException.DEFECTIVE_TOKEN, -1, |
| "Cannot read complete token"); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| arcFourDecrypt(token, ctext, 0, cLen, plaintext, pStart); |
| break; |
| |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported seal algorithm: " + sealAlg); |
| } |
| } |
| |
| void decryptData(WrapToken_v2 token, InputStream cipherStream, int cLen, |
| byte[] plaintext, int pStart, int key_usage) |
| throws GSSException, IOException { |
| |
| // Read encrypted data from stream |
| byte[] ciphertext = new byte[cLen]; |
| try { |
| Krb5Token.readFully(cipherStream, ciphertext, 0, cLen); |
| } catch (IOException e) { |
| GSSException ge = new GSSException( |
| GSSException.DEFECTIVE_TOKEN, -1, |
| "Cannot read complete token"); |
| ge.initCause(e); |
| throw ge; |
| } |
| switch (etype) { |
| case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: |
| aes128Decrypt(token, ciphertext, 0, cLen, |
| plaintext, pStart, key_usage); |
| break; |
| case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: |
| aes256Decrypt(token, ciphertext, 0, cLen, |
| plaintext, pStart, key_usage); |
| break; |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported etype: " + etype); |
| } |
| } |
| |
| void encryptData(WrapToken token, byte[] confounder, byte[] plaintext, |
| int start, int len, byte[] padding, OutputStream os) |
| throws GSSException, IOException { |
| |
| switch (sealAlg) { |
| case MessageToken.SEAL_ALG_DES: |
| // Encrypt on the fly and write |
| Cipher des = getInitializedDes(true, getDesEncryptionKey(keybytes), |
| ZERO_IV); |
| CipherOutputStream cos = new CipherOutputStream(os, des); |
| // debug(getHexBytes(confounder, confounder.length)); |
| cos.write(confounder); |
| // debug(" " + getHexBytes(plaintext, start, len)); |
| cos.write(plaintext, start, len); |
| // debug(" " + getHexBytes(padding, padding.length)); |
| cos.write(padding); |
| break; |
| |
| case MessageToken.SEAL_ALG_DES3_KD: |
| byte[] ctext = des3KdEncrypt(confounder, plaintext, start, len, |
| padding); |
| |
| // Write to stream |
| os.write(ctext); |
| break; |
| |
| case MessageToken.SEAL_ALG_ARCFOUR_HMAC: |
| byte[] ciphertext = arcFourEncrypt(token, confounder, plaintext, |
| start, len, padding); |
| |
| // Write to stream |
| os.write(ciphertext); |
| break; |
| |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported seal algorithm: " + sealAlg); |
| } |
| } |
| |
| /* |
| * Encrypt data in the new GSS tokens |
| * |
| * Wrap Tokens (with confidentiality) |
| * { Encrypt(16-byte confounder | plaintext | 16-byte token_header) | |
| * 12-byte HMAC } |
| * where HMAC is on {16-byte confounder | plaintext | 16-byte token_header} |
| * HMAC is not encrypted; it is appended at the end. |
| */ |
| byte[] encryptData(WrapToken_v2 token, byte[] confounder, byte[] tokenHeader, |
| byte[] plaintext, int start, int len, int key_usage) |
| throws GSSException { |
| |
| switch (etype) { |
| case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: |
| return aes128Encrypt(confounder, tokenHeader, |
| plaintext, start, len, key_usage); |
| case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: |
| return aes256Encrypt(confounder, tokenHeader, |
| plaintext, start, len, key_usage); |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported etype: " + etype); |
| } |
| } |
| |
| void encryptData(WrapToken token, byte[] confounder, byte[] plaintext, |
| int pStart, int pLen, byte[] padding, byte[] ciphertext, int cStart) |
| throws GSSException { |
| |
| switch (sealAlg) { |
| case MessageToken.SEAL_ALG_DES: |
| int pos = cStart; |
| // Encrypt and write |
| Cipher des = getInitializedDes(true, getDesEncryptionKey(keybytes), |
| ZERO_IV); |
| try { |
| // debug(getHexBytes(confounder, confounder.length)); |
| pos += des.update(confounder, 0, confounder.length, |
| ciphertext, pos); |
| // debug(" " + getHexBytes(dataBytes, dataOffset, dataLen)); |
| pos += des.update(plaintext, pStart, pLen, |
| ciphertext, pos); |
| // debug(" " + getHexBytes(padding, padding.length)); |
| des.update(padding, 0, padding.length, |
| ciphertext, pos); |
| des.doFinal(); |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use DES Cipher - " + e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| break; |
| |
| case MessageToken.SEAL_ALG_DES3_KD: |
| byte[] ctext = des3KdEncrypt(confounder, plaintext, pStart, pLen, |
| padding); |
| System.arraycopy(ctext, 0, ciphertext, cStart, ctext.length); |
| break; |
| |
| case MessageToken.SEAL_ALG_ARCFOUR_HMAC: |
| byte[] ctext2 = arcFourEncrypt(token, confounder, plaintext, pStart, |
| pLen, padding); |
| System.arraycopy(ctext2, 0, ciphertext, cStart, ctext2.length); |
| break; |
| |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported seal algorithm: " + sealAlg); |
| } |
| } |
| |
| /* |
| * Encrypt data in the new GSS tokens |
| * |
| * Wrap Tokens (with confidentiality) |
| * { Encrypt(16-byte confounder | plaintext | 16-byte token_header) | |
| * 12-byte HMAC } |
| * where HMAC is on {16-byte confounder | plaintext | 16-byte token_header} |
| * HMAC is not encrypted; it is appended at the end. |
| */ |
| int encryptData(WrapToken_v2 token, byte[] confounder, byte[] tokenHeader, |
| byte[] plaintext, int pStart, int pLen, byte[] ciphertext, int cStart, |
| int key_usage) throws GSSException { |
| |
| byte[] ctext = null; |
| switch (etype) { |
| case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: |
| ctext = aes128Encrypt(confounder, tokenHeader, |
| plaintext, pStart, pLen, key_usage); |
| break; |
| case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: |
| ctext = aes256Encrypt(confounder, tokenHeader, |
| plaintext, pStart, pLen, key_usage); |
| break; |
| default: |
| throw new GSSException(GSSException.FAILURE, -1, |
| "Unsupported etype: " + etype); |
| } |
| System.arraycopy(ctext, 0, ciphertext, cStart, ctext.length); |
| return ctext.length; |
| } |
| |
| // --------------------- DES methods |
| |
| /** |
| * Computes the DesCbc checksum based on the algorithm published in FIPS |
| * Publication 113. This involves applying padding to the data passed |
| * in, then performing DesCbc encryption on the data with a zero initial |
| * vector, and finally returning the last 8 bytes of the encryption |
| * result. |
| * |
| * @param key the bytes for the DES key |
| * @param header a header to process first before the data is. |
| * @param data the data to checksum |
| * @param offset the offset where the data begins |
| * @param len the length of the data |
| * @throws GSSException when an error occuse in the encryption |
| */ |
| private byte[] getDesCbcChecksum(byte key[], |
| byte[] header, |
| byte[] data, int offset, int len) |
| throws GSSException { |
| |
| Cipher des = getInitializedDes(true, key, ZERO_IV); |
| |
| int blockSize = des.getBlockSize(); |
| |
| /* |
| * Here the data need not be a multiple of the blocksize |
| * (8). Encrypt and throw away results for all blocks except for |
| * the very last block. |
| */ |
| |
| byte[] finalBlock = new byte[blockSize]; |
| |
| int numBlocks = len / blockSize; |
| int lastBytes = len % blockSize; |
| if (lastBytes == 0) { |
| // No need for padding. Save last block from application data |
| numBlocks -= 1; |
| System.arraycopy(data, offset + numBlocks*blockSize, |
| finalBlock, 0, blockSize); |
| } else { |
| System.arraycopy(data, offset + numBlocks*blockSize, |
| finalBlock, 0, lastBytes); |
| // Zero padding automatically done |
| } |
| |
| try { |
| byte[] temp = new byte[Math.max(blockSize, |
| (header == null? blockSize : header.length))]; |
| |
| if (header != null) { |
| // header will be null when doing DES-MD5 Checksum |
| des.update(header, 0, header.length, temp, 0); |
| } |
| |
| // Iterate over all but the last block |
| for (int i = 0; i < numBlocks; i++) { |
| des.update(data, offset, blockSize, |
| temp, 0); |
| offset += blockSize; |
| } |
| |
| // Now process the final block |
| byte[] retVal = new byte[blockSize]; |
| des.update(finalBlock, 0, blockSize, retVal, 0); |
| des.doFinal(); |
| |
| return retVal; |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use DES Cipher - " + e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| } |
| |
| /** |
| * Obtains an initialized DES cipher. |
| * |
| * @param encryptMode true if encryption is desired, false is decryption |
| * is desired. |
| * @param key the bytes for the DES key |
| * @param ivBytes the initial vector bytes |
| */ |
| private final Cipher getInitializedDes(boolean encryptMode, byte[] key, |
| byte[] ivBytes) |
| throws GSSException { |
| |
| |
| try { |
| IvParameterSpec iv = new IvParameterSpec(ivBytes); |
| SecretKey jceKey = (SecretKey) (new SecretKeySpec(key, "DES")); |
| |
| Cipher desCipher = Cipher.getInstance("DES/CBC/NoPadding"); |
| desCipher.init( |
| (encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE), |
| jceKey, iv); |
| return desCipher; |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| } |
| |
| /** |
| * Helper routine to decrypt fromm a byte array and write the |
| * application data straight to an output array with minimal |
| * buffer copies. The confounder and the padding are stored |
| * separately and not copied into this output array. |
| * @param key the DES key to use |
| * @param cipherText the encrypted data |
| * @param offset the offset for the encrypted data |
| * @param len the length of the encrypted data |
| * @param dataOutBuf the output buffer where the application data |
| * should be writte |
| * @param dataOffset the offser where the application data should |
| * be written. |
| * @throws GSSException is an error occurs while decrypting the |
| * data |
| */ |
| private void desCbcDecrypt(WrapToken token, byte[] key, byte[] cipherText, |
| int offset, int len, byte[] dataOutBuf, int dataOffset) |
| throws GSSException { |
| |
| try { |
| |
| int temp = 0; |
| |
| Cipher des = getInitializedDes(false, key, ZERO_IV); |
| |
| /* |
| * Remove the counfounder first. |
| * CONFOUNDER_SIZE is one DES block ie 8 bytes. |
| */ |
| temp = des.update(cipherText, offset, WrapToken.CONFOUNDER_SIZE, |
| token.confounder); |
| // temp should be CONFOUNDER_SIZE |
| // debug("\n\ttemp is " + temp + " and CONFOUNDER_SIZE is " |
| // + CONFOUNDER_SIZE); |
| |
| offset += WrapToken.CONFOUNDER_SIZE; |
| len -= WrapToken.CONFOUNDER_SIZE; |
| |
| /* |
| * len is a multiple of 8 due to padding. |
| * Decrypt all blocks directly into the output buffer except for |
| * the very last block. Remove the trailing padding bytes from the |
| * very last block and copy that into the output buffer. |
| */ |
| |
| int blockSize = des.getBlockSize(); |
| int numBlocks = len / blockSize - 1; |
| |
| // Iterate over all but the last block |
| for (int i = 0; i < numBlocks; i++) { |
| temp = des.update(cipherText, offset, blockSize, |
| dataOutBuf, dataOffset); |
| // temp should be blockSize |
| // debug("\n\ttemp is " + temp + " and blockSize is " |
| // + blockSize); |
| |
| offset += blockSize; |
| dataOffset += blockSize; |
| } |
| |
| // Now process the last block |
| byte[] finalBlock = new byte[blockSize]; |
| des.update(cipherText, offset, blockSize, finalBlock); |
| |
| des.doFinal(); |
| |
| /* |
| * There is always at least one padding byte. The padding bytes |
| * are all the value of the number of padding bytes. |
| */ |
| |
| int padSize = finalBlock[blockSize - 1]; |
| if (padSize < 1 || padSize > 8) |
| throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, |
| "Invalid padding on Wrap Token"); |
| token.padding = WrapToken.pads[padSize]; |
| blockSize -= padSize; |
| |
| // Copy this last block into the output buffer |
| System.arraycopy(finalBlock, 0, dataOutBuf, dataOffset, |
| blockSize); |
| |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use DES cipher - " + e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| } |
| |
| /** |
| * Helper routine to decrypt from an InputStream and write the |
| * application data straight to an output array with minimal |
| * buffer copies. The confounder and the padding are stored |
| * separately and not copied into this output array. |
| * @param key the DES key to use |
| * @param is the InputStream from which the cipher text should be |
| * read |
| * @param len the length of the ciphertext data |
| * @param dataOutBuf the output buffer where the application data |
| * should be writte |
| * @param dataOffset the offser where the application data should |
| * be written. |
| * @throws GSSException is an error occurs while decrypting the |
| * data |
| */ |
| private void desCbcDecrypt(WrapToken token, byte[] key, |
| InputStream is, int len, byte[] dataOutBuf, int dataOffset) |
| throws GSSException, IOException { |
| |
| int temp = 0; |
| |
| Cipher des = getInitializedDes(false, key, ZERO_IV); |
| |
| WrapTokenInputStream truncatedInputStream = |
| new WrapTokenInputStream(is, len); |
| CipherInputStream cis = new CipherInputStream(truncatedInputStream, |
| des); |
| /* |
| * Remove the counfounder first. |
| * CONFOUNDER_SIZE is one DES block ie 8 bytes. |
| */ |
| temp = cis.read(token.confounder); |
| |
| len -= temp; |
| // temp should be CONFOUNDER_SIZE |
| // debug("Got " + temp + " bytes; CONFOUNDER_SIZE is " |
| // + CONFOUNDER_SIZE + "\n"); |
| // debug("Confounder is " + getHexBytes(confounder) + "\n"); |
| |
| |
| /* |
| * len is a multiple of 8 due to padding. |
| * Decrypt all blocks directly into the output buffer except for |
| * the very last block. Remove the trailing padding bytes from the |
| * very last block and copy that into the output buffer. |
| */ |
| |
| int blockSize = des.getBlockSize(); |
| int numBlocks = len / blockSize - 1; |
| |
| // Iterate over all but the last block |
| for (int i = 0; i < numBlocks; i++) { |
| // debug("dataOffset is " + dataOffset + "\n"); |
| temp = cis.read(dataOutBuf, dataOffset, blockSize); |
| |
| // temp should be blockSize |
| // debug("Got " + temp + " bytes and blockSize is " |
| // + blockSize + "\n"); |
| // debug("Bytes are: " |
| // + getHexBytes(dataOutBuf, dataOffset, temp) + "\n"); |
| dataOffset += blockSize; |
| } |
| |
| // Now process the last block |
| byte[] finalBlock = new byte[blockSize]; |
| // debug("Will call read on finalBlock" + "\n"); |
| temp = cis.read(finalBlock); |
| // temp should be blockSize |
| /* |
| debug("Got " + temp + " bytes and blockSize is " |
| + blockSize + "\n"); |
| debug("Bytes are: " |
| + getHexBytes(finalBlock, 0, temp) + "\n"); |
| debug("Will call doFinal" + "\n"); |
| */ |
| try { |
| des.doFinal(); |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use DES cipher - " + e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| /* |
| * There is always at least one padding byte. The padding bytes |
| * are all the value of the number of padding bytes. |
| */ |
| |
| int padSize = finalBlock[blockSize - 1]; |
| if (padSize < 1 || padSize > 8) |
| throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, |
| "Invalid padding on Wrap Token"); |
| token.padding = WrapToken.pads[padSize]; |
| blockSize -= padSize; |
| |
| // Copy this last block into the output buffer |
| System.arraycopy(finalBlock, 0, dataOutBuf, dataOffset, |
| blockSize); |
| } |
| |
| private static byte[] getDesEncryptionKey(byte[] key) |
| throws GSSException { |
| |
| /* |
| * To meet export control requirements, double check that the |
| * key being used is no longer than 64 bits. |
| * |
| * Note that from a protocol point of view, an |
| * algorithm that is not DES will be rejected before this |
| * point. Also, a DES key that is not 64 bits will be |
| * rejected by a good JCE provider. |
| */ |
| if (key.length > 8) |
| throw new GSSException(GSSException.FAILURE, -100, |
| "Invalid DES Key!"); |
| |
| byte[] retVal = new byte[key.length]; |
| for (int i = 0; i < key.length; i++) |
| retVal[i] = (byte)(key[i] ^ 0xf0); // RFC 1964, Section 1.2.2 |
| return retVal; |
| } |
| |
| // ---- DES3-KD methods |
| private void des3KdDecrypt(WrapToken token, byte[] ciphertext, |
| int cStart, int cLen, byte[] plaintext, int pStart) |
| throws GSSException { |
| byte[] ptext; |
| try { |
| ptext = Des3.decryptRaw(keybytes, KG_USAGE_SEAL, ZERO_IV, |
| ciphertext, cStart, cLen); |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use DES3-KD Cipher - " + e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| /* |
| Krb5Token.debug("\ndes3KdDecrypt in: " + |
| Krb5Token.getHexBytes(ciphertext, cStart, cLen)); |
| Krb5Token.debug("\ndes3KdDecrypt plain: " + |
| Krb5Token.getHexBytes(ptext)); |
| */ |
| |
| // Strip out confounder and padding |
| /* |
| * There is always at least one padding byte. The padding bytes |
| * are all the value of the number of padding bytes. |
| */ |
| int padSize = ptext[ptext.length - 1]; |
| if (padSize < 1 || padSize > 8) |
| throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, |
| "Invalid padding on Wrap Token"); |
| |
| token.padding = WrapToken.pads[padSize]; |
| int len = ptext.length - WrapToken.CONFOUNDER_SIZE - padSize; |
| |
| System.arraycopy(ptext, WrapToken.CONFOUNDER_SIZE, |
| plaintext, pStart, len); |
| |
| // Needed to calculate checksum |
| System.arraycopy(ptext, 0, token.confounder, |
| 0, WrapToken.CONFOUNDER_SIZE); |
| } |
| |
| private byte[] des3KdEncrypt(byte[] confounder, byte[] plaintext, |
| int start, int len, byte[] padding) throws GSSException { |
| |
| |
| // [confounder | plaintext | padding] |
| byte[] all = new byte[confounder.length + len + padding.length]; |
| System.arraycopy(confounder, 0, all, 0, confounder.length); |
| System.arraycopy(plaintext, start, all, confounder.length, len); |
| System.arraycopy(padding, 0, all, confounder.length + len, |
| padding.length); |
| |
| // Krb5Token.debug("\ndes3KdEncrypt:" + Krb5Token.getHexBytes(all)); |
| |
| // Encrypt |
| try { |
| byte[] answer = Des3.encryptRaw(keybytes, KG_USAGE_SEAL, ZERO_IV, |
| all, 0, all.length); |
| // Krb5Token.debug("\ndes3KdEncrypt encrypted:" + |
| // Krb5Token.getHexBytes(answer)); |
| return answer; |
| } catch (Exception e) { |
| // GeneralSecurityException, KrbCryptoException |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use DES3-KD Cipher - " + e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| } |
| |
| // ---- RC4-HMAC methods |
| private void arcFourDecrypt(WrapToken token, byte[] ciphertext, |
| int cStart, int cLen, byte[] plaintext, int pStart) |
| throws GSSException { |
| |
| // obtain Sequence number needed for decryption |
| // first decrypt the Sequence Number using checksum |
| byte[] seqNum = decryptSeq(token.getChecksum(), |
| token.getEncSeqNumber(), 0, 8); |
| |
| byte[] ptext; |
| try { |
| ptext = ArcFourHmac.decryptRaw(keybytes, KG_USAGE_SEAL, ZERO_IV, |
| ciphertext, cStart, cLen, seqNum); |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use ArcFour Cipher - " + e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| /* |
| Krb5Token.debug("\narcFourDecrypt in: " + |
| Krb5Token.getHexBytes(ciphertext, cStart, cLen)); |
| Krb5Token.debug("\narcFourDecrypt plain: " + |
| Krb5Token.getHexBytes(ptext)); |
| */ |
| |
| // Strip out confounder and padding |
| /* |
| * There is always at least one padding byte. The padding bytes |
| * are all the value of the number of padding bytes. |
| */ |
| int padSize = ptext[ptext.length - 1]; |
| if (padSize < 1) |
| throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, |
| "Invalid padding on Wrap Token"); |
| |
| token.padding = WrapToken.pads[padSize]; |
| int len = ptext.length - WrapToken.CONFOUNDER_SIZE - padSize; |
| |
| System.arraycopy(ptext, WrapToken.CONFOUNDER_SIZE, |
| plaintext, pStart, len); |
| |
| // Krb5Token.debug("\narcFourDecrypt plaintext: " + |
| // Krb5Token.getHexBytes(plaintext)); |
| |
| // Needed to calculate checksum |
| System.arraycopy(ptext, 0, token.confounder, |
| 0, WrapToken.CONFOUNDER_SIZE); |
| } |
| |
| private byte[] arcFourEncrypt(WrapToken token, byte[] confounder, |
| byte[] plaintext, int start, int len, byte[] padding) |
| throws GSSException { |
| |
| // [confounder | plaintext | padding] |
| byte[] all = new byte[confounder.length + len + padding.length]; |
| System.arraycopy(confounder, 0, all, 0, confounder.length); |
| System.arraycopy(plaintext, start, all, confounder.length, len); |
| System.arraycopy(padding, 0, all, confounder.length + len, |
| padding.length); |
| |
| // get the token Sequence Number required for encryption |
| // Note: When using this RC4 based encryption type, the sequence number |
| // is always sent in big-endian rather than little-endian order. |
| byte[] seqNum = new byte[4]; |
| WrapToken.writeBigEndian(token.getSequenceNumber(), seqNum); |
| |
| // Krb5Token.debug("\narcFourEncrypt:" + Krb5Token.getHexBytes(all)); |
| |
| // Encrypt |
| try { |
| byte[] answer = ArcFourHmac.encryptRaw(keybytes, KG_USAGE_SEAL, |
| seqNum, all, 0, all.length); |
| // Krb5Token.debug("\narcFourEncrypt encrypted:" + |
| // Krb5Token.getHexBytes(answer)); |
| return answer; |
| } catch (Exception e) { |
| // GeneralSecurityException, KrbCryptoException |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use ArcFour Cipher - " + e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| } |
| |
| // ---- AES methods |
| private byte[] aes128Encrypt(byte[] confounder, byte[] tokenHeader, |
| byte[] plaintext, int start, int len, int key_usage) |
| throws GSSException { |
| |
| // encrypt { AES-plaintext-data | filler | header } |
| // AES-plaintext-data { confounder | plaintext } |
| // WrapToken = { tokenHeader | |
| // Encrypt (confounder | plaintext | tokenHeader ) | HMAC } |
| |
| byte[] all = new byte[confounder.length + len + tokenHeader.length]; |
| System.arraycopy(confounder, 0, all, 0, confounder.length); |
| System.arraycopy(plaintext, start, all, confounder.length, len); |
| System.arraycopy(tokenHeader, 0, all, confounder.length+len, |
| tokenHeader.length); |
| |
| // Krb5Token.debug("\naes128Encrypt:" + Krb5Token.getHexBytes(all)); |
| try { |
| byte[] answer = Aes128.encryptRaw(keybytes, key_usage, |
| ZERO_IV_AES, |
| all, 0, all.length); |
| // Krb5Token.debug("\naes128Encrypt encrypted:" + |
| // Krb5Token.getHexBytes(answer)); |
| return answer; |
| } catch (Exception e) { |
| // GeneralSecurityException, KrbCryptoException |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use AES128 Cipher - " + e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| } |
| |
| private void aes128Decrypt(WrapToken_v2 token, byte[] ciphertext, |
| int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) |
| throws GSSException { |
| |
| byte[] ptext = null; |
| |
| try { |
| ptext = Aes128.decryptRaw(keybytes, key_usage, |
| ZERO_IV_AES, ciphertext, cStart, cLen); |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use AES128 Cipher - " + e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| /* |
| Krb5Token.debug("\naes128Decrypt in: " + |
| Krb5Token.getHexBytes(ciphertext, cStart, cLen)); |
| Krb5Token.debug("\naes128Decrypt plain: " + |
| Krb5Token.getHexBytes(ptext)); |
| Krb5Token.debug("\naes128Decrypt ptext: " + |
| Krb5Token.getHexBytes(ptext)); |
| */ |
| |
| // Strip out confounder and token header |
| int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE - |
| WrapToken_v2.TOKEN_HEADER_SIZE; |
| System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE, |
| plaintext, pStart, len); |
| |
| /* |
| Krb5Token.debug("\naes128Decrypt plaintext: " + |
| Krb5Token.getHexBytes(plaintext, pStart, len)); |
| */ |
| } |
| |
| private byte[] aes256Encrypt(byte[] confounder, byte[] tokenHeader, |
| byte[] plaintext, int start, int len, int key_usage) |
| throws GSSException { |
| |
| // encrypt { AES-plaintext-data | filler | header } |
| // AES-plaintext-data { confounder | plaintext } |
| // WrapToken = { tokenHeader | |
| // Encrypt (confounder | plaintext | tokenHeader ) | HMAC } |
| |
| byte[] all = new byte[confounder.length + len + tokenHeader.length]; |
| System.arraycopy(confounder, 0, all, 0, confounder.length); |
| System.arraycopy(plaintext, start, all, confounder.length, len); |
| System.arraycopy(tokenHeader, 0, all, confounder.length+len, |
| tokenHeader.length); |
| |
| // Krb5Token.debug("\naes256Encrypt:" + Krb5Token.getHexBytes(all)); |
| |
| try { |
| byte[] answer = Aes256.encryptRaw(keybytes, key_usage, |
| ZERO_IV_AES, all, 0, all.length); |
| // Krb5Token.debug("\naes256Encrypt encrypted:" + |
| // Krb5Token.getHexBytes(answer)); |
| return answer; |
| } catch (Exception e) { |
| // GeneralSecurityException, KrbCryptoException |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use AES256 Cipher - " + e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| } |
| |
| private void aes256Decrypt(WrapToken_v2 token, byte[] ciphertext, |
| int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) |
| throws GSSException { |
| |
| byte[] ptext; |
| try { |
| ptext = Aes256.decryptRaw(keybytes, key_usage, |
| ZERO_IV_AES, ciphertext, cStart, cLen); |
| } catch (GeneralSecurityException e) { |
| GSSException ge = new GSSException(GSSException.FAILURE, -1, |
| "Could not use AES128 Cipher - " + e.getMessage()); |
| ge.initCause(e); |
| throw ge; |
| } |
| |
| /* |
| Krb5Token.debug("\naes256Decrypt in: " + |
| Krb5Token.getHexBytes(ciphertext, cStart, cLen)); |
| Krb5Token.debug("\naes256Decrypt plain: " + |
| Krb5Token.getHexBytes(ptext)); |
| Krb5Token.debug("\naes256Decrypt ptext: " + |
| Krb5Token.getHexBytes(ptext)); |
| */ |
| |
| // Strip out confounder and token header |
| int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE - |
| WrapToken_v2.TOKEN_HEADER_SIZE; |
| System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE, |
| plaintext, pStart, len); |
| |
| /* |
| Krb5Token.debug("\naes128Decrypt plaintext: " + |
| Krb5Token.getHexBytes(plaintext, pStart, len)); |
| */ |
| |
| } |
| |
| /** |
| * This class provides a truncated inputstream needed by WrapToken. The |
| * truncated inputstream is passed to CipherInputStream. It prevents |
| * the CipherInputStream from treating the bytes of the following token |
| * as part fo the ciphertext for this token. |
| */ |
| class WrapTokenInputStream extends InputStream { |
| |
| private InputStream is; |
| private int length; |
| private int remaining; |
| |
| private int temp; |
| |
| public WrapTokenInputStream(InputStream is, int length) { |
| this.is = is; |
| this.length = length; |
| remaining = length; |
| } |
| |
| public final int read() throws IOException { |
| if (remaining == 0) |
| return -1; |
| else { |
| temp = is.read(); |
| if (temp != -1) |
| remaining -= temp; |
| return temp; |
| } |
| } |
| |
| public final int read(byte[] b) throws IOException { |
| if (remaining == 0) |
| return -1; |
| else { |
| temp = Math.min(remaining, b.length); |
| temp = is.read(b, 0, temp); |
| if (temp != -1) |
| remaining -= temp; |
| return temp; |
| } |
| } |
| |
| public final int read(byte[] b, |
| int off, |
| int len) throws IOException { |
| if (remaining == 0) |
| return -1; |
| else { |
| temp = Math.min(remaining, len); |
| temp = is.read(b, off, temp); |
| if (temp != -1) |
| remaining -= temp; |
| return temp; |
| } |
| } |
| |
| public final long skip(long n) throws IOException { |
| if (remaining == 0) |
| return 0; |
| else { |
| temp = (int) Math.min(remaining, n); |
| temp = (int) is.skip(temp); |
| remaining -= temp; |
| return temp; |
| } |
| } |
| |
| public final int available() throws IOException { |
| return Math.min(remaining, is.available()); |
| } |
| |
| public final void close() throws IOException { |
| remaining = 0; |
| } |
| } |
| } |