blob: cb7543b3fd552a1c71c61375f6284816942f5a27 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2003-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.security.ssl;
27
28import java.io.IOException;
29import java.io.PrintStream;
30import java.security.AccessController;
31import java.security.AccessControlContext;
32import java.security.PrivilegedExceptionAction;
33import java.security.PrivilegedActionException;
34import java.security.SecureRandom;
35import java.net.InetAddress;
36
37import javax.net.ssl.SSLException;
38import javax.security.auth.kerberos.KerberosTicket;
39import javax.security.auth.kerberos.KerberosKey;
40import javax.security.auth.kerberos.KerberosPrincipal;
41import javax.security.auth.kerberos.ServicePermission;
42import sun.security.jgss.GSSUtil;
43
44import sun.security.krb5.Config;
45import sun.security.krb5.EncryptionKey;
46import sun.security.krb5.EncryptedData;
47import sun.security.krb5.PrincipalName;
48import sun.security.krb5.Realm;
49import sun.security.krb5.KrbException;
50import sun.security.krb5.internal.Ticket;
51import sun.security.krb5.internal.EncTicketPart;
52import sun.security.krb5.internal.crypto.KeyUsage;
53
54import sun.security.jgss.krb5.Krb5Util;
55
56/**
57 * This is Kerberos option in the client key exchange message
58 * (CLIENT -> SERVER). It holds the Kerberos ticket and the encrypted
59 * premaster secret encrypted with the session key sealed in the ticket.
60 * From RFC 2712:
61 * struct
62 * {
63 * opaque Ticket;
64 * opaque authenticator; // optional
65 * opaque EncryptedPreMasterSecret; // encrypted with the session key
66 * // which is sealed in the ticket
67 * } KerberosWrapper;
68 *
69 *
70 * Ticket and authenticator are encrypted as per RFC 1510 (in ASN.1)
71 * Encrypted pre-master secret has the same structure as it does for RSA
72 * except for Kerberos, the encryption key is the session key instead of
73 * the RSA public key.
74 *
75 * XXX authenticator currently ignored
76 *
77 */
78final class KerberosClientKeyExchange extends HandshakeMessage {
79
80 private KerberosPreMasterSecret preMaster;
81 private byte[] encodedTicket;
82 private KerberosPrincipal peerPrincipal;
83 private KerberosPrincipal localPrincipal;
84
85 /**
86 * Creates an instance of KerberosClientKeyExchange consisting of the
87 * Kerberos service ticket, authenticator and encrypted premaster secret.
88 * Called by client handshaker.
89 *
90 * @param serverName name of server with which to do handshake;
91 * this is used to get the Kerberos service ticket
92 * @param protocolVersion Maximum version supported by client (i.e,
93 * version it requested in client hello)
94 * @param rand random number generator to use for generating pre-master
95 * secret
96 */
97 KerberosClientKeyExchange(String serverName, boolean isLoopback,
98 AccessControlContext acc, ProtocolVersion protocolVersion,
99 SecureRandom rand) throws IOException {
100
101 // Get service ticket
102 KerberosTicket ticket = getServiceTicket(serverName, isLoopback, acc);
103 encodedTicket = ticket.getEncoded();
104
105 // Record the Kerberos principals
106 peerPrincipal = ticket.getServer();
107 localPrincipal = ticket.getClient();
108
109 // Optional authenticator, encrypted using session key,
110 // currently ignored
111
112 // Generate premaster secret and encrypt it using session key
113 EncryptionKey sessionKey = new EncryptionKey(
114 ticket.getSessionKeyType(),
115 ticket.getSessionKey().getEncoded());
116
117 preMaster = new KerberosPreMasterSecret(protocolVersion,
118 rand, sessionKey);
119 }
120
121 /**
122 * Creates an instance of KerberosClientKeyExchange from its ASN.1 encoding.
123 * Used by ServerHandshaker to verify and obtain premaster secret.
124 *
125 * @param protocolVersion current protocol version
126 * @param clientVersion version requested by client in its ClientHello;
127 * used by premaster secret version check
128 * @param rand random number generator used for generating random
129 * premaster secret if ticket and/or premaster verification fails
130 * @param input inputstream from which to get ASN.1-encoded KerberosWrapper
131 * @param serverKey server's master secret key
132 */
133 KerberosClientKeyExchange(ProtocolVersion protocolVersion,
134 ProtocolVersion clientVersion,
135 SecureRandom rand, HandshakeInStream input, KerberosKey[] serverKeys)
136 throws IOException {
137
138 // Read ticket
139 encodedTicket = input.getBytes16();
140
141 if (debug != null && Debug.isOn("verbose")) {
142 Debug.println(System.out,
143 "encoded Kerberos service ticket", encodedTicket);
144 }
145
146 EncryptionKey sessionKey = null;
147
148 try {
149 Ticket t = new Ticket(encodedTicket);
150
151 EncryptedData encPart = t.encPart;
152 PrincipalName ticketSname = t.sname;
153 Realm ticketRealm = t.realm;
154
155 String serverPrincipal = serverKeys[0].getPrincipal().getName();
156
157 /*
158 * permission to access and use the secret key of the Kerberized
159 * "host" service is done in ServerHandshaker.getKerberosKeys()
160 * to ensure server has the permission to use the secret key
161 * before promising the client
162 */
163
164 // Check that ticket Sname matches serverPrincipal
165 String ticketPrinc = ticketSname.toString().concat("@" +
166 ticketRealm.toString());
167 if (!ticketPrinc.equals(serverPrincipal)) {
168 if (debug != null && Debug.isOn("handshake"))
169 System.out.println("Service principal in Ticket does not"
170 + " match associated principal in KerberosKey");
171 throw new IOException("Server principal is " +
172 serverPrincipal + " but ticket is for " +
173 ticketPrinc);
174 }
175
176 // See if we have the right key to decrypt the ticket to get
177 // the session key.
178 int encPartKeyType = encPart.getEType();
179 KerberosKey dkey = findKey(encPartKeyType, serverKeys);
180 if (dkey == null) {
181 // %%% Should print string repr of etype
182 throw new IOException(
183 "Cannot find key of appropriate type to decrypt ticket - need etype " +
184 encPartKeyType);
185 }
186
187 EncryptionKey secretKey = new EncryptionKey(
188 encPartKeyType,
189 dkey.getEncoded());
190
191 // Decrypt encPart using server's secret key
192 byte[] bytes = encPart.decrypt(secretKey, KeyUsage.KU_TICKET);
193
194 // Reset data stream after decryption, remove redundant bytes
195 byte[] temp = encPart.reset(bytes, true);
196 EncTicketPart encTicketPart = new EncTicketPart(temp);
197
198 // Record the Kerberos Principals
199 peerPrincipal =
200 new KerberosPrincipal(encTicketPart.cname.getName());
201 localPrincipal = new KerberosPrincipal(ticketSname.getName());
202
203 sessionKey = encTicketPart.key;
204
205 if (debug != null && Debug.isOn("handshake")) {
206 System.out.println("server principal: " + serverPrincipal);
207 System.out.println("realm: " + encTicketPart.crealm.toString());
208 System.out.println("cname: " + encTicketPart.cname.toString());
209 }
210 } catch (IOException e) {
211 throw e;
212 } catch (Exception e) {
213 if (debug != null && Debug.isOn("handshake")) {
214 System.out.println("KerberosWrapper error getting session key,"
215 + " generating random secret (" + e.getMessage() + ")");
216 }
217 sessionKey = null;
218 }
219
220 input.getBytes16(); // XXX Read and ignore authenticator
221
222 if (sessionKey != null) {
223 preMaster = new KerberosPreMasterSecret(protocolVersion,
224 clientVersion, rand, input, sessionKey);
225 } else {
226 // Generate bogus premaster secret
227 preMaster = new KerberosPreMasterSecret(protocolVersion, rand);
228 }
229 }
230
231 int messageType() {
232 return ht_client_key_exchange;
233 }
234
235 int messageLength() {
236 return (6 + encodedTicket.length + preMaster.getEncrypted().length);
237 }
238
239 void send(HandshakeOutStream s) throws IOException {
240 s.putBytes16(encodedTicket);
241 s.putBytes16(null); // XXX no authenticator
242 s.putBytes16(preMaster.getEncrypted());
243 }
244
245 void print(PrintStream s) throws IOException {
246 s.println("*** ClientKeyExchange, Kerberos");
247
248 if (debug != null && Debug.isOn("verbose")) {
249 Debug.println(s, "Kerberos service ticket", encodedTicket);
250 Debug.println(s, "Random Secret", preMaster.getUnencrypted());
251 Debug.println(s, "Encrypted random Secret",
252 preMaster.getEncrypted());
253 }
254 }
255
256 // Similar to sun.security.jgss.krb5.Krb5InitCredenetial/Krb5Context
257 private static KerberosTicket getServiceTicket(String srvName,
258 boolean isLoopback, final AccessControlContext acc) throws IOException {
259
260 // get the local hostname if srvName is loopback address
261 String serverName = srvName;
262 if (isLoopback) {
263 String localHost = java.security.AccessController.doPrivileged(
264 new java.security.PrivilegedAction<String>() {
265 public String run() {
266 String hostname;
267 try {
268 hostname = InetAddress.getLocalHost().getHostName();
269 } catch (java.net.UnknownHostException e) {
270 hostname = "localhost";
271 }
272 return hostname;
273 }
274 });
275 serverName = localHost;
276 }
277
278 // Resolve serverName (possibly in IP addr form) to Kerberos principal
279 // name for service with hostname
280 String serviceName = "host/" + serverName;
281 PrincipalName principal;
282 try {
283 principal = new PrincipalName(serviceName,
284 PrincipalName.KRB_NT_SRV_HST);
285 } catch (SecurityException se) {
286 throw se;
287 } catch (Exception e) {
288 IOException ioe = new IOException("Invalid service principal" +
289 " name: " + serviceName);
290 ioe.initCause(e);
291 throw ioe;
292 }
293 String realm = principal.getRealmAsString();
294
295 final String serverPrincipal = principal.toString();
296 final String tgsPrincipal = "krbtgt/" + realm + "@" + realm;
297 final String clientPrincipal = null; // use default
298
299
300 // check permission to obtain a service ticket to initiate a
301 // context with the "host" service
302 SecurityManager sm = System.getSecurityManager();
303 if (sm != null) {
304 sm.checkPermission(new ServicePermission(serverPrincipal,
305 "initiate"), acc);
306 }
307
308 try {
309 KerberosTicket ticket = AccessController.doPrivileged(
310 new PrivilegedExceptionAction<KerberosTicket>() {
311 public KerberosTicket run() throws Exception {
312 return Krb5Util.getTicketFromSubjectAndTgs(
313 GSSUtil.CALLER_SSL_CLIENT,
314 clientPrincipal, serverPrincipal,
315 tgsPrincipal, acc);
316 }});
317
318 if (ticket == null) {
319 throw new IOException("Failed to find any kerberos service" +
320 " ticket for " + serverPrincipal);
321 }
322 return ticket;
323 } catch (PrivilegedActionException e) {
324 IOException ioe = new IOException(
325 "Attempt to obtain kerberos service ticket for " +
326 serverPrincipal + " failed!");
327 ioe.initCause(e);
328 throw ioe;
329 }
330 }
331
332 KerberosPreMasterSecret getPreMasterSecret() {
333 return preMaster;
334 }
335
336 KerberosPrincipal getPeerPrincipal() {
337 return peerPrincipal;
338 }
339
340 KerberosPrincipal getLocalPrincipal() {
341 return localPrincipal;
342 }
343
344 private static KerberosKey findKey(int etype, KerberosKey[] keys) {
345 int ktype;
346 for (int i = 0; i < keys.length; i++) {
347 ktype = keys[i].getKeyType();
348 if (etype == ktype) {
349 return keys[i];
350 }
351 }
352 // Key not found.
353 // %%% kludge to allow DES keys to be used for diff etypes
354 if ((etype == EncryptedData.ETYPE_DES_CBC_CRC ||
355 etype == EncryptedData.ETYPE_DES_CBC_MD5)) {
356 for (int i = 0; i < keys.length; i++) {
357 ktype = keys[i].getKeyType();
358 if (ktype == EncryptedData.ETYPE_DES_CBC_CRC ||
359 ktype == EncryptedData.ETYPE_DES_CBC_MD5) {
360 return new KerberosKey(keys[i].getPrincipal(),
361 keys[i].getEncoded(),
362 etype,
363 keys[i].getVersionNumber());
364 }
365 }
366 }
367 return null;
368 }
369}