001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package javax.security.auth.kerberos;
019:
020: import java.io.IOException;
021: import java.io.ObjectInputStream;
022: import java.io.ObjectOutputStream;
023: import java.io.Serializable;
024: import java.util.Arrays;
025:
026: import javax.crypto.Cipher;
027: import javax.crypto.SecretKey;
028: import javax.crypto.spec.IvParameterSpec;
029: import javax.crypto.spec.SecretKeySpec;
030: import javax.security.auth.DestroyFailedException;
031: import javax.security.auth.Destroyable;
032:
033: import org.apache.harmony.auth.internal.kerberos.v5.EncryptionKey;
034: import org.apache.harmony.auth.internal.nls.Messages;
035: import org.apache.harmony.security.utils.Array;
036:
037: /**
038: * This class encapsulates a Kerberos encryption key.
039: *
040: */
041: class KeyImpl implements SecretKey, Destroyable, Serializable {
042:
043: private static final long serialVersionUID = -7889313790214321193L;
044:
045: private transient byte[] keyBytes;
046:
047: private transient int keyType;
048:
049: // indicates the ticket state
050: private transient boolean destroyed;
051:
052: // Pre-calculated parity values
053: // TODO the alternative for boolean table - any acceptable algorithm?
054: private final static boolean[] PARITY = new boolean[] { false,
055: true, true, false, true, false, false, true, true, false,
056: false, true, false, true, true, false, true, false, false,
057: true, false, true, true, false, false, true, true, false,
058: true, false, false, true, true, false, false, true, false,
059: true, true, false, false, true, true, false, true, false,
060: false, true, false, true, true, false, true, false, false,
061: true, true, false, false, true, false, true, true, false,
062: true, false, false, true, false, true, true, false, false,
063: true, true, false, true, false, false, true, false, true,
064: true, false, true, false, false, true, true, false, false,
065: true, false, true, true, false, false, true, true, false,
066: true, false, false, true, true, false, false, true, false,
067: true, true, false, true, false, false, true, false, true,
068: true, false, false, true, true, false, true, false, false,
069: true, true, false, false, true, false, true, true, false,
070: false, true, true, false, true, false, false, true, false,
071: true, true, false, true, false, false, true, true, false,
072: false, true, false, true, true, false, false, true, true,
073: false, true, false, false, true, true, false, false, true,
074: false, true, true, false, true, false, false, true, false,
075: true, true, false, false, true, true, false, true, false,
076: false, true, false, true, true, false, true, false, false,
077: true, true, false, false, true, false, true, true, false,
078: true, false, false, true, false, true, true, false, false,
079: true, true, false, true, false, false, true, true, false,
080: false, true, false, true, true, false, false, true, true,
081: false, true, false, false, true, false, true, true, false,
082: true, false, false, true, true, false, false, true, false,
083: true, true, false };
084:
085: // Pre-calculated reversed values
086: // TODO any acceptable alternative algorithm instead of table?
087: private static final byte[] REVERSE = new byte[] { 0, 64, 32, 96,
088: 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, 4, 68,
089: 36, 100, 20, 84, 52, 116, 12, 76, 44, 108, 28, 92, 60, 124,
090: 2, 66, 34, 98, 18, 82, 50, 114, 10, 74, 42, 106, 26, 90,
091: 58, 122, 6, 70, 38, 102, 22, 86, 54, 118, 14, 78, 46, 110,
092: 30, 94, 62, 126, 1, 65, 33, 97, 17, 81, 49, 113, 9, 73, 41,
093: 105, 25, 89, 57, 121, 5, 69, 37, 101, 21, 85, 53, 117, 13,
094: 77, 45, 109, 29, 93, 61, 125, 3, 67, 35, 99, 19, 83, 51,
095: 115, 11, 75, 43, 107, 27, 91, 59, 123, 7, 71, 39, 103, 23,
096: 87, 55, 119, 15, 79, 47, 111, 31, 95, 63, 127 };
097:
098: /**
099: * creates a secret key from a given raw bytes
100: *
101: * @param keyBytes
102: * @param keyType
103: */
104: public KeyImpl(byte[] keyBytes, int keyType) {
105: this .keyBytes = new byte[keyBytes.length];
106: System.arraycopy(keyBytes, 0, this .keyBytes, 0,
107: this .keyBytes.length);
108: this .keyType = keyType;
109: }
110:
111: /**
112: * creates a secret key from a given password
113: *
114: * @param principal
115: * @param password
116: * @param algorithm
117: */
118: public KeyImpl(KerberosPrincipal principal, char[] password,
119: String algorithm) {
120:
121: //
122: // See http://www.ietf.org/rfc/rfc3961.txt for algorithm description
123: //
124:
125: if (principal == null || password == null) {
126: throw new NullPointerException();
127: }
128:
129: if (algorithm != null && "DES".compareTo(algorithm) != 0) { //$NON-NLS-1$
130: throw new IllegalArgumentException(Messages
131: .getString("auth.49")); //$NON-NLS-1$
132: }
133:
134: keyType = 3; // DES algorithm
135: keyBytes = new byte[8];
136:
137: String realm = principal.getRealm();
138: String pname = principal.getName();
139:
140: StringBuilder buf = new StringBuilder();
141: buf.append(password);
142: buf.append(realm);
143: buf.append(pname.substring(0, pname.length() - realm.length()
144: - 1));
145:
146: byte[] tmp = buf.toString().getBytes();
147:
148: // pad with 0x00 to 8 byte boundary
149: byte[] raw = new byte[tmp.length
150: + ((tmp.length % 8) == 0 ? 0 : (8 - tmp.length % 8))];
151: System.arraycopy(tmp, 0, raw, 0, tmp.length);
152:
153: long k1, k2 = 0;
154: boolean isOdd = false;
155: // for each 8-byte block in raw byte array
156: for (int i = 0; i < raw.length; i = i + 8, isOdd = !isOdd) {
157:
158: k1 = 0;
159: if (isOdd) {
160: //reverse
161: for (int j = 7; j > -1; j--) {
162: k1 = (k1 << 7) + REVERSE[raw[i + j] & 0x7F];
163: }
164: } else {
165: for (int j = 0; j < 8; j++) {
166: k1 = (k1 << 7) + (raw[i + j] & 0x7F);
167: }
168: }
169: k2 = k2 ^ k1;
170: }
171:
172: // 56-bit long to byte array (8 bytes)
173: for (int i = 7; i > -1; i--) {
174: keyBytes[i] = (byte) k2;
175: keyBytes[i] = (byte) (keyBytes[i] << 1);
176: k2 = k2 >> 7;
177: }
178: keyCorrection(keyBytes);
179:
180: // calculate DES-CBC check sum
181: try {
182: Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding"); //$NON-NLS-1$
183:
184: // use tmp key as IV
185: IvParameterSpec IV = new IvParameterSpec(keyBytes);
186:
187: // do DES encryption
188: SecretKey secretKey = new SecretKeySpec(keyBytes, "DES"); //$NON-NLS-1$
189: cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV);
190: byte[] enc = cipher.doFinal(raw);
191:
192: // final last block is check sum
193: System.arraycopy(enc, enc.length - 8, keyBytes, 0, 8);
194:
195: keyCorrection(keyBytes);
196:
197: } catch (Exception e) {
198: throw new RuntimeException(Messages.getString("auth.4A"), e); //$NON-NLS-1$
199: }
200: }
201:
202: private void keyCorrection(byte[] key) {
203:
204: // fix parity
205: for (int i = 0; i < 8; i++) {
206: if (!PARITY[key[i] & 0xFF]) {
207: if ((key[i] & 0x01) == 0) {
208: key[i]++;
209: } else {
210: key[i]--;
211: }
212: }
213: }
214:
215: // TODO if is week do XOR
216: //if(DESKeySpec.isWeak(keyBytes,0)){
217: //}
218: }
219:
220: /**
221: * Method is described in
222: * <code>getAlgorithm</code> in interface <code>Key</code>
223: */
224: public final String getAlgorithm() {
225: checkState();
226: if (keyType == 0) {
227: return "NULL"; //$NON-NLS-1$
228: }
229: return "DES"; //$NON-NLS-1$
230: }
231:
232: /**
233: * Method is described in
234: * <code>getFormat</code> in interface <code>Key</code>
235: */
236: public final String getFormat() {
237: checkState();
238: return "RAW"; //$NON-NLS-1$
239: }
240:
241: /**
242: * Method is described in
243: * <code>getEncoded</code> in interface <code>Key</code>
244: */
245: public final byte[] getEncoded() {
246: checkState();
247: byte[] tmp = new byte[keyBytes.length];
248: System.arraycopy(keyBytes, 0, tmp, 0, tmp.length);
249: return tmp;
250: }
251:
252: /**
253: * Returns the key type for this key
254: */
255: public final int getKeyType() {
256: checkState();
257: return keyType;
258: }
259:
260: /**
261: * Destroys this key
262: */
263: public void destroy() throws DestroyFailedException {
264: if (!destroyed) {
265: Arrays.fill(keyBytes, (byte) 0);
266: destroyed = true;
267: }
268:
269: }
270:
271: /**
272: * Determines if this key has been destroyed
273: */
274: public boolean isDestroyed() {
275: return destroyed;
276: }
277:
278: /**
279: * A string representation of this key
280: */
281: @Override
282: public String toString() {
283: String s_key = null;
284: StringBuilder sb = new StringBuilder();
285:
286: if (keyBytes.length == 0) {
287: s_key = "Empty Key"; //$NON-NLS-1$
288: } else {
289: s_key = Array.toString(keyBytes, " "); //$NON-NLS-1$
290: }
291: sb
292: .append("EncryptionKey: ").append("KeyType = ").append(keyType); //$NON-NLS-1$ //$NON-NLS-2$
293: sb.append("KeyBytes (Hex dump) = ").append(s_key); //$NON-NLS-1$
294: return sb.toString();
295: }
296:
297: /**
298: * if a key is destroyed then IllegalStateException should be thrown
299: */
300: private void checkState() {
301: if (destroyed) {
302: throw new IllegalStateException(Messages
303: .getString("auth.48")); //$NON-NLS-1$
304: }
305: }
306:
307: private void readObject(ObjectInputStream s) throws IOException,
308: ClassNotFoundException {
309:
310: s.defaultReadObject();
311:
312: EncryptionKey ekey = (EncryptionKey) EncryptionKey.ASN1
313: .decode((byte[]) s.readObject());
314:
315: keyType = ekey.getType();
316: keyBytes = ekey.getValue();
317: }
318:
319: private void writeObject(ObjectOutputStream s) throws IOException {
320:
321: if (destroyed) {
322: throw new IOException(Messages.getString("auth.48")); //$NON-NLS-1$
323: }
324: s.defaultWriteObject();
325:
326: byte[] enc = EncryptionKey.ASN1.encode(new EncryptionKey(
327: keyType, keyBytes));
328: s.writeObject(enc);
329: }
330: }
|