1 /*
   2  * Copyright (c) 2000, 2018, 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.internal;
  33 
  34 import java.io.ObjectOutputStream;
  35 import sun.security.krb5.PrincipalName;
  36 import sun.security.krb5.Checksum;
  37 import sun.security.krb5.Asn1Exception;
  38 import sun.security.krb5.Realm;
  39 import sun.security.krb5.RealmException;
  40 import sun.security.util.*;
  41 import java.io.IOException;
  42 import java.io.ObjectInputStream;
  43 import java.math.BigInteger;
  44 import java.util.ArrayList;
  45 import java.util.Arrays;
  46 import java.util.List;
  47 import sun.security.krb5.internal.util.KerberosString;
  48 /**
  49  * Implements the ASN.1 KRBError type.
  50  *
  51  * <pre>{@code
  52  * KRB-ERROR       ::= [APPLICATION 30] SEQUENCE {
  53  *         pvno            [0] INTEGER (5),
  54  *         msg-type        [1] INTEGER (30),
  55  *         ctime           [2] KerberosTime OPTIONAL,
  56  *         cusec           [3] Microseconds OPTIONAL,
  57  *         stime           [4] KerberosTime,
  58  *         susec           [5] Microseconds,
  59  *         error-code      [6] Int32,
  60  *         crealm          [7] Realm OPTIONAL,
  61  *         cname           [8] PrincipalName OPTIONAL,
  62  *         realm           [9] Realm -- service realm --,
  63  *         sname           [10] PrincipalName -- service name --,
  64  *         e-text          [11] KerberosString OPTIONAL,
  65  *         e-data          [12] OCTET STRING OPTIONAL
  66  * }
  67  *
  68  * METHOD-DATA     ::= SEQUENCE OF PA-DATA
  69  *
  70  * TYPED-DATA      ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE {
  71  *         data-type       [0] Int32,
  72  *         data-value      [1] OCTET STRING OPTIONAL
  73  * }
  74  * }</pre>
  75  *
  76  * <p>
  77  * This definition reflects the Network Working Group RFC 4120
  78  * specification available at
  79  * <a href="http://www.ietf.org/rfc/rfc4120.txt">
  80  * http://www.ietf.org/rfc/rfc4120.txt</a>.
  81  */
  82 
  83 public class KRBError implements java.io.Serializable {
  84     static final long serialVersionUID = 3643809337475284503L;
  85 
  86     private int pvno;
  87     private int msgType;
  88     private KerberosTime cTime; //optional
  89     private Integer cuSec; //optional
  90     private KerberosTime sTime;
  91     private Integer suSec;
  92     private int errorCode;
  93     private Realm crealm; //optional
  94     private PrincipalName cname; //optional
  95     private PrincipalName sname;
  96     private String eText; //optional
  97     private byte[] eData; //optional
  98     private Checksum eCksum; //optional
  99 
 100     private PAData[] pa;    // PA-DATA in eData
 101 
 102     private static boolean DEBUG = Krb5.DEBUG;
 103 
 104     private void readObject(ObjectInputStream is)
 105             throws IOException, ClassNotFoundException {
 106         try {
 107             init(new DerValue((byte[])is.readObject()));
 108             parseEData(eData);
 109         } catch (Exception e) {
 110             throw new IOException(e);
 111         }
 112     }
 113 
 114     private void writeObject(ObjectOutputStream os)
 115             throws IOException {
 116         try {
 117             os.writeObject(asn1Encode());
 118         } catch (Exception e) {
 119             throw new IOException(e);
 120         }
 121     }
 122 
 123     public KRBError(
 124                     APOptions new_apOptions,
 125                     KerberosTime new_cTime,
 126                     Integer new_cuSec,
 127                     KerberosTime new_sTime,
 128                     Integer new_suSec,
 129                     int new_errorCode,
 130                     PrincipalName new_cname,
 131                     PrincipalName new_sname,
 132                     String new_eText,
 133                     byte[] new_eData
 134                         ) throws IOException, Asn1Exception {
 135         pvno = Krb5.PVNO;
 136         msgType = Krb5.KRB_ERROR;
 137         cTime = new_cTime;
 138         cuSec = new_cuSec;
 139         sTime = new_sTime;
 140         suSec = new_suSec;
 141         errorCode = new_errorCode;
 142         crealm = new_cname.getRealm();
 143         cname = new_cname;
 144         sname = new_sname;
 145         eText = new_eText;
 146         eData = new_eData;
 147 
 148         parseEData(eData);
 149     }
 150 
 151     public KRBError(
 152                     APOptions new_apOptions,
 153                     KerberosTime new_cTime,
 154                     Integer new_cuSec,
 155                     KerberosTime new_sTime,
 156                     Integer new_suSec,
 157                     int new_errorCode,
 158                     PrincipalName new_cname,
 159                     PrincipalName new_sname,
 160                     String new_eText,
 161                     byte[] new_eData,
 162                     Checksum new_eCksum
 163                         ) throws IOException, Asn1Exception {
 164         pvno = Krb5.PVNO;
 165         msgType = Krb5.KRB_ERROR;
 166         cTime = new_cTime;
 167         cuSec = new_cuSec;
 168         sTime = new_sTime;
 169         suSec = new_suSec;
 170         errorCode = new_errorCode;
 171         crealm = new_cname.getRealm();
 172         cname = new_cname;
 173         sname = new_sname;
 174         eText = new_eText;
 175         eData = new_eData;
 176         eCksum = new_eCksum;
 177 
 178         parseEData(eData);
 179     }
 180 
 181     public KRBError(byte[] data) throws Asn1Exception,
 182             RealmException, KrbApErrException, IOException {
 183         init(new DerValue(data));
 184         parseEData(eData);
 185     }
 186 
 187     public KRBError(DerValue encoding) throws Asn1Exception,
 188             RealmException, KrbApErrException, IOException {
 189         init(encoding);
 190         showDebug();
 191         parseEData(eData);
 192     }
 193 
 194     /*
 195      * Attention:
 196      *
 197      * According to RFC 4120, e-data field in a KRB-ERROR message is
 198      * a METHOD-DATA when errorCode is KDC_ERR_PREAUTH_REQUIRED,
 199      * and application-specific otherwise (The RFC suggests using
 200      * TYPED-DATA).
 201      *
 202      * Hence, the ideal procedure to parse e-data should look like:
 203      *
 204      * if (errorCode is KDC_ERR_PREAUTH_REQUIRED) {
 205      *    parse as METHOD-DATA
 206      * } else {
 207      *    try parsing as TYPED-DATA
 208      * }
 209      *
 210      * Unfortunately, we know that some implementations also use the
 211      * METHOD-DATA format for errorcode KDC_ERR_PREAUTH_FAILED, and
 212      * do not use the TYPED-DATA for other errorcodes (say,
 213      * KDC_ERR_CLIENT_REVOKED).
 214      */
 215 
 216     // parse the edata field
 217     private void parseEData(byte[] data) throws IOException {
 218         if (data == null) {
 219             return;
 220         }
 221 
 222         // We need to parse eData as METHOD-DATA for both errorcodes.
 223         if (errorCode == Krb5.KDC_ERR_PREAUTH_REQUIRED
 224                 || errorCode == Krb5.KDC_ERR_PREAUTH_FAILED) {
 225             try {
 226                 // RFC 4120 does not guarantee that eData is METHOD-DATA when
 227                 // errorCode is KDC_ERR_PREAUTH_FAILED. Therefore, the parse
 228                 // may fail.
 229                 parsePAData(data);
 230             } catch (Exception e) {
 231                 if (DEBUG) {
 232                     System.out.println("Unable to parse eData field of KRB-ERROR:\n" +
 233                             new sun.security.util.HexDumpEncoder().encodeBuffer(data));
 234                 }
 235                 IOException ioe = new IOException(
 236                         "Unable to parse eData field of KRB-ERROR");
 237                 ioe.initCause(e);
 238                 throw ioe;
 239             }
 240         } else {
 241             if (DEBUG) {
 242                 System.out.println("Unknown eData field of KRB-ERROR:\n" +
 243                         new sun.security.util.HexDumpEncoder().encodeBuffer(data));
 244             }
 245         }
 246     }
 247 
 248     /**
 249      * Try parsing the data as a sequence of PA-DATA.
 250      * @param data the data block
 251      */
 252     private void parsePAData(byte[] data)
 253             throws IOException, Asn1Exception {
 254         DerValue derPA = new DerValue(data);
 255         List<PAData> paList = new ArrayList<>();
 256         while (derPA.data.available() > 0) {
 257             // read the PA-DATA
 258             DerValue tmp = derPA.data.getDerValue();
 259             PAData pa_data = new PAData(tmp);
 260             paList.add(pa_data);
 261             if (DEBUG) {
 262                 System.out.println(pa_data);
 263             }
 264         }
 265         pa = paList.toArray(new PAData[paList.size()]);
 266     }
 267 
 268     public final Realm getClientRealm() {
 269         return crealm;
 270     }
 271 
 272     public final KerberosTime getServerTime() {
 273         return sTime;
 274     }
 275 
 276     public final KerberosTime getClientTime() {
 277         return cTime;
 278     }
 279 
 280     public final Integer getServerMicroSeconds() {
 281         return suSec;
 282     }
 283 
 284     public final Integer getClientMicroSeconds() {
 285         return cuSec;
 286     }
 287 
 288     public final int getErrorCode() {
 289         return errorCode;
 290     }
 291 
 292     // access pre-auth info
 293     public final PAData[] getPA() {
 294         return pa;
 295     }
 296 
 297     public final String getErrorString() {
 298         return eText;
 299     }
 300 
 301     /**
 302      * Initializes a KRBError object.
 303      * @param encoding a DER-encoded data.
 304      * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data.
 305      * @exception IOException if an I/O error occurs while reading encoded data.
 306      * @exception KrbApErrException if the value read from the DER-encoded data
 307      *  stream does not match the pre-defined value.
 308      * @exception RealmException if an error occurs while parsing a Realm object.
 309      */
 310     private void init(DerValue encoding) throws Asn1Exception,
 311             RealmException, KrbApErrException, IOException {
 312         DerValue der, subDer;
 313         if (((encoding.getTag() & (byte)0x1F) != (byte)0x1E)
 314                 || (encoding.isApplication() != true)
 315                 || (encoding.isConstructed() != true)) {
 316             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 317         }
 318         der = encoding.getData().getDerValue();
 319         if (der.getTag() != DerValue.tag_Sequence) {
 320             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 321         }
 322         subDer = der.getData().getDerValue();
 323         if ((subDer.getTag() & (byte)0x1F) == (byte)0x00) {
 324 
 325             pvno = subDer.getData().getBigInteger().intValue();
 326             if (pvno != Krb5.PVNO)
 327                 throw new KrbApErrException(Krb5.KRB_AP_ERR_BADVERSION);
 328         } else {
 329             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 330         }
 331 
 332         subDer = der.getData().getDerValue();
 333         if ((subDer.getTag() & (byte)0x1F) == (byte)0x01) {
 334             msgType = subDer.getData().getBigInteger().intValue();
 335             if (msgType != Krb5.KRB_ERROR) {
 336                 throw new KrbApErrException(Krb5.KRB_AP_ERR_MSG_TYPE);
 337             }
 338         } else {
 339             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 340         }
 341 
 342         cTime = KerberosTime.parse(der.getData(), (byte)0x02, true);
 343         if ((der.getData().peekByte() & 0x1F) == 0x03) {
 344             subDer = der.getData().getDerValue();
 345             cuSec = subDer.getData().getBigInteger().intValue();
 346         }
 347         else cuSec = null;
 348         sTime = KerberosTime.parse(der.getData(), (byte)0x04, false);
 349         subDer = der.getData().getDerValue();
 350         if ((subDer.getTag() & (byte)0x1F) == (byte)0x05) {
 351             suSec = subDer.getData().getBigInteger().intValue();
 352         }
 353         else  throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 354         subDer = der.getData().getDerValue();
 355         if ((subDer.getTag() & (byte)0x1F) == (byte)0x06) {
 356             errorCode = subDer.getData().getBigInteger().intValue();
 357         }
 358         else  throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 359         crealm = Realm.parse(der.getData(), (byte)0x07, true);
 360         cname = PrincipalName.parse(der.getData(), (byte)0x08, true, crealm);
 361         Realm realm = Realm.parse(der.getData(), (byte)0x09, false);
 362         sname = PrincipalName.parse(der.getData(), (byte)0x0A, false, realm);
 363         eText = null;
 364         eData = null;
 365         eCksum = null;
 366         if (der.getData().available() >0) {
 367             if ((der.getData().peekByte() & 0x1F) == 0x0B) {
 368                 subDer = der.getData().getDerValue();
 369                 eText = new KerberosString(subDer.getData().getDerValue())
 370                         .toString();
 371             }
 372         }
 373         if (der.getData().available() >0) {
 374             if ((der.getData().peekByte() & 0x1F) == 0x0C) {
 375                 subDer = der.getData().getDerValue();
 376                 eData = subDer.getData().getOctetString();
 377             }
 378         }
 379         if (der.getData().available() >0) {
 380             eCksum = Checksum.parse(der.getData(), (byte)0x0D, true);
 381         }
 382         if (der.getData().available() >0)
 383             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 384     }
 385 
 386     /**
 387      * For debug use only
 388      */
 389     private void showDebug() {
 390         if (DEBUG) {
 391             System.out.println(">>>KRBError:");
 392             if (cTime != null)
 393                 System.out.println("\t cTime is " + cTime.toDate().toString() + " " + cTime.toDate().getTime());
 394             if (cuSec != null) {
 395                 System.out.println("\t cuSec is " + cuSec.intValue());
 396             }
 397 
 398             System.out.println("\t sTime is " + sTime.toDate().toString
 399                                () + " " + sTime.toDate().getTime());
 400             System.out.println("\t suSec is " + suSec);
 401             System.out.println("\t error code is " + errorCode);
 402             System.out.println("\t error Message is " + Krb5.getErrorMessage(errorCode));
 403             if (crealm != null) {
 404                 System.out.println("\t crealm is " + crealm.toString());
 405             }
 406             if (cname != null) {
 407                 System.out.println("\t cname is " + cname.toString());
 408             }
 409             if (sname != null) {
 410                 System.out.println("\t sname is " + sname.toString());
 411             }
 412             if (eData != null) {
 413                 System.out.println("\t eData provided.");
 414             }
 415             if (eCksum != null) {
 416                 System.out.println("\t checksum provided.");
 417             }
 418             System.out.println("\t msgType is " + msgType);
 419         }
 420     }
 421 
 422     /**
 423      * Encodes an KRBError object.
 424      * @return the byte array of encoded KRBError object.
 425      * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data.
 426      * @exception IOException if an I/O error occurs while reading encoded data.
 427      */
 428     public byte[] asn1Encode() throws Asn1Exception, IOException {
 429         DerOutputStream temp = new DerOutputStream();
 430         DerOutputStream bytes = new DerOutputStream();
 431 
 432         temp.putInteger(BigInteger.valueOf(pvno));
 433         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x00), temp);
 434         temp = new DerOutputStream();
 435         temp.putInteger(BigInteger.valueOf(msgType));
 436         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x01), temp);
 437 
 438         if (cTime != null) {
 439             bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x02), cTime.asn1Encode());
 440         }
 441         if (cuSec != null) {
 442             temp = new DerOutputStream();
 443             temp.putInteger(BigInteger.valueOf(cuSec.intValue()));
 444             bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x03), temp);
 445         }
 446 
 447         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x04), sTime.asn1Encode());
 448         temp = new DerOutputStream();
 449         temp.putInteger(BigInteger.valueOf(suSec.intValue()));
 450         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x05), temp);
 451         temp = new DerOutputStream();
 452         temp.putInteger(BigInteger.valueOf(errorCode));
 453         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x06), temp);
 454 
 455         if (crealm != null) {
 456             bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x07), crealm.asn1Encode());
 457         }
 458         if (cname != null) {
 459             bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x08), cname.asn1Encode());
 460         }
 461 
 462         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x09), sname.getRealm().asn1Encode());
 463         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x0A), sname.asn1Encode());
 464 
 465         if (eText != null) {
 466             temp = new DerOutputStream();
 467             temp.putDerValue(new KerberosString(eText).toDerValue());
 468             bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x0B), temp);
 469         }
 470         if (eData != null) {
 471             temp = new DerOutputStream();
 472             temp.putOctetString(eData);
 473             bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x0C), temp);
 474         }
 475         if (eCksum != null) {
 476             bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x0D), eCksum.asn1Encode());
 477         }
 478 
 479         temp = new DerOutputStream();
 480         temp.write(DerValue.tag_Sequence, bytes);
 481         bytes = new DerOutputStream();
 482         bytes.write(DerValue.createTag(DerValue.TAG_APPLICATION, true, (byte)0x1E), temp);
 483         return bytes.toByteArray();
 484     }
 485 
 486     @Override public boolean equals(Object obj) {
 487         if (this == obj) {
 488             return true;
 489         }
 490 
 491         if (!(obj instanceof KRBError)) {
 492             return false;
 493         }
 494 
 495         KRBError other = (KRBError)obj;
 496         return  pvno == other.pvno &&
 497                 msgType == other.msgType &&
 498                 isEqual(cTime, other.cTime) &&
 499                 isEqual(cuSec, other.cuSec) &&
 500                 isEqual(sTime, other.sTime) &&
 501                 isEqual(suSec, other.suSec) &&
 502                 errorCode == other.errorCode &&
 503                 isEqual(crealm, other.crealm) &&
 504                 isEqual(cname, other.cname) &&
 505                 isEqual(sname, other.sname) &&
 506                 isEqual(eText, other.eText) &&
 507                 java.util.Arrays.equals(eData, other.eData) &&
 508                 isEqual(eCksum, other.eCksum);
 509     }
 510 
 511     private static boolean isEqual(Object a, Object b) {
 512         return (a == null)?(b == null):(a.equals(b));
 513     }
 514 
 515     @Override public int hashCode() {
 516         int result = 17;
 517         result = 37 * result + pvno;
 518         result = 37 * result + msgType;
 519         if (cTime != null) result = 37 * result + cTime.hashCode();
 520         if (cuSec != null) result = 37 * result + cuSec.hashCode();
 521         if (sTime != null) result = 37 * result + sTime.hashCode();
 522         if (suSec != null) result = 37 * result + suSec.hashCode();
 523         result = 37 * result + errorCode;
 524         if (crealm != null) result = 37 * result + crealm.hashCode();
 525         if (cname != null) result = 37 * result + cname.hashCode();
 526         if (sname != null) result = 37 * result + sname.hashCode();
 527         if (eText != null) result = 37 * result + eText.hashCode();
 528         result = 37 * result + Arrays.hashCode(eData);
 529         if (eCksum != null) result = 37 * result + eCksum.hashCode();
 530         return result;
 531     }
 532 }