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