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