1/*
2 * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package javax.security.auth.kerberos;
27
28import java.util.Arrays;
29import javax.crypto.SecretKey;
30import javax.security.auth.DestroyFailedException;
31
32/**
33 * This class encapsulates a long term secret key for a Kerberos
34 * principal.<p>
35 *
36 * A {@code KerberosKey} object includes an EncryptionKey, a
37 * {@link KerberosPrincipal} as its owner, and the version number
38 * of the key.<p>
39 *
40 * An EncryptionKey is defined in Section 4.2.9 of the Kerberos Protocol
41 * Specification (<a href=http://www.ietf.org/rfc/rfc4120.txt>RFC 4120</a>) as:
42 * <pre>
43 *     EncryptionKey   ::= SEQUENCE {
44 *             keytype         [0] Int32 -- actually encryption type --,
45 *             keyvalue        [1] OCTET STRING
46 *     }
47 * </pre>
48 * The key material of a {@code KerberosKey} is defined as the value
49 * of the {@code keyValue} above.<p>
50 *
51 * All Kerberos JAAS login modules that obtain a principal's password and
52 * generate the secret key from it should use this class.
53 * Sometimes, such as when authenticating a server in
54 * the absence of user-to-user authentication, the login module will store
55 * an instance of this class in the private credential set of a
56 * {@link javax.security.auth.Subject Subject} during the commit phase of the
57 * authentication process.<p>
58 *
59 * A Kerberos service using a keytab to read secret keys should use
60 * the {@link KeyTab} class, where latest keys can be read when needed.<p>
61 *
62 * It might be necessary for the application to be granted a
63 * {@link javax.security.auth.PrivateCredentialPermission
64 * PrivateCredentialPermission} if it needs to access the {@code KerberosKey}
65 * instance from a Subject. This permission is not needed when the
66 * application depends on the default JGSS Kerberos mechanism to access the
67 * {@code KerberosKey}. In that case, however, the application will need an
68 * appropriate
69 * {@link javax.security.auth.kerberos.ServicePermission ServicePermission}.<p>
70 *
71 * When creating a {@code KerberosKey} using the
72 * {@link #KerberosKey(KerberosPrincipal, char[], String)} constructor,
73 * an implementation may accept non-IANA algorithm names (For example,
74 * "ArcFourMac" for "rc4-hmac"), but the {@link #getAlgorithm} method
75 * must always return the IANA algorithm name.
76 *
77 * @implNote Old algorithm names used before JDK 9 are supported in the
78 * {@link #KerberosKey(KerberosPrincipal, char[], String)} constructor in this
79 * implementation for compatibility reasons, which are "DES" (and null) for
80 * "des-cbc-md5", "DESede" for "des3-cbc-sha1-kd", "ArcFourHmac" for "rc4-hmac",
81 * "AES128" for "aes128-cts-hmac-sha1-96", and "AES256" for
82 * "aes256-cts-hmac-sha1-96".
83 *
84 * @author Mayank Upadhyay
85 * @since 1.4
86 */
87public class KerberosKey implements SecretKey {
88
89    private static final long serialVersionUID = -4625402278148246993L;
90
91   /**
92     * The principal that this secret key belongs to.
93     *
94     * @serial
95     */
96    private KerberosPrincipal principal;
97
98   /**
99     * the version number of this secret key
100     *
101     * @serial
102     */
103    private final int versionNum;
104
105   /**
106    * {@code KeyImpl} is serialized by writing out the ASN.1 encoded bytes
107    * of the encryption key.
108    *
109    * @serial
110    */
111
112    private KeyImpl key;
113    private transient boolean destroyed = false;
114
115    /**
116     * Constructs a {@code KerberosKey} from the given bytes when the key type
117     * and key version number are known. This can be used when reading the
118     * secret key information from a Kerberos "keytab".
119     *
120     * @param principal the principal that this secret key belongs to
121     * @param keyBytes the key material for the secret key
122     * @param keyType the key type for the secret key as defined by the
123     * Kerberos protocol specification.
124     * @param versionNum the version number of this secret key
125     */
126    public KerberosKey(KerberosPrincipal principal,
127                       byte[] keyBytes,
128                       int keyType,
129                       int versionNum) {
130        this.principal = principal;
131        this.versionNum = versionNum;
132        key = new KeyImpl(keyBytes, keyType);
133    }
134
135    /**
136     * Constructs a {@code KerberosKey} from a principal's password using the
137     * specified algorithm name. The algorithm name (case insensitive) should
138     * be provided as the encryption type string defined on the IANA
139     * <a href="https://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml#kerberos-parameters-1">Kerberos Encryption Type Numbers</a>
140     * page. The version number of the key generated will be 0.
141     *
142     * @param principal the principal that this password belongs to
143     * @param password the password that should be used to compute the key
144     * @param algorithm the name for the algorithm that this key will be
145     * used for
146     * @throws IllegalArgumentException if the name of the
147     * algorithm passed is unsupported.
148     */
149    public KerberosKey(KerberosPrincipal principal,
150                       char[] password,
151                       String algorithm) {
152
153        this.principal = principal;
154        this.versionNum = 0;
155        // Pass principal in for salt
156        key = new KeyImpl(principal, password, algorithm);
157    }
158
159    /**
160     * Returns the principal that this key belongs to.
161     *
162     * @return the principal this key belongs to.
163     * @throws IllegalStateException if the key is destroyed
164     */
165    public final KerberosPrincipal getPrincipal() {
166        if (destroyed) {
167            throw new IllegalStateException("This key is no longer valid");
168        }
169        return principal;
170    }
171
172    /**
173     * Returns the key version number.
174     *
175     * @return the key version number.
176     * @throws IllegalStateException if the key is destroyed
177     */
178    public final int getVersionNumber() {
179        if (destroyed) {
180            throw new IllegalStateException("This key is no longer valid");
181        }
182        return versionNum;
183    }
184
185    /**
186     * Returns the key type for this long-term key.
187     *
188     * @return the key type.
189     * @throws IllegalStateException if the key is destroyed
190     */
191    public final int getKeyType() {
192        // KeyImpl already checked if destroyed
193        return key.getKeyType();
194    }
195
196    /*
197     * Methods from java.security.Key
198     */
199
200    /**
201     * Returns the standard algorithm name for this key. The algorithm names
202     * are the encryption type string defined on the IANA
203     * <a href="https://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml#kerberos-parameters-1">Kerberos Encryption Type Numbers</a>
204     * page.
205     * <p>
206     * This method can return the following value not defined on the IANA page:
207     * <ol>
208     *     <li>none: for etype equal to 0</li>
209     *     <li>unknown: for etype greater than 0 but unsupported by
210     *         the implementation</li>
211     *     <li>private: for etype smaller than 0</li>
212     * </ol>
213     *
214     * @return the name of the algorithm associated with this key.
215     * @throws IllegalStateException if the key is destroyed
216     */
217    public final String getAlgorithm() {
218        // KeyImpl already checked if destroyed
219        return key.getAlgorithm();
220    }
221
222    /**
223     * Returns the name of the encoding format for this secret key.
224     *
225     * @return the String "RAW"
226     * @throws IllegalStateException if the key is destroyed
227     */
228    public final String getFormat() {
229        // KeyImpl already checked if destroyed
230        return key.getFormat();
231    }
232
233    /**
234     * Returns the key material of this secret key.
235     *
236     * @return the key material
237     * @throws IllegalStateException if the key is destroyed
238     */
239    public final byte[] getEncoded() {
240        // KeyImpl already checked if destroyed
241        return key.getEncoded();
242    }
243
244    /**
245     * Destroys this key by clearing out the key material of this secret key.
246     *
247     * @throws DestroyFailedException if some error occurs while destorying
248     * this key.
249     */
250    public void destroy() throws DestroyFailedException {
251        if (!destroyed) {
252            key.destroy();
253            principal = null;
254            destroyed = true;
255        }
256    }
257
258
259    /** Determines if this key has been destroyed.*/
260    public boolean isDestroyed() {
261        return destroyed;
262    }
263
264    /**
265     * Returns an informative textual representation of this {@code KerberosKey}.
266     *
267     * @return an informative textual representation of this {@code KerberosKey}.
268     */
269    public String toString() {
270        if (destroyed) {
271            return "Destroyed KerberosKey";
272        }
273        return "Kerberos Principal " + principal +
274                "Key Version " + versionNum +
275                "key "  + key.toString();
276    }
277
278    /**
279     * Returns a hash code for this {@code KerberosKey}.
280     *
281     * @return a hash code for this {@code KerberosKey}.
282     * @since 1.6
283     */
284    public int hashCode() {
285        int result = 17;
286        if (isDestroyed()) {
287            return result;
288        }
289        result = 37 * result + Arrays.hashCode(getEncoded());
290        result = 37 * result + getKeyType();
291        if (principal != null) {
292            result = 37 * result + principal.hashCode();
293        }
294        return result * 37 + versionNum;
295    }
296
297    /**
298     * Compares the specified object with this {@code KerberosKey} for
299     * equality. Returns true if the given object is also a
300     * {@code KerberosKey} and the two
301     * {@code KerberosKey} instances are equivalent.
302     * A destroyed {@code KerberosKey} object is only equal to itself.
303     *
304     * @param other the object to compare to
305     * @return true if the specified object is equal to this {@code KerberosKey},
306     * false otherwise.
307     * @since 1.6
308     */
309    public boolean equals(Object other) {
310
311        if (other == this) {
312            return true;
313        }
314
315        if (! (other instanceof KerberosKey)) {
316            return false;
317        }
318
319        KerberosKey otherKey = ((KerberosKey) other);
320        if (isDestroyed() || otherKey.isDestroyed()) {
321            return false;
322        }
323
324        if (versionNum != otherKey.getVersionNumber() ||
325                getKeyType() != otherKey.getKeyType() ||
326                !Arrays.equals(getEncoded(), otherKey.getEncoded())) {
327            return false;
328        }
329
330        if (principal == null) {
331            if (otherKey.getPrincipal() != null) {
332                return false;
333            }
334        } else {
335            if (!principal.equals(otherKey.getPrincipal())) {
336                return false;
337            }
338        }
339
340        return true;
341    }
342}
343