1 /* 2 * Copyright (c) 2013, 2020, 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 package java.security; 27 28 import java.io.IOException; 29 import java.math.BigInteger; 30 import java.util.Arrays; 31 import java.util.regex.Pattern; 32 import sun.security.util.*; 33 34 /** 35 * An attribute associated with a PKCS12 keystore entry. 36 * The attribute name is an ASN.1 Object Identifier and the attribute 37 * value is a set of ASN.1 types. 38 * 39 * @since 1.8 40 */ 41 public final class PKCS12Attribute implements KeyStore.Entry.Attribute { 42 43 private static final Pattern COLON_SEPARATED_HEX_PAIRS = 44 Pattern.compile("^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2})+$"); 45 private String name; 46 private String value; 47 private final byte[] encoded; 48 private int hashValue = -1; 49 50 /** 51 * Constructs a PKCS12 attribute from its name and value. 52 * The name is an ASN.1 Object Identifier represented as a list of 53 * dot-separated integers. 54 * A string value is represented as the string itself. 55 * A binary value is represented as a string of colon-separated 56 * pairs of hexadecimal digits. 57 * Multi-valued attributes are represented as a comma-separated 58 * list of values, enclosed in square brackets. See 59 * {@link Arrays#toString(java.lang.Object[])}. 60 * <p> 61 * A string value will be DER-encoded as an ASN.1 UTF8String and a 62 * binary value will be DER-encoded as an ASN.1 Octet String. 63 * 64 * @param name the attribute's identifier 65 * @param value the attribute's value 66 * 67 * @exception NullPointerException if {@code name} or {@code value} 68 * is {@code null} 69 * @exception IllegalArgumentException if {@code name} or 70 * {@code value} is incorrectly formatted 71 */ PKCS12Attribute(String name, String value)72 public PKCS12Attribute(String name, String value) { 73 if (name == null || value == null) { 74 throw new NullPointerException(); 75 } 76 // Validate name 77 ObjectIdentifier type; 78 try { 79 type = new ObjectIdentifier(name); 80 } catch (IOException e) { 81 throw new IllegalArgumentException("Incorrect format: name", e); 82 } 83 this.name = name; 84 85 // Validate value 86 int length = value.length(); 87 String[] values; 88 if (length > 1 && 89 value.charAt(0) == '[' && value.charAt(length - 1) == ']') { 90 values = value.substring(1, length - 1).split(", "); 91 } else { 92 values = new String[]{ value }; 93 } 94 this.value = value; 95 96 try { 97 this.encoded = encode(type, values); 98 } catch (IOException e) { 99 throw new IllegalArgumentException("Incorrect format: value", e); 100 } 101 } 102 103 /** 104 * Constructs a PKCS12 attribute from its ASN.1 DER encoding. 105 * The DER encoding is specified by the following ASN.1 definition: 106 * <pre> 107 * 108 * Attribute ::= SEQUENCE { 109 * type AttributeType, 110 * values SET OF AttributeValue 111 * } 112 * AttributeType ::= OBJECT IDENTIFIER 113 * AttributeValue ::= ANY defined by type 114 * 115 * </pre> 116 * 117 * @param encoded the attribute's ASN.1 DER encoding. It is cloned 118 * to prevent subsequent modificaion. 119 * 120 * @exception NullPointerException if {@code encoded} is 121 * {@code null} 122 * @exception IllegalArgumentException if {@code encoded} is 123 * incorrectly formatted 124 */ PKCS12Attribute(byte[] encoded)125 public PKCS12Attribute(byte[] encoded) { 126 if (encoded == null) { 127 throw new NullPointerException(); 128 } 129 this.encoded = encoded.clone(); 130 131 try { 132 parse(encoded); 133 } catch (IOException e) { 134 throw new IllegalArgumentException("Incorrect format: encoded", e); 135 } 136 } 137 138 /** 139 * Returns the attribute's ASN.1 Object Identifier represented as a 140 * list of dot-separated integers. 141 * 142 * @return the attribute's identifier 143 */ 144 @Override getName()145 public String getName() { 146 return name; 147 } 148 149 /** 150 * Returns the attribute's ASN.1 DER-encoded value as a string. 151 * An ASN.1 DER-encoded value is returned in one of the following 152 * {@code String} formats: 153 * <ul> 154 * <li> the DER encoding of a basic ASN.1 type that has a natural 155 * string representation is returned as the string itself. 156 * Such types are currently limited to BOOLEAN, INTEGER, 157 * OBJECT IDENTIFIER, UTCTime, GeneralizedTime and the 158 * following six ASN.1 string types: UTF8String, 159 * PrintableString, T61String, IA5String, BMPString and 160 * GeneralString. 161 * <li> the DER encoding of any other ASN.1 type is not decoded but 162 * returned as a binary string of colon-separated pairs of 163 * hexadecimal digits. 164 * </ul> 165 * Multi-valued attributes are represented as a comma-separated 166 * list of values, enclosed in square brackets. See 167 * {@link Arrays#toString(java.lang.Object[])}. 168 * 169 * @return the attribute value's string encoding 170 */ 171 @Override getValue()172 public String getValue() { 173 return value; 174 } 175 176 /** 177 * Returns the attribute's ASN.1 DER encoding. 178 * 179 * @return a clone of the attribute's DER encoding 180 */ getEncoded()181 public byte[] getEncoded() { 182 return encoded.clone(); 183 } 184 185 /** 186 * Compares this {@code PKCS12Attribute} and a specified object for 187 * equality. 188 * 189 * @param obj the comparison object 190 * 191 * @return true if {@code obj} is a {@code PKCS12Attribute} and 192 * their DER encodings are equal. 193 */ 194 @Override equals(Object obj)195 public boolean equals(Object obj) { 196 if (this == obj) { 197 return true; 198 } 199 if (!(obj instanceof PKCS12Attribute)) { 200 return false; 201 } 202 return Arrays.equals(encoded, ((PKCS12Attribute) obj).encoded); 203 } 204 205 /** 206 * Returns the hashcode for this {@code PKCS12Attribute}. 207 * The hash code is computed from its DER encoding. 208 * 209 * @return the hash code 210 */ 211 @Override hashCode()212 public int hashCode() { 213 int h = hashValue; 214 if (h == -1) { 215 hashValue = h = Arrays.hashCode(encoded); 216 } 217 return h; 218 } 219 220 /** 221 * Returns a string representation of this {@code PKCS12Attribute}. 222 * 223 * @return a name/value pair separated by an 'equals' symbol 224 */ 225 @Override toString()226 public String toString() { 227 return (name + "=" + value); 228 } 229 encode(ObjectIdentifier type, String[] values)230 private byte[] encode(ObjectIdentifier type, String[] values) 231 throws IOException { 232 DerOutputStream attribute = new DerOutputStream(); 233 attribute.putOID(type); 234 DerOutputStream attrContent = new DerOutputStream(); 235 for (String value : values) { 236 if (COLON_SEPARATED_HEX_PAIRS.matcher(value).matches()) { 237 byte[] bytes = 238 new BigInteger(value.replace(":", ""), 16).toByteArray(); 239 if (bytes[0] == 0) { 240 bytes = Arrays.copyOfRange(bytes, 1, bytes.length); 241 } 242 attrContent.putOctetString(bytes); 243 } else { 244 attrContent.putUTF8String(value); 245 } 246 } 247 attribute.write(DerValue.tag_Set, attrContent); 248 DerOutputStream attributeValue = new DerOutputStream(); 249 attributeValue.write(DerValue.tag_Sequence, attribute); 250 251 return attributeValue.toByteArray(); 252 } 253 parse(byte[] encoded)254 private void parse(byte[] encoded) throws IOException { 255 DerInputStream attributeValue = new DerInputStream(encoded); 256 DerValue[] attrSeq = attributeValue.getSequence(2); 257 if (attrSeq.length != 2) { 258 throw new IOException("Invalid length for PKCS12Attribute"); 259 } 260 ObjectIdentifier type = attrSeq[0].getOID(); 261 DerInputStream attrContent = 262 new DerInputStream(attrSeq[1].toByteArray()); 263 DerValue[] attrValueSet = attrContent.getSet(1); 264 String[] values = new String[attrValueSet.length]; 265 String printableString; 266 for (int i = 0; i < attrValueSet.length; i++) { 267 if (attrValueSet[i].tag == DerValue.tag_OctetString) { 268 values[i] = Debug.toString(attrValueSet[i].getOctetString()); 269 } else if ((printableString = attrValueSet[i].getAsString()) 270 != null) { 271 values[i] = printableString; 272 } else if (attrValueSet[i].tag == DerValue.tag_ObjectId) { 273 values[i] = attrValueSet[i].getOID().toString(); 274 } else if (attrValueSet[i].tag == DerValue.tag_GeneralizedTime) { 275 values[i] = attrValueSet[i].getGeneralizedTime().toString(); 276 } else if (attrValueSet[i].tag == DerValue.tag_UtcTime) { 277 values[i] = attrValueSet[i].getUTCTime().toString(); 278 } else if (attrValueSet[i].tag == DerValue.tag_Integer) { 279 values[i] = attrValueSet[i].getBigInteger().toString(); 280 } else if (attrValueSet[i].tag == DerValue.tag_Boolean) { 281 values[i] = String.valueOf(attrValueSet[i].getBoolean()); 282 } else { 283 values[i] = Debug.toString(attrValueSet[i].getDataBytes()); 284 } 285 } 286 287 this.name = type.toString(); 288 this.value = values.length == 1 ? values[0] : Arrays.toString(values); 289 } 290 } 291