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 }