1 /* 2 * Copyright (c) 2000, 2013, 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.util.*; 36 import java.net.*; 37 import java.util.Vector; 38 import java.util.Locale; 39 import java.io.IOException; 40 import java.math.BigInteger; 41 import java.util.Arrays; 42 import sun.security.krb5.internal.ccache.CCacheOutputStream; 43 import sun.security.krb5.internal.util.KerberosString; 44 45 46 /** 47 * Implements the ASN.1 PrincipalName type and its realm in a single class. 48 * <pre>{@code 49 * Realm ::= KerberosString 50 * 51 * PrincipalName ::= SEQUENCE { 52 * name-type [0] Int32, 53 * name-string [1] SEQUENCE OF KerberosString 54 * } 55 * }</pre> 56 * This class is immutable. 57 * @see Realm 58 */ 59 public class PrincipalName implements Cloneable { 60 61 //name types 62 63 /** 64 * Name type not known 65 */ 66 public static final int KRB_NT_UNKNOWN = 0; 67 68 /** 69 * Just the name of the principal as in DCE, or for users 70 */ 71 public static final int KRB_NT_PRINCIPAL = 1; 72 73 /** 74 * Service and other unique instance (krbtgt) 75 */ 76 public static final int KRB_NT_SRV_INST = 2; 77 78 /** 79 * Service with host name as instance (telnet, rcommands) 80 */ 81 public static final int KRB_NT_SRV_HST = 3; 82 83 /** 84 * Service with host as remaining components 85 */ 86 public static final int KRB_NT_SRV_XHST = 4; 87 88 /** 89 * Unique ID 90 */ 91 public static final int KRB_NT_UID = 5; 92 93 /** 94 * TGS Name 95 */ 96 public static final String TGS_DEFAULT_SRV_NAME = "krbtgt"; 97 public static final int TGS_DEFAULT_NT = KRB_NT_SRV_INST; 98 99 public static final char NAME_COMPONENT_SEPARATOR = '/'; 100 public static final char NAME_REALM_SEPARATOR = '@'; 101 public static final char REALM_COMPONENT_SEPARATOR = '.'; 102 103 public static final String NAME_COMPONENT_SEPARATOR_STR = "/"; 104 public static final String NAME_REALM_SEPARATOR_STR = "@"; 105 public static final String REALM_COMPONENT_SEPARATOR_STR = "."; 106 107 // Instance fields. 108 109 /** 110 * The name type, from PrincipalName's name-type field. 111 */ 112 private final int nameType; 113 114 /** 115 * The name strings, from PrincipalName's name-strings field. This field 116 * must be neither null nor empty. Each entry of it must also be neither 117 * null nor empty. Make sure to clone the field when it's passed in or out. 118 */ 119 private final String[] nameStrings; 120 121 /** 122 * The realm this principal belongs to. 123 */ 124 private final Realm nameRealm; // not null 125 126 127 /** 128 * When constructing a PrincipalName, whether the realm is included in 129 * the input, or deduced from default realm or domain-realm mapping. 130 */ 131 private final boolean realmDeduced; 132 133 // cached default salt, not used in clone 134 private transient String salt = null; 135 136 // There are 3 basic constructors. All other constructors must call them. 137 // All basic constructors must call validateNameStrings. 138 // 1. From name components 139 // 2. From name 140 // 3. From DER encoding 141 142 /** 143 * Creates a PrincipalName. 144 */ 145 public PrincipalName(int nameType, String[] nameStrings, Realm nameRealm) { 146 if (nameRealm == null) { 147 throw new IllegalArgumentException("Null realm not allowed"); 148 } 149 validateNameStrings(nameStrings); 150 this.nameType = nameType; 151 this.nameStrings = nameStrings.clone(); 152 this.nameRealm = nameRealm; 153 this.realmDeduced = false; 154 } 155 156 // This method is called by Windows NativeCred.c 157 public PrincipalName(String[] nameParts, String realm) throws RealmException { 158 this(KRB_NT_UNKNOWN, nameParts, new Realm(realm)); 159 } 160 161 // Validate a nameStrings argument 162 private static void validateNameStrings(String[] ns) { 163 if (ns == null) { 164 throw new IllegalArgumentException("Null nameStrings not allowed"); 165 } 166 if (ns.length == 0) { 167 throw new IllegalArgumentException("Empty nameStrings not allowed"); 168 } 169 for (String s: ns) { 170 if (s == null) { 171 throw new IllegalArgumentException("Null nameString not allowed"); 172 } 173 if (s.isEmpty()) { 174 throw new IllegalArgumentException("Empty nameString not allowed"); 175 } 176 } 177 } 178 179 public Object clone() { 180 try { 181 PrincipalName pName = (PrincipalName) super.clone(); 182 UNSAFE.putObject(this, NAME_STRINGS_OFFSET, nameStrings.clone()); 183 return pName; 184 } catch (CloneNotSupportedException ex) { 185 throw new AssertionError("Should never happen"); 186 } 187 } 188 189 private static final long NAME_STRINGS_OFFSET; 190 private static final sun.misc.Unsafe UNSAFE; 191 static { 192 try { 193 sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe(); 194 NAME_STRINGS_OFFSET = unsafe.objectFieldOffset( 195 PrincipalName.class.getDeclaredField("nameStrings")); 196 UNSAFE = unsafe; 197 } catch (ReflectiveOperationException e) { 198 throw new Error(e); 199 } 200 } 201 202 @Override 203 public boolean equals(Object o) { 204 if (this == o) { 205 return true; 206 } 207 if (o instanceof PrincipalName) { 208 PrincipalName other = (PrincipalName)o; 209 return nameRealm.equals(other.nameRealm) && 210 Arrays.equals(nameStrings, other.nameStrings); 211 } 212 return false; 213 } 214 215 /** 216 * Returns the ASN.1 encoding of the 217 * <pre>{@code 218 * PrincipalName ::= SEQUENCE { 219 * name-type [0] Int32, 220 * name-string [1] SEQUENCE OF KerberosString 221 * } 222 * 223 * KerberosString ::= GeneralString (IA5String) 224 * }</pre> 225 * 226 * <p> 227 * This definition reflects the Network Working Group RFC 4120 228 * specification available at 229 * <a href="http://www.ietf.org/rfc/rfc4120.txt"> 230 * http://www.ietf.org/rfc/rfc4120.txt</a>. 231 * 232 * @param encoding DER-encoded PrincipalName (without Realm) 233 * @param realm the realm for this name 234 * @exception Asn1Exception if an error occurs while decoding 235 * an ASN1 encoded data. 236 * @exception Asn1Exception if there is an ASN1 encoding error 237 * @exception IOException if an I/O error occurs 238 * @exception IllegalArgumentException if encoding is null 239 * reading encoded data. 240 */ 241 public PrincipalName(DerValue encoding, Realm realm) 242 throws Asn1Exception, IOException { 243 if (realm == null) { 244 throw new IllegalArgumentException("Null realm not allowed"); 245 } 246 realmDeduced = false; 247 nameRealm = realm; 248 DerValue der; 249 if (encoding == null) { 250 throw new IllegalArgumentException("Null encoding not allowed"); 251 } 252 if (encoding.getTag() != DerValue.tag_Sequence) { 253 throw new Asn1Exception(Krb5.ASN1_BAD_ID); 254 } 255 der = encoding.getData().getDerValue(); 256 if ((der.getTag() & 0x1F) == 0x00) { 257 BigInteger bint = der.getData().getBigInteger(); 258 nameType = bint.intValue(); 259 } else { 260 throw new Asn1Exception(Krb5.ASN1_BAD_ID); 261 } 262 der = encoding.getData().getDerValue(); 263 if ((der.getTag() & 0x01F) == 0x01) { 264 DerValue subDer = der.getData().getDerValue(); 265 if (subDer.getTag() != DerValue.tag_SequenceOf) { 266 throw new Asn1Exception(Krb5.ASN1_BAD_ID); 267 } 268 Vector<String> v = new Vector<>(); 269 DerValue subSubDer; 270 while(subDer.getData().available() > 0) { 271 subSubDer = subDer.getData().getDerValue(); 272 String namePart = new KerberosString(subSubDer).toString(); 273 v.addElement(namePart); 274 } 275 nameStrings = new String[v.size()]; 276 v.copyInto(nameStrings); 277 validateNameStrings(nameStrings); 278 } else { 279 throw new Asn1Exception(Krb5.ASN1_BAD_ID); 280 } 281 } 282 283 /** 284 * Parse (unmarshal) a <code>PrincipalName</code> from a DER 285 * input stream. This form 286 * parsing might be used when expanding a value which is part of 287 * a constructed sequence and uses explicitly tagged type. 288 * 289 * @exception Asn1Exception on error. 290 * @param data the Der input stream value, which contains one or 291 * more marshaled value. 292 * @param explicitTag tag number. 293 * @param optional indicate if this data field is optional 294 * @param realm the realm for the name 295 * @return an instance of <code>PrincipalName</code>, or null if the 296 * field is optional and missing. 297 */ 298 public static PrincipalName parse(DerInputStream data, 299 byte explicitTag, boolean 300 optional, 301 Realm realm) 302 throws Asn1Exception, IOException, RealmException { 303 304 if ((optional) && (((byte)data.peekByte() & (byte)0x1F) != 305 explicitTag)) 306 return null; 307 DerValue der = data.getDerValue(); 308 if (explicitTag != (der.getTag() & (byte)0x1F)) { 309 throw new Asn1Exception(Krb5.ASN1_BAD_ID); 310 } else { 311 DerValue subDer = der.getData().getDerValue(); 312 if (realm == null) { 313 realm = Realm.getDefault(); 314 } 315 return new PrincipalName(subDer, realm); 316 } 317 } 318 319 320 // XXX Error checkin consistent with MIT krb5_parse_name 321 // Code repetition, realm parsed again by class Realm 322 private static String[] parseName(String name) { 323 324 Vector<String> tempStrings = new Vector<>(); 325 String temp = name; 326 int i = 0; 327 int componentStart = 0; 328 String component; 329 330 while (i < temp.length()) { 331 if (temp.charAt(i) == NAME_COMPONENT_SEPARATOR) { 332 /* 333 * If this separator is escaped then don't treat it 334 * as a separator 335 */ 336 if (i > 0 && temp.charAt(i - 1) == '\\') { 337 temp = temp.substring(0, i - 1) + 338 temp.substring(i, temp.length()); 339 continue; 340 } 341 else { 342 if (componentStart <= i) { 343 component = temp.substring(componentStart, i); 344 tempStrings.addElement(component); 345 } 346 componentStart = i + 1; 347 } 348 } else { 349 if (temp.charAt(i) == NAME_REALM_SEPARATOR) { 350 /* 351 * If this separator is escaped then don't treat it 352 * as a separator 353 */ 354 if (i > 0 && temp.charAt(i - 1) == '\\') { 355 temp = temp.substring(0, i - 1) + 356 temp.substring(i, temp.length()); 357 continue; 358 } else { 359 if (componentStart < i) { 360 component = temp.substring(componentStart, i); 361 tempStrings.addElement(component); 362 } 363 componentStart = i + 1; 364 break; 365 } 366 } 367 } 368 i++; 369 } 370 371 if (i == temp.length()) { 372 component = temp.substring(componentStart, i); 373 tempStrings.addElement(component); 374 } 375 376 String[] result = new String[tempStrings.size()]; 377 tempStrings.copyInto(result); 378 return result; 379 } 380 381 /** 382 * Constructs a PrincipalName from a string. 383 * @param name the name 384 * @param type the type 385 * @param realm the realm, null if not known. Note that when realm is not 386 * null, it will be always used even if there is a realm part in name. When 387 * realm is null, will read realm part from name, or try to map a realm 388 * (for KRB_NT_SRV_HST), or use the default realm, or fail 389 * @throws RealmException 390 */ 391 public PrincipalName(String name, int type, String realm) 392 throws RealmException { 393 if (name == null) { 394 throw new IllegalArgumentException("Null name not allowed"); 395 } 396 String[] nameParts = parseName(name); 397 validateNameStrings(nameParts); 398 if (realm == null) { 399 realm = Realm.parseRealmAtSeparator(name); 400 } 401 402 // No realm info from parameter and string, must deduce later 403 realmDeduced = realm == null; 404 405 switch (type) { 406 case KRB_NT_SRV_HST: 407 if (nameParts.length >= 2) { 408 String hostName = nameParts[1]; 409 try { 410 // RFC4120 does not recommend canonicalizing a hostname. 411 // However, for compatibility reason, we will try 412 // canonicalize it and see if the output looks better. 413 414 String canonicalized = (InetAddress.getByName(hostName)). 415 getCanonicalHostName(); 416 417 // Looks if canonicalized is a longer format of hostName, 418 // we accept cases like 419 // bunny -> bunny.rabbit.hole 420 if (canonicalized.toLowerCase(Locale.ENGLISH).startsWith( 421 hostName.toLowerCase(Locale.ENGLISH)+".")) { 422 hostName = canonicalized; 423 } 424 } catch (UnknownHostException | SecurityException e) { 425 // not canonicalized or no permission to do so, use old 426 } 427 nameParts[1] = hostName.toLowerCase(Locale.ENGLISH); 428 } 429 nameStrings = nameParts; 430 nameType = type; 431 432 if (realm != null) { 433 nameRealm = new Realm(realm); 434 } else { 435 // We will try to get realm name from the mapping in 436 // the configuration. If it is not specified 437 // we will use the default realm. This nametype does 438 // not allow a realm to be specified. The name string must of 439 // the form service@host and this is internally changed into 440 // service/host by Kerberos 441 String mapRealm = mapHostToRealm(nameParts[1]); 442 if (mapRealm != null) { 443 nameRealm = new Realm(mapRealm); 444 } else { 445 nameRealm = Realm.getDefault(); 446 } 447 } 448 break; 449 case KRB_NT_UNKNOWN: 450 case KRB_NT_PRINCIPAL: 451 case KRB_NT_SRV_INST: 452 case KRB_NT_SRV_XHST: 453 case KRB_NT_UID: 454 nameStrings = nameParts; 455 nameType = type; 456 if (realm != null) { 457 nameRealm = new Realm(realm); 458 } else { 459 nameRealm = Realm.getDefault(); 460 } 461 break; 462 default: 463 throw new IllegalArgumentException("Illegal name type"); 464 } 465 } 466 467 public PrincipalName(String name, int type) throws RealmException { 468 this(name, type, (String)null); 469 } 470 471 public PrincipalName(String name) throws RealmException { 472 this(name, KRB_NT_UNKNOWN); 473 } 474 475 public PrincipalName(String name, String realm) throws RealmException { 476 this(name, KRB_NT_UNKNOWN, realm); 477 } 478 479 public static PrincipalName tgsService(String r1, String r2) 480 throws KrbException { 481 return new PrincipalName(PrincipalName.KRB_NT_SRV_INST, 482 new String[] {PrincipalName.TGS_DEFAULT_SRV_NAME, r1}, 483 new Realm(r2)); 484 } 485 486 public String getRealmAsString() { 487 return getRealmString(); 488 } 489 490 public String getPrincipalNameAsString() { 491 StringBuilder temp = new StringBuilder(nameStrings[0]); 492 for (int i = 1; i < nameStrings.length; i++) 493 temp.append(nameStrings[i]); 494 return temp.toString(); 495 } 496 497 public int hashCode() { 498 return toString().hashCode(); 499 } 500 501 public String getName() { 502 return toString(); 503 } 504 505 public int getNameType() { 506 return nameType; 507 } 508 509 public String[] getNameStrings() { 510 return nameStrings.clone(); 511 } 512 513 public byte[][] toByteArray() { 514 byte[][] result = new byte[nameStrings.length][]; 515 for (int i = 0; i < nameStrings.length; i++) { 516 result[i] = new byte[nameStrings[i].length()]; 517 result[i] = nameStrings[i].getBytes(); 518 } 519 return result; 520 } 521 522 public String getRealmString() { 523 return nameRealm.toString(); 524 } 525 526 public Realm getRealm() { 527 return nameRealm; 528 } 529 530 public String getSalt() { 531 if (salt == null) { 532 StringBuilder salt = new StringBuilder(); 533 salt.append(nameRealm.toString()); 534 for (int i = 0; i < nameStrings.length; i++) { 535 salt.append(nameStrings[i]); 536 } 537 return salt.toString(); 538 } 539 return salt; 540 } 541 542 public String toString() { 543 StringBuilder str = new StringBuilder(); 544 for (int i = 0; i < nameStrings.length; i++) { 545 if (i > 0) 546 str.append("/"); 547 str.append(nameStrings[i]); 548 } 549 str.append("@"); 550 str.append(nameRealm.toString()); 551 return str.toString(); 552 } 553 554 public String getNameString() { 555 StringBuilder str = new StringBuilder(); 556 for (int i = 0; i < nameStrings.length; i++) { 557 if (i > 0) 558 str.append("/"); 559 str.append(nameStrings[i]); 560 } 561 return str.toString(); 562 } 563 564 /** 565 * Encodes a <code>PrincipalName</code> object. Note that only the type and 566 * names are encoded. To encode the realm, call getRealm().asn1Encode(). 567 * @return the byte array of the encoded PrncipalName object. 568 * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data. 569 * @exception IOException if an I/O error occurs while reading encoded data. 570 * 571 */ 572 public byte[] asn1Encode() throws Asn1Exception, IOException { 573 DerOutputStream bytes = new DerOutputStream(); 574 DerOutputStream temp = new DerOutputStream(); 575 BigInteger bint = BigInteger.valueOf(this.nameType); 576 temp.putInteger(bint); 577 bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x00), temp); 578 temp = new DerOutputStream(); 579 DerValue[] der = new DerValue[nameStrings.length]; 580 for (int i = 0; i < nameStrings.length; i++) { 581 der[i] = new KerberosString(nameStrings[i]).toDerValue(); 582 } 583 temp.putSequence(der); 584 bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x01), temp); 585 temp = new DerOutputStream(); 586 temp.write(DerValue.tag_Sequence, bytes); 587 return temp.toByteArray(); 588 } 589 590 591 /** 592 * Checks if two <code>PrincipalName</code> objects have identical values in their corresponding data fields. 593 * 594 * @param pname the other <code>PrincipalName</code> object. 595 * @return true if two have identical values, otherwise, return false. 596 */ 597 // It is used in <code>sun.security.krb5.internal.ccache</code> package. 598 public boolean match(PrincipalName pname) { 599 boolean matched = true; 600 //name type is just a hint, no two names can be the same ignoring name type. 601 // if (this.nameType != pname.nameType) { 602 // matched = false; 603 // } 604 if ((this.nameRealm != null) && (pname.nameRealm != null)) { 605 if (!(this.nameRealm.toString().equalsIgnoreCase(pname.nameRealm.toString()))) { 606 matched = false; 607 } 608 } 609 if (this.nameStrings.length != pname.nameStrings.length) { 610 matched = false; 611 } else { 612 for (int i = 0; i < this.nameStrings.length; i++) { 613 if (!(this.nameStrings[i].equalsIgnoreCase(pname.nameStrings[i]))) { 614 matched = false; 615 } 616 } 617 } 618 return matched; 619 } 620 621 /** 622 * Writes data field values of <code>PrincipalName</code> in FCC format to an output stream. 623 * 624 * @param cos a <code>CCacheOutputStream</code> for writing data. 625 * @exception IOException if an I/O exception occurs. 626 * @see sun.security.krb5.internal.ccache.CCacheOutputStream 627 */ 628 public void writePrincipal(CCacheOutputStream cos) throws IOException { 629 cos.write32(nameType); 630 cos.write32(nameStrings.length); 631 byte[] realmBytes = null; 632 realmBytes = nameRealm.toString().getBytes(); 633 cos.write32(realmBytes.length); 634 cos.write(realmBytes, 0, realmBytes.length); 635 byte[] bytes = null; 636 for (int i = 0; i < nameStrings.length; i++) { 637 bytes = nameStrings[i].getBytes(); 638 cos.write32(bytes.length); 639 cos.write(bytes, 0, bytes.length); 640 } 641 } 642 643 /** 644 * Returns the instance component of a name. 645 * In a multi-component name such as a KRB_NT_SRV_INST 646 * name, the second component is returned. 647 * Null is returned if there are not two or more 648 * components in the name. 649 * 650 * @return instance component of a multi-component name. 651 */ 652 public String getInstanceComponent() 653 { 654 if (nameStrings != null && nameStrings.length >= 2) 655 { 656 return new String(nameStrings[1]); 657 } 658 659 return null; 660 } 661 662 static String mapHostToRealm(String name) { 663 String result = null; 664 try { 665 String subname = null; 666 Config c = Config.getInstance(); 667 if ((result = c.get("domain_realm", name)) != null) 668 return result; 669 else { 670 for (int i = 1; i < name.length(); i++) { 671 if ((name.charAt(i) == '.') && (i != name.length() - 1)) { //mapping could be .ibm.com = AUSTIN.IBM.COM 672 subname = name.substring(i); 673 result = c.get("domain_realm", subname); 674 if (result != null) { 675 break; 676 } 677 else { 678 subname = name.substring(i + 1); //or mapping could be ibm.com = AUSTIN.IBM.COM 679 result = c.get("domain_realm", subname); 680 if (result != null) { 681 break; 682 } 683 } 684 } 685 } 686 } 687 } catch (KrbException e) { 688 } 689 return result; 690 } 691 692 public boolean isRealmDeduced() { 693 return realmDeduced; 694 } 695 }