1 /*
   2  * Copyright (c) 2017, 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 sun.security.krb5.internal.crypto.EType;
  35 import sun.security.util.*;
  36 import sun.security.krb5.Asn1Exception;
  37 import java.io.IOException;
  38 import java.util.Vector;
  39 
  40 import sun.security.krb5.internal.util.KerberosString;
  41 
  42 /**
  43  * Implements the ASN.1 PA-DATA type.
  44  *
  45  * <pre>{@code
  46  * PA-DATA         ::= SEQUENCE {
  47  *         -- NOTE: first tag is [1], not [0]
  48  *         padata-type     [1] Int32,
  49  *         padata-value    [2] OCTET STRING -- might be encoded AP-REQ
  50  * }
  51  * }</pre>
  52  *
  53  * <p>
  54  * This definition reflects the Network Working Group RFC 4120
  55  * specification available at
  56  * <a href="http://www.ietf.org/rfc/rfc4120.txt">
  57  * http://www.ietf.org/rfc/rfc4120.txt</a>.
  58  */
  59 
  60 public class PAData {
  61     private int pADataType;
  62     private byte[] pADataValue = null;
  63     private static final byte TAG_PATYPE = 1;
  64     private static final byte TAG_PAVALUE = 2;
  65 
  66     private PAData() {
  67     }
  68 
  69     public PAData(int new_pADataType, byte[] new_pADataValue) {
  70         pADataType = new_pADataType;
  71         if (new_pADataValue != null) {
  72             pADataValue = new_pADataValue.clone();
  73         }
  74     }
  75 
  76     public Object clone() {
  77         PAData new_pAData = new PAData();
  78         new_pAData.pADataType = pADataType;
  79         if (pADataValue != null) {
  80             new_pAData.pADataValue = new byte[pADataValue.length];
  81             System.arraycopy(pADataValue, 0, new_pAData.pADataValue,
  82                              0, pADataValue.length);
  83         }
  84         return new_pAData;
  85     }
  86 
  87     /**
  88      * Constructs a PAData object.
  89      * @param encoding a Der-encoded data.
  90      * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data.
  91      * @exception IOException if an I/O error occurs while reading encoded data.
  92      */
  93     public PAData(DerValue encoding) throws Asn1Exception, IOException {
  94         DerValue der = null;
  95         if (encoding.getTag() != DerValue.tag_Sequence) {
  96             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
  97         }
  98         der = encoding.getData().getDerValue();
  99         if ((der.getTag() & 0x1F) == 0x01) {
 100             this.pADataType = der.getData().getBigInteger().intValue();
 101         }
 102         else
 103             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 104         der = encoding.getData().getDerValue();
 105         if ((der.getTag() & 0x1F) == 0x02) {
 106             this.pADataValue = der.getData().getOctetString();
 107         }
 108         if (encoding.getData().available() > 0)
 109             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 110     }
 111 
 112     /**
 113      * Encodes this object to an OutputStream.
 114      *
 115      * @return byte array of the encoded data.
 116      * @exception IOException if an I/O error occurs while reading encoded data.
 117      * @exception Asn1Exception on encoding errors.
 118      */
 119     public byte[] asn1Encode() throws Asn1Exception, IOException {
 120 
 121         DerOutputStream bytes = new DerOutputStream();
 122         DerOutputStream temp = new DerOutputStream();
 123 
 124         temp.putInteger(pADataType);
 125         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_PATYPE), temp);
 126         temp = new DerOutputStream();
 127         temp.putOctetString(pADataValue);
 128         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_PAVALUE), temp);
 129 
 130         temp = new DerOutputStream();
 131         temp.write(DerValue.tag_Sequence, bytes);
 132         return temp.toByteArray();
 133     }
 134 
 135     // accessor methods
 136     public int getType() {
 137         return pADataType;
 138     }
 139 
 140     public byte[] getValue() {
 141         return ((pADataValue == null) ? null : pADataValue.clone());
 142     }
 143 
 144     /**
 145      * Parse (unmarshal) a PAData from a DER input stream.  This form
 146      * parsing might be used when expanding a value which is part of
 147      * a constructed sequence and uses explicitly tagged type.
 148      *
 149      * @exception Asn1Exception if an Asn1Exception occurs.
 150      * @param data the Der input stream value, which contains one or more
 151      *        marshaled values.
 152      * @param explicitTag tag number.
 153      * @param optional indicates if this data field is optional.
 154      * @return an array of PAData.
 155      */
 156     public static PAData[] parseSequence(DerInputStream data,
 157                                       byte explicitTag, boolean optional)
 158         throws Asn1Exception, IOException {
 159         if ((optional) &&
 160                 (((byte)data.peekByte() & (byte)0x1F) != explicitTag))
 161                 return null;
 162         DerValue subDer = data.getDerValue();
 163         DerValue subsubDer = subDer.getData().getDerValue();
 164         if (subsubDer.getTag() != DerValue.tag_SequenceOf) {
 165             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 166         }
 167         Vector<PAData> v = new Vector<>();
 168         while (subsubDer.getData().available() > 0) {
 169             v.addElement(new PAData(subsubDer.getData().getDerValue()));
 170         }
 171         if (v.size() > 0) {
 172             PAData[] pas = new PAData[v.size()];
 173             v.copyInto(pas);
 174             return pas;
 175         }
 176         return null;
 177     }
 178 
 179     /**
 180      * Gets the preferred etype from the PAData array.
 181      * <ol>
 182      * <li>ETYPE-INFO2-ENTRY with unknown s2kparams ignored</li>
 183      * <li>ETYPE-INFO2 preferred to ETYPE-INFO</li>
 184      * <li>Multiple entries for same etype in one PA-DATA, use the first one.</li>
 185      * <li>Multiple PA-DATA with same type, choose the last one.</li>
 186      * </ol>
 187      * (This is useful when PA-DATAs from KRB-ERROR and AS-REP are combined).
 188      *
 189      * @return the etype, or defaultEType if not enough info
 190      * @throws Asn1Exception|IOException if there is an encoding error
 191      */
 192     public static int getPreferredEType(PAData[] pas, int defaultEType)
 193             throws IOException, Asn1Exception {
 194 
 195         if (pas == null) return defaultEType;
 196 
 197         DerValue d = null, d2 = null;
 198         for (PAData p: pas) {
 199             if (p.getValue() == null) continue;
 200             switch (p.getType()) {
 201                 case Krb5.PA_ETYPE_INFO:
 202                     d = new DerValue(p.getValue());
 203                     break;
 204                 case Krb5.PA_ETYPE_INFO2:
 205                     d2 = new DerValue(p.getValue());
 206                     break;
 207             }
 208         }
 209         if (d2 != null) {
 210             while (d2.data.available() > 0) {
 211                 DerValue value = d2.data.getDerValue();
 212                 ETypeInfo2 tmp = new ETypeInfo2(value);
 213                 if (EType.isNewer(tmp.getEType()) || tmp.getParams() == null) {
 214                     // we don't support non-null s2kparams for old etypes
 215                     return tmp.getEType();
 216                 }
 217             }
 218         }
 219         if (d != null) {
 220             while (d.data.available() > 0) {
 221                 DerValue value = d.data.getDerValue();
 222                 ETypeInfo tmp = new ETypeInfo(value);
 223                 return tmp.getEType();
 224             }
 225         }
 226         return defaultEType;
 227     }
 228 
 229     /**
 230      * A place to store a pair of salt and s2kparams.
 231      * An empty salt is changed to null, to be interoperable
 232      * with Windows 2000 server. This is in fact not correct.
 233      */
 234     public static class SaltAndParams {
 235         public final String salt;
 236         public final byte[] params;
 237         public SaltAndParams(String s, byte[] p) {
 238             if (s != null && s.isEmpty()) s = null;
 239             this.salt = s;
 240             this.params = p;
 241         }
 242     }
 243 
 244     /**
 245      * Fetches salt and s2kparams value for eType in a series of PA-DATAs.
 246      * 1. ETYPE-INFO2-ENTRY with unknown s2kparams ignored
 247      * 2. PA-ETYPE-INFO2 preferred to PA-ETYPE-INFO preferred to PA-PW-SALT.
 248      * 3. multiple entries for same etype in one PA-DATA, use the first one.
 249      * 4. Multiple PA-DATA with same type, choose the last one
 250      * (This is useful when PA-DATAs from KRB-ERROR and AS-REP are combined).
 251      * @return salt and s2kparams. can be null if not found
 252      */
 253     public static SaltAndParams getSaltAndParams(int eType, PAData[] pas)
 254             throws Asn1Exception, IOException {
 255 
 256         if (pas == null) return null;
 257 
 258         DerValue d = null, d2 = null;
 259         String paPwSalt = null;
 260 
 261         for (PAData p: pas) {
 262             if (p.getValue() == null) continue;
 263             switch (p.getType()) {
 264                 case Krb5.PA_PW_SALT:
 265                     paPwSalt = new String(p.getValue(),
 266                             KerberosString.MSNAME?"UTF8":"8859_1");
 267                     break;
 268                 case Krb5.PA_ETYPE_INFO:
 269                     d = new DerValue(p.getValue());
 270                     break;
 271                 case Krb5.PA_ETYPE_INFO2:
 272                     d2 = new DerValue(p.getValue());
 273                     break;
 274             }
 275         }
 276         if (d2 != null) {
 277             while (d2.data.available() > 0) {
 278                 DerValue value = d2.data.getDerValue();
 279                 ETypeInfo2 tmp = new ETypeInfo2(value);
 280                 if (tmp.getEType() == eType &&
 281                         (EType.isNewer(eType) || tmp.getParams() == null)) {
 282                     // we don't support non-null s2kparams for old etypes
 283                     return new SaltAndParams(tmp.getSalt(), tmp.getParams());
 284                 }
 285             }
 286         }
 287         if (d != null) {
 288             while (d.data.available() > 0) {
 289                 DerValue value = d.data.getDerValue();
 290                 ETypeInfo tmp = new ETypeInfo(value);
 291                 if (tmp.getEType() == eType) {
 292                     return new SaltAndParams(tmp.getSalt(), null);
 293                 }
 294             }
 295         }
 296         if (paPwSalt != null) {
 297             return new SaltAndParams(paPwSalt, null);
 298         }
 299         return null;
 300     }
 301 
 302     @Override
 303     public String toString(){
 304         StringBuilder sb = new StringBuilder();
 305         sb.append(">>>Pre-Authentication Data:\n\t PA-DATA type = ")
 306                 .append(pADataType).append('\n');
 307 
 308         switch(pADataType) {
 309             case Krb5.PA_ENC_TIMESTAMP:
 310                 sb.append("\t PA-ENC-TIMESTAMP");
 311                 break;
 312             case Krb5.PA_ETYPE_INFO:
 313                 if (pADataValue != null) {
 314                     try {
 315                         DerValue der = new DerValue(pADataValue);
 316                         while (der.data.available() > 0) {
 317                             DerValue value = der.data.getDerValue();
 318                             ETypeInfo info = new ETypeInfo(value);
 319                             sb.append("\t PA-ETYPE-INFO etype = ")
 320                                     .append(info.getEType())
 321                                     .append(", salt = ")
 322                                     .append(info.getSalt())
 323                                     .append('\n');
 324                         }
 325                     } catch (IOException|Asn1Exception e) {
 326                         sb.append("\t <Unparseable PA-ETYPE-INFO>\n");
 327                     }
 328                 }
 329                 break;
 330             case Krb5.PA_ETYPE_INFO2:
 331                 if (pADataValue != null) {
 332                     try {
 333                         DerValue der = new DerValue(pADataValue);
 334                         while (der.data.available() > 0) {
 335                             DerValue value = der.data.getDerValue();
 336                             ETypeInfo2 info2 = new ETypeInfo2(value);
 337                             sb.append("\t PA-ETYPE-INFO2 etype = ")
 338                                     .append(info2.getEType())
 339                                     .append(", salt = ")
 340                                     .append(info2.getSalt())
 341                                     .append(", s2kparams = ");
 342                             byte[] s2kparams = info2.getParams();
 343                             if (s2kparams == null) {
 344                                 sb.append("null\n");
 345                             } else if (s2kparams.length == 0) {
 346                                 sb.append("empty\n");
 347                             } else {
 348                                 sb.append(new sun.security.util.HexDumpEncoder()
 349                                         .encodeBuffer(s2kparams));
 350                             }
 351                         }
 352                     } catch (IOException|Asn1Exception e) {
 353                         sb.append("\t <Unparseable PA-ETYPE-INFO>\n");
 354                     }
 355                 }
 356                 break;
 357             case Krb5.PA_FOR_USER:
 358                 sb.append("\t PA-FOR-USER\n");
 359                 break;
 360             default:
 361                 // Unknown Pre-auth type
 362                 break;
 363         }
 364         return sb.toString();
 365     }
 366 }