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.Krb5; 35 import sun.security.util.*; 36 import java.io.IOException; 37 import java.util.*; 38 39 import sun.security.krb5.internal.util.KerberosString; 40 41 /** 42 * Implements the ASN.1 Realm type. 43 * 44 * {@code Realm ::= GeneralString} 45 * 46 * This class is immutable. 47 */ 48 public class Realm implements Cloneable { 49 private final String realm; // not null nor empty 50 51 public Realm(String name) throws RealmException { 52 realm = parseRealm(name); 53 } 54 55 public static Realm getDefault() throws RealmException { 56 try { 57 return new Realm(Config.getInstance().getDefaultRealm()); 58 } catch (RealmException re) { 59 throw re; 60 } catch (KrbException ke) { 61 throw new RealmException(ke); 62 } 63 } 64 65 // Immutable class, no need to clone 66 public Object clone() { 67 return this; 68 } 69 70 public boolean equals(Object obj) { 71 if (this == obj) { 72 return true; 73 } 74 75 if (!(obj instanceof Realm)) { 76 return false; 77 } 78 79 Realm that = (Realm)obj; 80 return this.realm.equals(that.realm); 81 } 82 83 public int hashCode() { 84 return realm.hashCode(); 85 } 86 87 /** 88 * Constructs a Realm object. 89 * @param encoding a Der-encoded data. 90 * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data. 91 * @exception IOException if an I/O error occurs while reading encoded data. 92 * @exception RealmException if an error occurs while parsing a Realm object. 93 */ 94 public Realm(DerValue encoding) 95 throws Asn1Exception, RealmException, IOException { 96 if (encoding == null) { 97 throw new IllegalArgumentException("encoding can not be null"); 98 } 99 realm = new KerberosString(encoding).toString(); 100 if (realm == null || realm.length() == 0) 101 throw new RealmException(Krb5.REALM_NULL); 102 if (!isValidRealmString(realm)) 103 throw new RealmException(Krb5.REALM_ILLCHAR); 104 } 105 106 public String toString() { 107 return realm; 108 } 109 110 // Extract realm from a string like dummy@REALM 111 public static String parseRealmAtSeparator(String name) 112 throws RealmException { 113 if (name == null) { 114 throw new IllegalArgumentException 115 ("null input name is not allowed"); 116 } 117 String temp = new String(name); 118 String result = null; 119 int i = 0; 120 while (i < temp.length()) { 121 if (temp.charAt(i) == PrincipalName.NAME_REALM_SEPARATOR) { 122 if (i == 0 || temp.charAt(i - 1) != '\\') { 123 if (i + 1 < temp.length()) { 124 result = temp.substring(i + 1, temp.length()); 125 } else { 126 throw new IllegalArgumentException 127 ("empty realm part not allowed"); 128 } 129 break; 130 } 131 } 132 i++; 133 } 134 if (result != null) { 135 if (result.length() == 0) 136 throw new RealmException(Krb5.REALM_NULL); 137 if (!isValidRealmString(result)) 138 throw new RealmException(Krb5.REALM_ILLCHAR); 139 } 140 return result; 141 } 142 143 public static String parseRealmComponent(String name) { 144 if (name == null) { 145 throw new IllegalArgumentException 146 ("null input name is not allowed"); 147 } 148 String temp = new String(name); 149 String result = null; 150 int i = 0; 151 while (i < temp.length()) { 152 if (temp.charAt(i) == PrincipalName.REALM_COMPONENT_SEPARATOR) { 153 if (i == 0 || temp.charAt(i - 1) != '\\') { 154 if (i + 1 < temp.length()) 155 result = temp.substring(i + 1, temp.length()); 156 break; 157 } 158 } 159 i++; 160 } 161 return result; 162 } 163 164 protected static String parseRealm(String name) throws RealmException { 165 String result = parseRealmAtSeparator(name); 166 if (result == null) 167 result = name; 168 if (result == null || result.length() == 0) 169 throw new RealmException(Krb5.REALM_NULL); 170 if (!isValidRealmString(result)) 171 throw new RealmException(Krb5.REALM_ILLCHAR); 172 return result; 173 } 174 175 // This is protected because the definition of a realm 176 // string is fixed 177 protected static boolean isValidRealmString(String name) { 178 if (name == null) 179 return false; 180 if (name.length() == 0) 181 return false; 182 for (int i = 0; i < name.length(); i++) { 183 if (name.charAt(i) == '/' || 184 name.charAt(i) == ':' || 185 name.charAt(i) == '\0') { 186 return false; 187 } 188 } 189 return true; 190 } 191 192 /** 193 * Encodes a Realm object. 194 * @return the byte array of encoded KrbCredInfo object. 195 * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data. 196 * @exception IOException if an I/O error occurs while reading encoded data. 197 * 198 */ 199 public byte[] asn1Encode() throws Asn1Exception, IOException { 200 DerOutputStream out = new DerOutputStream(); 201 out.putDerValue(new KerberosString(this.realm).toDerValue()); 202 return out.toByteArray(); 203 } 204 205 206 /** 207 * Parse (unmarshal) a realm from a DER input stream. This form 208 * parsing might be used when expanding a value which is part of 209 * a constructed sequence and uses explicitly tagged type. 210 * 211 * @exception Asn1Exception on error. 212 * @param data the Der input stream value, which contains one or more marshaled value. 213 * @param explicitTag tag number. 214 * @param optional indicate if this data field is optional 215 * @return an instance of Realm. 216 * 217 */ 218 public static Realm parse(DerInputStream data, byte explicitTag, boolean optional) 219 throws Asn1Exception, IOException, RealmException { 220 if ((optional) && (((byte)data.peekByte() & (byte)0x1F) != explicitTag)) { 221 return null; 222 } 223 DerValue der = data.getDerValue(); 224 if (explicitTag != (der.getTag() & (byte)0x1F)) { 225 throw new Asn1Exception(Krb5.ASN1_BAD_ID); 226 } else { 227 DerValue subDer = der.getData().getDerValue(); 228 return new Realm(subDer); 229 } 230 } 231 232 /** 233 * Returns an array of realms that may be traversed to obtain 234 * a TGT from the initiating realm cRealm to the target realm 235 * sRealm. 236 * <br> 237 * This method would read [capaths] to create a path, or generate a 238 * hierarchical path if [capaths] does not contain a sub-stanza for cRealm 239 * or the sub-stanza does not contain a tag for sRealm. 240 * <br> 241 * The returned list would never be null, and it always contains 242 * cRealm as the head entry. sRealm is not included as the tail. 243 * 244 * @param cRealm the initiating realm, not null 245 * @param sRealm the target realm, not null, not equals to cRealm 246 * @return array of realms including at least cRealm as the first 247 * element 248 */ 249 public static String[] getRealmsList(String cRealm, String sRealm) { 250 try { 251 // Try [capaths] 252 return parseCapaths(cRealm, sRealm); 253 } catch (KrbException ke) { 254 // Now assume the realms are organized hierarchically. 255 return parseHierarchy(cRealm, sRealm); 256 } 257 } 258 259 /** 260 * Parses the [capaths] stanza of the configuration file for a 261 * list of realms to traverse to obtain credentials from the 262 * initiating realm cRealm to the target realm sRealm. 263 * 264 * For a given client realm C there is a tag C in [capaths] whose 265 * subtag S has a value which is a (possibly partial) path from C 266 * to S. When the path is partial, it contains only the tail of the 267 * full path. Values of other subtags will be used to build the full 268 * path. The value "." means a direct path from C to S. If realm S 269 * does not appear as a subtag, there is no path defined here. 270 * 271 * The implementation ignores all values which equals to C or S, or 272 * a "." in multiple values, or any duplicated realm names. 273 * 274 * When a path value has more than two realms, they can be specified 275 * with multiple key-value pairs each having a single value, but the 276 * order must not change. 277 * 278 * For example: 279 * 280 * [capaths] 281 * TIVOLI.COM = { 282 * IBM.COM = IBM_LDAPCENTRAL.COM MOONLITE.ORG 283 * IBM_LDAPCENTRAL.COM = LDAPCENTRAL.NET 284 * LDAPCENTRAL.NET = . 285 * } 286 * 287 * TIVOLI.COM has a direct path to LDAPCENTRAL.NET, which has a direct 288 * path to IBM_LDAPCENTRAL.COM. It also has a partial path to IBM.COM 289 * being "IBM_LDAPCENTRAL.COM MOONLITE.ORG". Merging these info together, 290 * a full path from TIVOLI.COM to IBM.COM will be 291 * 292 * TIVOLI.COM -> LDAPCENTRAL.NET -> IBM_LDAPCENTRAL.COM 293 * -> IBM_LDAPCENTRAL.COM -> MOONLITE.ORG 294 * 295 * Please note the sRealm IBM.COM does not appear in the path. 296 * 297 * @param cRealm the initiating realm 298 * @param sRealm the target realm, not the same as cRealm 299 * @return array of realms including at least cRealm as the first 300 * element 301 * @throws KrbException if the config does not contain a sub-stanza 302 * for cRealm in [capaths] or the sub-stanza does not contain 303 * sRealm as a tag 304 */ 305 private static String[] parseCapaths(String cRealm, String sRealm) 306 throws KrbException { 307 308 // This line could throw a KrbException 309 Config cfg = Config.getInstance(); 310 311 if (!cfg.exists("capaths", cRealm, sRealm)) { 312 throw new KrbException("No conf"); 313 } 314 315 LinkedList<String> path = new LinkedList<>(); 316 317 String head = sRealm; 318 while (true) { 319 String value = cfg.getAll("capaths", cRealm, head); 320 if (value == null) { 321 break; 322 } 323 String[] more = value.split("\\s+"); 324 boolean changed = false; 325 for (int i=more.length-1; i>=0; i--) { 326 if (path.contains(more[i]) 327 || more[i].equals(".") 328 || more[i].equals(cRealm) 329 || more[i].equals(sRealm) 330 || more[i].equals(head)) { 331 // Ignore invalid values 332 continue; 333 } 334 changed = true; 335 path.addFirst(more[i]); 336 } 337 if (!changed) break; 338 head = path.getFirst(); 339 } 340 path.addFirst(cRealm); 341 return path.toArray(new String[path.size()]); 342 } 343 344 /** 345 * Build a list of realm that can be traversed 346 * to obtain credentials from the initiating realm cRealm 347 * for a service in the target realm sRealm. 348 * @param cRealm the initiating realm 349 * @param sRealm the target realm, not the same as cRealm 350 * @return array of realms including cRealm as the first element 351 */ 352 private static String[] parseHierarchy(String cRealm, String sRealm) { 353 354 String[] cComponents = cRealm.split("\\."); 355 String[] sComponents = sRealm.split("\\."); 356 357 int cPos = cComponents.length; 358 int sPos = sComponents.length; 359 360 boolean hasCommon = false; 361 for (sPos--, cPos--; sPos >=0 && cPos >= 0 && 362 sComponents[sPos].equals(cComponents[cPos]); 363 sPos--, cPos--) { 364 hasCommon = true; 365 } 366 367 // For those with common components: 368 // length pos 369 // SITES1.SALES.EXAMPLE.COM 4 1 370 // EVERYWHERE.EXAMPLE.COM 3 0 371 372 // For those without common components: 373 // length pos 374 // DEVEL.EXAMPLE.COM 3 2 375 // PROD.EXAMPLE.ORG 3 2 376 377 LinkedList<String> path = new LinkedList<>(); 378 379 // Un-common ones for client side 380 for (int i=0; i<=cPos; i++) { 381 path.addLast(subStringFrom(cComponents, i)); 382 } 383 384 // Common one 385 if (hasCommon) { 386 path.addLast(subStringFrom(cComponents, cPos+1)); 387 } 388 389 // Un-common ones for server side 390 for (int i=sPos; i>=0; i--) { 391 path.addLast(subStringFrom(sComponents, i)); 392 } 393 394 // Remove sRealm from path. Note that it might be added at last loop 395 // or as a common component, if sRealm is a parent of cRealm 396 path.removeLast(); 397 398 return path.toArray(new String[path.size()]); 399 } 400 401 /** 402 * Creates a realm name using components from the given position. 403 * For example, subStringFrom({"A", "B", "C"}, 1) is "B.C". 404 */ 405 private static String subStringFrom(String[] components, int from) { 406 StringBuilder sb = new StringBuilder(); 407 for (int i=from; i<components.length; i++) { 408 if (sb.length() != 0) sb.append('.'); 409 sb.append(components[i]); 410 } 411 return sb.toString(); 412 } 413 }