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