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