1 /* 2 * Copyright (c) 2000, 2012, 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 26 /* 27 * 28 * (C) Copyright IBM Corp. 1999 All Rights Reserved. 29 * Copyright 1997 The Open Group Research Institute. All rights reserved. 30 */ 31 32 package sun.security.krb5; 33 34 import sun.security.krb5.internal.*; 35 import sun.security.krb5.internal.crypto.*; 36 import sun.security.jgss.krb5.Krb5AcceptCredential; 37 import java.net.InetAddress; 38 import sun.security.util.*; 39 import java.io.IOException; 40 import java.util.Arrays; 41 import java.security.MessageDigest; 42 import java.security.NoSuchAlgorithmException; 43 import sun.security.krb5.internal.rcache.AuthTimeWithHash; 44 45 /** 46 * This class encapsulates a KRB-AP-REQ that a client sends to a 47 * server for authentication. 48 */ 49 public class KrbApReq { 50 51 private byte[] obuf; 52 private KerberosTime ctime; 53 private int cusec; 54 private Authenticator authenticator; 55 private Credentials creds; 56 private APReq apReqMessg; 57 58 // Used by acceptor side 59 private static ReplayCache rcache = ReplayCache.getInstance(); 60 private static boolean DEBUG = Krb5.DEBUG; 61 private static final char[] hexConst = "0123456789ABCDEF".toCharArray(); 62 63 private static final MessageDigest md; 64 65 static { 66 try { 67 md = MessageDigest.getInstance("MD5"); 68 } catch (NoSuchAlgorithmException ex) { 69 throw new RuntimeException("Impossible"); 70 } 71 } 72 73 /** 74 * Constructs an AP-REQ message to send to the peer. 75 * @param tgsCred the <code>Credentials</code> to be used to construct the 76 * AP Request protocol message. 77 * @param mutualRequired Whether mutual authentication is required 78 * @param useSubkey Whether the subkey is to be used to protect this 79 * specific application session. If this is not set then the 80 * session key from the ticket will be used. 81 * @throws KrbException for any Kerberos protocol specific error 82 * @throws IOException for any IO related errors 83 * (e.g. socket operations) 84 */ 85 /* 86 // Not Used 87 public KrbApReq(Credentials tgsCred, 88 boolean mutualRequired, 89 boolean useSubKey, 90 boolean useSeqNumber) throws Asn1Exception, 91 KrbCryptoException, KrbException, IOException { 92 93 this(tgsCred, mutualRequired, useSubKey, useSeqNumber, null); 94 } 95 */ 96 97 /** 98 * Constructs an AP-REQ message to send to the peer. 99 * @param tgsCred the <code>Credentials</code> to be used to construct the 100 * AP Request protocol message. 101 * @param mutualRequired Whether mutual authentication is required 102 * @param useSubkey Whether the subkey is to be used to protect this 103 * specific application session. If this is not set then the 104 * session key from the ticket will be used. 105 * @param checksum checksum of the the application data that accompanies 106 * the KRB_AP_REQ. 107 * @throws KrbException for any Kerberos protocol specific error 108 * @throws IOException for any IO related errors 109 * (e.g. socket operations) 110 */ 111 // Used in InitSecContextToken 112 public KrbApReq(Credentials tgsCred, 113 boolean mutualRequired, 114 boolean useSubKey, 115 boolean useSeqNumber, 116 Checksum cksum) throws Asn1Exception, 117 KrbCryptoException, KrbException, IOException { 118 119 APOptions apOptions = (mutualRequired? 120 new APOptions(Krb5.AP_OPTS_MUTUAL_REQUIRED): 121 new APOptions()); 122 if (DEBUG) 123 System.out.println(">>> KrbApReq: APOptions are " + apOptions); 124 125 EncryptionKey subKey = (useSubKey? 126 new EncryptionKey(tgsCred.getSessionKey()): 127 null); 128 129 SeqNumber seqNum = new LocalSeqNumber(); 130 131 init(apOptions, 132 tgsCred, 133 cksum, 134 subKey, 135 seqNum, 136 null, // AuthorizationData authzData 137 KeyUsage.KU_AP_REQ_AUTHENTICATOR); 138 139 } 140 141 /** 142 * Constructs an AP-REQ message from the bytes received from the 143 * peer. 144 * @param message The message received from the peer 145 * @param keys <code>EncrtyptionKey</code>s to decrypt the message; 146 * key selected will depend on etype used to encrypte data 147 * @throws KrbException for any Kerberos protocol specific error 148 * @throws IOException for any IO related errors 149 * (e.g. socket operations) 150 */ 151 // Used in InitSecContextToken (for AP_REQ and not TGS REQ) 152 public KrbApReq(byte[] message, 153 Krb5AcceptCredential cred, 154 InetAddress initiator) 155 throws KrbException, IOException { 156 obuf = message; 157 if (apReqMessg == null) 158 decode(); 159 authenticate(cred, initiator); 160 } 161 162 /** 163 * Constructs an AP-REQ message from the bytes received from the 164 * peer. 165 * @param value The <code>DerValue</code> that contains the 166 * DER enoded AP-REQ protocol message 167 * @param keys <code>EncrtyptionKey</code>s to decrypt the message; 168 * 169 * @throws KrbException for any Kerberos protocol specific error 170 * @throws IOException for any IO related errors 171 * (e.g. socket operations) 172 */ 173 /* 174 public KrbApReq(DerValue value, EncryptionKey[] key, InetAddress initiator) 175 throws KrbException, IOException { 176 obuf = value.toByteArray(); 177 if (apReqMessg == null) 178 decode(value); 179 authenticate(keys, initiator); 180 } 181 182 KrbApReq(APOptions options, 183 Credentials tgs_creds, 184 Checksum cksum, 185 EncryptionKey subKey, 186 SeqNumber seqNumber, 187 AuthorizationData authorizationData) 188 throws KrbException, IOException { 189 init(options, tgs_creds, cksum, subKey, seqNumber, authorizationData); 190 } 191 */ 192 193 /** used by KrbTgsReq **/ 194 KrbApReq(APOptions apOptions, 195 Ticket ticket, 196 EncryptionKey key, 197 PrincipalName cname, 198 Checksum cksum, 199 KerberosTime ctime, 200 EncryptionKey subKey, 201 SeqNumber seqNumber, 202 AuthorizationData authorizationData) 203 throws Asn1Exception, IOException, 204 KdcErrException, KrbCryptoException { 205 206 init(apOptions, ticket, key, cname, 207 cksum, ctime, subKey, seqNumber, authorizationData, 208 KeyUsage.KU_PA_TGS_REQ_AUTHENTICATOR); 209 210 } 211 212 private void init(APOptions options, 213 Credentials tgs_creds, 214 Checksum cksum, 215 EncryptionKey subKey, 216 SeqNumber seqNumber, 217 AuthorizationData authorizationData, 218 int usage) 219 throws KrbException, IOException { 220 221 ctime = KerberosTime.now(); 222 init(options, 223 tgs_creds.ticket, 224 tgs_creds.key, 225 tgs_creds.client, 226 cksum, 227 ctime, 228 subKey, 229 seqNumber, 230 authorizationData, 231 usage); 232 } 233 234 private void init(APOptions apOptions, 235 Ticket ticket, 236 EncryptionKey key, 237 PrincipalName cname, 238 Checksum cksum, 239 KerberosTime ctime, 240 EncryptionKey subKey, 241 SeqNumber seqNumber, 242 AuthorizationData authorizationData, 243 int usage) 244 throws Asn1Exception, IOException, 245 KdcErrException, KrbCryptoException { 246 247 createMessage(apOptions, ticket, key, cname, 248 cksum, ctime, subKey, seqNumber, authorizationData, 249 usage); 250 obuf = apReqMessg.asn1Encode(); 251 } 252 253 254 void decode() throws KrbException, IOException { 255 DerValue encoding = new DerValue(obuf); 256 decode(encoding); 257 } 258 259 void decode(DerValue encoding) throws KrbException, IOException { 260 apReqMessg = null; 261 try { 262 apReqMessg = new APReq(encoding); 263 } catch (Asn1Exception e) { 264 apReqMessg = null; 265 KRBError err = new KRBError(encoding); 266 String errStr = err.getErrorString(); 267 String eText; 268 if (errStr.charAt(errStr.length() - 1) == 0) 269 eText = errStr.substring(0, errStr.length() - 1); 270 else 271 eText = errStr; 272 KrbException ke = new KrbException(err.getErrorCode(), eText); 273 ke.initCause(e); 274 throw ke; 275 } 276 } 277 278 private void authenticate(Krb5AcceptCredential cred, InetAddress initiator) 279 throws KrbException, IOException { 280 int encPartKeyType = apReqMessg.ticket.encPart.getEType(); 281 Integer kvno = apReqMessg.ticket.encPart.getKeyVersionNumber(); 282 EncryptionKey[] keys = cred.getKrb5EncryptionKeys(apReqMessg.ticket.sname); 283 EncryptionKey dkey = EncryptionKey.findKey(encPartKeyType, kvno, keys); 284 285 if (dkey == null) { 286 throw new KrbException(Krb5.API_INVALID_ARG, 287 "Cannot find key of appropriate type to decrypt AP REP - " + 288 EType.toString(encPartKeyType)); 289 } 290 291 byte[] bytes = apReqMessg.ticket.encPart.decrypt(dkey, 292 KeyUsage.KU_TICKET); 293 byte[] temp = apReqMessg.ticket.encPart.reset(bytes); 294 EncTicketPart enc_ticketPart = new EncTicketPart(temp); 295 296 checkPermittedEType(enc_ticketPart.key.getEType()); 297 298 byte[] bytes2 = apReqMessg.authenticator.decrypt(enc_ticketPart.key, 299 KeyUsage.KU_AP_REQ_AUTHENTICATOR); 300 byte[] temp2 = apReqMessg.authenticator.reset(bytes2); 301 authenticator = new Authenticator(temp2); 302 ctime = authenticator.ctime; 303 cusec = authenticator.cusec; 304 authenticator.ctime = 305 authenticator.ctime.withMicroSeconds(authenticator.cusec); 306 307 if (!authenticator.cname.equals(enc_ticketPart.cname)) { 308 throw new KrbApErrException(Krb5.KRB_AP_ERR_BADMATCH); 309 } 310 311 if (!authenticator.ctime.inClockSkew()) 312 throw new KrbApErrException(Krb5.KRB_AP_ERR_SKEW); 313 314 byte[] hash = md.digest(apReqMessg.authenticator.cipher); 315 char[] h = new char[hash.length * 2]; 316 for (int i=0; i<hash.length; i++) { 317 h[2*i] = hexConst[(hash[i]&0xff)>>4]; 318 h[2*i+1] = hexConst[hash[i]&0xf]; 319 } 320 AuthTimeWithHash time = new AuthTimeWithHash( 321 authenticator.cname.toString(), 322 apReqMessg.ticket.sname.toString(), 323 authenticator.ctime.getSeconds(), 324 authenticator.cusec, 325 new String(h)); 326 rcache.checkAndStore(KerberosTime.now(), time); 327 328 if (initiator != null) { 329 // sender host address 330 HostAddress sender = new HostAddress(initiator); 331 if (enc_ticketPart.caddr != null 332 && !enc_ticketPart.caddr.inList(sender)) { 333 if (DEBUG) { 334 System.out.println(">>> KrbApReq: initiator is " 335 + sender.getInetAddress() 336 + ", but caddr is " 337 + Arrays.toString( 338 enc_ticketPart.caddr.getInetAddresses())); 339 } 340 throw new KrbApErrException(Krb5.KRB_AP_ERR_BADADDR); 341 } 342 } 343 344 // XXX check for repeated authenticator 345 // if found 346 // throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT); 347 // else 348 // save authenticator to check for later 349 350 KerberosTime now = KerberosTime.now(); 351 352 if ((enc_ticketPart.starttime != null && 353 enc_ticketPart.starttime.greaterThanWRTClockSkew(now)) || 354 enc_ticketPart.flags.get(Krb5.TKT_OPTS_INVALID)) 355 throw new KrbApErrException(Krb5.KRB_AP_ERR_TKT_NYV); 356 357 // if the current time is later than end time by more 358 // than the allowable clock skew, throws ticket expired exception. 359 if (enc_ticketPart.endtime != null && 360 now.greaterThanWRTClockSkew(enc_ticketPart.endtime)) { 361 throw new KrbApErrException(Krb5.KRB_AP_ERR_TKT_EXPIRED); 362 } 363 364 creds = new Credentials( 365 apReqMessg.ticket, 366 authenticator.cname, 367 apReqMessg.ticket.sname, 368 enc_ticketPart.key, 369 enc_ticketPart.flags, 370 enc_ticketPart.authtime, 371 enc_ticketPart.starttime, 372 enc_ticketPart.endtime, 373 enc_ticketPart.renewTill, 374 enc_ticketPart.caddr, 375 enc_ticketPart.authorizationData); 376 if (DEBUG) { 377 System.out.println(">>> KrbApReq: authenticate succeed."); 378 } 379 } 380 381 /** 382 * Returns the credentials that are contained in the ticket that 383 * is part of this this AP-REP. 384 */ 385 public Credentials getCreds() { 386 return creds; 387 } 388 389 KerberosTime getCtime() { 390 if (ctime != null) 391 return ctime; 392 return authenticator.ctime; 393 } 394 395 int cusec() { 396 return cusec; 397 } 398 399 APOptions getAPOptions() throws KrbException, IOException { 400 if (apReqMessg == null) 401 decode(); 402 if (apReqMessg != null) 403 return apReqMessg.apOptions; 404 return null; 405 } 406 407 /** 408 * Returns true if mutual authentication is required and hence an 409 * AP-REP will need to be generated. 410 * @throws KrbException 411 * @throws IOException 412 */ 413 public boolean getMutualAuthRequired() throws KrbException, IOException { 414 if (apReqMessg == null) 415 decode(); 416 if (apReqMessg != null) 417 return apReqMessg.apOptions.get(Krb5.AP_OPTS_MUTUAL_REQUIRED); 418 return false; 419 } 420 421 boolean useSessionKey() throws KrbException, IOException { 422 if (apReqMessg == null) 423 decode(); 424 if (apReqMessg != null) 425 return apReqMessg.apOptions.get(Krb5.AP_OPTS_USE_SESSION_KEY); 426 return false; 427 } 428 429 /** 430 * Returns the optional subkey stored in the Authenticator for 431 * this message. Returns null if none is stored. 432 */ 433 public EncryptionKey getSubKey() { 434 // XXX Can authenticator be null 435 return authenticator.getSubKey(); 436 } 437 438 /** 439 * Returns the optional sequence number stored in the 440 * Authenticator for this message. Returns null if none is 441 * stored. 442 */ 443 public Integer getSeqNumber() { 444 // XXX Can authenticator be null 445 return authenticator.getSeqNumber(); 446 } 447 448 /** 449 * Returns the optional Checksum stored in the 450 * Authenticator for this message. Returns null if none is 451 * stored. 452 */ 453 public Checksum getChecksum() { 454 return authenticator.getChecksum(); 455 } 456 457 /** 458 * Returns the ASN.1 encoding that should be sent to the peer. 459 */ 460 public byte[] getMessage() { 461 return obuf; 462 } 463 464 /** 465 * Returns the principal name of the client that generated this 466 * message. 467 */ 468 public PrincipalName getClient() { 469 return creds.getClient(); 470 } 471 472 private void createMessage(APOptions apOptions, 473 Ticket ticket, 474 EncryptionKey key, 475 PrincipalName cname, 476 Checksum cksum, 477 KerberosTime ctime, 478 EncryptionKey subKey, 479 SeqNumber seqNumber, 480 AuthorizationData authorizationData, 481 int usage) 482 throws Asn1Exception, IOException, 483 KdcErrException, KrbCryptoException { 484 485 Integer seqno = null; 486 487 if (seqNumber != null) 488 seqno = new Integer(seqNumber.current()); 489 490 authenticator = 491 new Authenticator(cname, 492 cksum, 493 ctime.getMicroSeconds(), 494 ctime, 495 subKey, 496 seqno, 497 authorizationData); 498 499 byte[] temp = authenticator.asn1Encode(); 500 501 EncryptedData encAuthenticator = 502 new EncryptedData(key, temp, usage); 503 504 apReqMessg = 505 new APReq(apOptions, ticket, encAuthenticator); 506 } 507 508 // Check that key is one of the permitted types 509 private static void checkPermittedEType(int target) throws KrbException { 510 int[] etypes = EType.getDefaults("permitted_enctypes"); 511 if (!EType.isSupported(target, etypes)) { 512 throw new KrbException(EType.toString(target) + 513 " encryption type not in permitted_enctypes list"); 514 } 515 } 516 }