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 32package sun.security.krb5; 33 34import sun.security.krb5.internal.*; 35import sun.security.util.*; 36import java.net.*; 37import java.util.Vector; 38import java.util.Locale; 39import java.io.IOException; 40import java.math.BigInteger; 41import java.util.Arrays; 42import sun.security.krb5.internal.ccache.CCacheOutputStream; 43import 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 */ 59public 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 jdk.internal.misc.Unsafe UNSAFE; 191 static { 192 try { 193 jdk.internal.misc.Unsafe unsafe = jdk.internal.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 if (hostName.endsWith(".")) { 428 hostName = hostName.substring(0, hostName.length() - 1); 429 } 430 nameParts[1] = hostName.toLowerCase(Locale.ENGLISH); 431 } 432 nameStrings = nameParts; 433 nameType = type; 434 435 if (realm != null) { 436 nameRealm = new Realm(realm); 437 } else { 438 // We will try to get realm name from the mapping in 439 // the configuration. If it is not specified 440 // we will use the default realm. This nametype does 441 // not allow a realm to be specified. The name string must of 442 // the form service@host and this is internally changed into 443 // service/host by Kerberos 444 String mapRealm = mapHostToRealm(nameParts[1]); 445 if (mapRealm != null) { 446 nameRealm = new Realm(mapRealm); 447 } else { 448 nameRealm = Realm.getDefault(); 449 } 450 } 451 break; 452 case KRB_NT_UNKNOWN: 453 case KRB_NT_PRINCIPAL: 454 case KRB_NT_SRV_INST: 455 case KRB_NT_SRV_XHST: 456 case KRB_NT_UID: 457 nameStrings = nameParts; 458 nameType = type; 459 if (realm != null) { 460 nameRealm = new Realm(realm); 461 } else { 462 nameRealm = Realm.getDefault(); 463 } 464 break; 465 default: 466 throw new IllegalArgumentException("Illegal name type"); 467 } 468 } 469 470 public PrincipalName(String name, int type) throws RealmException { 471 this(name, type, (String)null); 472 } 473 474 public PrincipalName(String name) throws RealmException { 475 this(name, KRB_NT_UNKNOWN); 476 } 477 478 public PrincipalName(String name, String realm) throws RealmException { 479 this(name, KRB_NT_UNKNOWN, realm); 480 } 481 482 public static PrincipalName tgsService(String r1, String r2) 483 throws KrbException { 484 return new PrincipalName(PrincipalName.KRB_NT_SRV_INST, 485 new String[] {PrincipalName.TGS_DEFAULT_SRV_NAME, r1}, 486 new Realm(r2)); 487 } 488 489 public String getRealmAsString() { 490 return getRealmString(); 491 } 492 493 public String getPrincipalNameAsString() { 494 StringBuilder temp = new StringBuilder(nameStrings[0]); 495 for (int i = 1; i < nameStrings.length; i++) 496 temp.append(nameStrings[i]); 497 return temp.toString(); 498 } 499 500 public int hashCode() { 501 return toString().hashCode(); 502 } 503 504 public String getName() { 505 return toString(); 506 } 507 508 public int getNameType() { 509 return nameType; 510 } 511 512 public String[] getNameStrings() { 513 return nameStrings.clone(); 514 } 515 516 public byte[][] toByteArray() { 517 byte[][] result = new byte[nameStrings.length][]; 518 for (int i = 0; i < nameStrings.length; i++) { 519 result[i] = new byte[nameStrings[i].length()]; 520 result[i] = nameStrings[i].getBytes(); 521 } 522 return result; 523 } 524 525 public String getRealmString() { 526 return nameRealm.toString(); 527 } 528 529 public Realm getRealm() { 530 return nameRealm; 531 } 532 533 public String getSalt() { 534 if (salt == null) { 535 StringBuilder salt = new StringBuilder(); 536 salt.append(nameRealm.toString()); 537 for (int i = 0; i < nameStrings.length; i++) { 538 salt.append(nameStrings[i]); 539 } 540 return salt.toString(); 541 } 542 return salt; 543 } 544 545 public String toString() { 546 StringBuilder str = new StringBuilder(); 547 for (int i = 0; i < nameStrings.length; i++) { 548 if (i > 0) 549 str.append("/"); 550 str.append(nameStrings[i]); 551 } 552 str.append("@"); 553 str.append(nameRealm.toString()); 554 return str.toString(); 555 } 556 557 public String getNameString() { 558 StringBuilder str = new StringBuilder(); 559 for (int i = 0; i < nameStrings.length; i++) { 560 if (i > 0) 561 str.append("/"); 562 str.append(nameStrings[i]); 563 } 564 return str.toString(); 565 } 566 567 /** 568 * Encodes a <code>PrincipalName</code> object. Note that only the type and 569 * names are encoded. To encode the realm, call getRealm().asn1Encode(). 570 * @return the byte array of the encoded PrncipalName object. 571 * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data. 572 * @exception IOException if an I/O error occurs while reading encoded data. 573 * 574 */ 575 public byte[] asn1Encode() throws Asn1Exception, IOException { 576 DerOutputStream bytes = new DerOutputStream(); 577 DerOutputStream temp = new DerOutputStream(); 578 BigInteger bint = BigInteger.valueOf(this.nameType); 579 temp.putInteger(bint); 580 bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x00), temp); 581 temp = new DerOutputStream(); 582 DerValue[] der = new DerValue[nameStrings.length]; 583 for (int i = 0; i < nameStrings.length; i++) { 584 der[i] = new KerberosString(nameStrings[i]).toDerValue(); 585 } 586 temp.putSequence(der); 587 bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x01), temp); 588 temp = new DerOutputStream(); 589 temp.write(DerValue.tag_Sequence, bytes); 590 return temp.toByteArray(); 591 } 592 593 594 /** 595 * Checks if two <code>PrincipalName</code> objects have identical values in their corresponding data fields. 596 * 597 * @param pname the other <code>PrincipalName</code> object. 598 * @return true if two have identical values, otherwise, return false. 599 */ 600 // It is used in <code>sun.security.krb5.internal.ccache</code> package. 601 public boolean match(PrincipalName pname) { 602 boolean matched = true; 603 //name type is just a hint, no two names can be the same ignoring name type. 604 // if (this.nameType != pname.nameType) { 605 // matched = false; 606 // } 607 if ((this.nameRealm != null) && (pname.nameRealm != null)) { 608 if (!(this.nameRealm.toString().equalsIgnoreCase(pname.nameRealm.toString()))) { 609 matched = false; 610 } 611 } 612 if (this.nameStrings.length != pname.nameStrings.length) { 613 matched = false; 614 } else { 615 for (int i = 0; i < this.nameStrings.length; i++) { 616 if (!(this.nameStrings[i].equalsIgnoreCase(pname.nameStrings[i]))) { 617 matched = false; 618 } 619 } 620 } 621 return matched; 622 } 623 624 /** 625 * Writes data field values of <code>PrincipalName</code> in FCC format to an output stream. 626 * 627 * @param cos a <code>CCacheOutputStream</code> for writing data. 628 * @exception IOException if an I/O exception occurs. 629 * @see sun.security.krb5.internal.ccache.CCacheOutputStream 630 */ 631 public void writePrincipal(CCacheOutputStream cos) throws IOException { 632 cos.write32(nameType); 633 cos.write32(nameStrings.length); 634 byte[] realmBytes = null; 635 realmBytes = nameRealm.toString().getBytes(); 636 cos.write32(realmBytes.length); 637 cos.write(realmBytes, 0, realmBytes.length); 638 byte[] bytes = null; 639 for (int i = 0; i < nameStrings.length; i++) { 640 bytes = nameStrings[i].getBytes(); 641 cos.write32(bytes.length); 642 cos.write(bytes, 0, bytes.length); 643 } 644 } 645 646 /** 647 * Returns the instance component of a name. 648 * In a multi-component name such as a KRB_NT_SRV_INST 649 * name, the second component is returned. 650 * Null is returned if there are not two or more 651 * components in the name. 652 * 653 * @return instance component of a multi-component name. 654 */ 655 public String getInstanceComponent() 656 { 657 if (nameStrings != null && nameStrings.length >= 2) 658 { 659 return new String(nameStrings[1]); 660 } 661 662 return null; 663 } 664 665 static String mapHostToRealm(String name) { 666 String result = null; 667 try { 668 String subname = null; 669 Config c = Config.getInstance(); 670 if ((result = c.get("domain_realm", name)) != null) 671 return result; 672 else { 673 for (int i = 1; i < name.length(); i++) { 674 if ((name.charAt(i) == '.') && (i != name.length() - 1)) { //mapping could be .ibm.com = AUSTIN.IBM.COM 675 subname = name.substring(i); 676 result = c.get("domain_realm", subname); 677 if (result != null) { 678 break; 679 } 680 else { 681 subname = name.substring(i + 1); //or mapping could be ibm.com = AUSTIN.IBM.COM 682 result = c.get("domain_realm", subname); 683 if (result != null) { 684 break; 685 } 686 } 687 } 688 } 689 } 690 } catch (KrbException e) { 691 } 692 return result; 693 } 694 695 public boolean isRealmDeduced() { 696 return realmDeduced; 697 } 698} 699