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