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