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