Path: blob/master/src/java.base/share/classes/java/security/PKCS12Attribute.java
41152 views
/*1* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.security;2627import java.io.IOException;28import java.math.BigInteger;29import java.util.Arrays;30import java.util.regex.Pattern;31import sun.security.util.*;3233/**34* An attribute associated with a PKCS12 keystore entry.35* The attribute name is an ASN.1 Object Identifier and the attribute36* value is a set of ASN.1 types.37*38* @since 1.839*/40public final class PKCS12Attribute implements KeyStore.Entry.Attribute {4142private static final Pattern COLON_SEPARATED_HEX_PAIRS =43Pattern.compile("^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2})+$");44private String name;45private String value;46private final byte[] encoded;47private int hashValue = -1;4849/**50* Constructs a PKCS12 attribute from its name and value.51* The name is an ASN.1 Object Identifier represented as a list of52* dot-separated integers.53* A string value is represented as the string itself.54* A binary value is represented as a string of colon-separated55* pairs of hexadecimal digits.56* Multi-valued attributes are represented as a comma-separated57* list of values, enclosed in square brackets. See58* {@link Arrays#toString(java.lang.Object[])}.59* <p>60* A string value will be DER-encoded as an ASN.1 UTF8String and a61* binary value will be DER-encoded as an ASN.1 Octet String.62*63* @param name the attribute's identifier64* @param value the attribute's value65*66* @throws NullPointerException if {@code name} or {@code value}67* is {@code null}68* @throws IllegalArgumentException if {@code name} or69* {@code value} is incorrectly formatted70*/71public PKCS12Attribute(String name, String value) {72if (name == null || value == null) {73throw new NullPointerException();74}75// Validate name76ObjectIdentifier type;77try {78type = ObjectIdentifier.of(name);79} catch (IOException e) {80throw new IllegalArgumentException("Incorrect format: name", e);81}82this.name = name;8384// Validate value85int length = value.length();86String[] values;87if (length > 1 &&88value.charAt(0) == '[' && value.charAt(length - 1) == ']') {89values = value.substring(1, length - 1).split(", ");90} else {91values = new String[]{ value };92}93this.value = value;9495try {96this.encoded = encode(type, values);97} catch (IOException e) {98throw new IllegalArgumentException("Incorrect format: value", e);99}100}101102/**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 AttributeValue110* }111* AttributeType ::= OBJECT IDENTIFIER112* AttributeValue ::= ANY defined by type113*114* </pre>115*116* @param encoded the attribute's ASN.1 DER encoding. It is cloned117* to prevent subsequent modification.118*119* @throws NullPointerException if {@code encoded} is120* {@code null}121* @throws IllegalArgumentException if {@code encoded} is122* incorrectly formatted123*/124public PKCS12Attribute(byte[] encoded) {125if (encoded == null) {126throw new NullPointerException();127}128this.encoded = encoded.clone();129130try {131parse(encoded);132} catch (IOException e) {133throw new IllegalArgumentException("Incorrect format: encoded", e);134}135}136137/**138* Returns the attribute's ASN.1 Object Identifier represented as a139* list of dot-separated integers.140*141* @return the attribute's identifier142*/143@Override144public String getName() {145return name;146}147148/**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 following151* {@code String} formats:152* <ul>153* <li> the DER encoding of a basic ASN.1 type that has a natural154* string representation is returned as the string itself.155* Such types are currently limited to BOOLEAN, INTEGER,156* OBJECT IDENTIFIER, UTCTime, GeneralizedTime and the157* following six ASN.1 string types: UTF8String,158* PrintableString, T61String, IA5String, BMPString and159* GeneralString.160* <li> the DER encoding of any other ASN.1 type is not decoded but161* returned as a binary string of colon-separated pairs of162* hexadecimal digits.163* </ul>164* Multi-valued attributes are represented as a comma-separated165* list of values, enclosed in square brackets. See166* {@link Arrays#toString(java.lang.Object[])}.167*168* @return the attribute value's string encoding169*/170@Override171public String getValue() {172return value;173}174175/**176* Returns the attribute's ASN.1 DER encoding.177*178* @return a clone of the attribute's DER encoding179*/180public byte[] getEncoded() {181return encoded.clone();182}183184/**185* Compares this {@code PKCS12Attribute} and a specified object for186* equality.187*188* @param obj the comparison object189*190* @return true if {@code obj} is a {@code PKCS12Attribute} and191* their DER encodings are equal.192*/193@Override194public boolean equals(Object obj) {195if (this == obj) {196return true;197}198if (!(obj instanceof PKCS12Attribute)) {199return false;200}201return Arrays.equals(encoded, ((PKCS12Attribute) obj).encoded);202}203204/**205* Returns the hashcode for this {@code PKCS12Attribute}.206* The hash code is computed from its DER encoding.207*208* @return the hash code209*/210@Override211public int hashCode() {212int h = hashValue;213if (h == -1) {214hashValue = h = Arrays.hashCode(encoded);215}216return h;217}218219/**220* Returns a string representation of this {@code PKCS12Attribute}.221*222* @return a name/value pair separated by an 'equals' symbol223*/224@Override225public String toString() {226return (name + "=" + value);227}228229private byte[] encode(ObjectIdentifier type, String[] values)230throws IOException {231DerOutputStream attribute = new DerOutputStream();232attribute.putOID(type);233DerOutputStream attrContent = new DerOutputStream();234for (String value : values) {235if (COLON_SEPARATED_HEX_PAIRS.matcher(value).matches()) {236byte[] bytes =237new BigInteger(value.replace(":", ""), 16).toByteArray();238if (bytes[0] == 0) {239bytes = Arrays.copyOfRange(bytes, 1, bytes.length);240}241attrContent.putOctetString(bytes);242} else {243attrContent.putUTF8String(value);244}245}246attribute.write(DerValue.tag_Set, attrContent);247DerOutputStream attributeValue = new DerOutputStream();248attributeValue.write(DerValue.tag_Sequence, attribute);249250return attributeValue.toByteArray();251}252253private void parse(byte[] encoded) throws IOException {254DerInputStream attributeValue = new DerInputStream(encoded);255DerValue[] attrSeq = attributeValue.getSequence(2);256if (attrSeq.length != 2) {257throw new IOException("Invalid length for PKCS12Attribute");258}259ObjectIdentifier type = attrSeq[0].getOID();260DerInputStream attrContent =261new DerInputStream(attrSeq[1].toByteArray());262DerValue[] attrValueSet = attrContent.getSet(1);263String[] values = new String[attrValueSet.length];264String printableString;265for (int i = 0; i < attrValueSet.length; i++) {266if (attrValueSet[i].tag == DerValue.tag_OctetString) {267values[i] = Debug.toString(attrValueSet[i].getOctetString());268} else if ((printableString = attrValueSet[i].getAsString())269!= null) {270values[i] = printableString;271} else if (attrValueSet[i].tag == DerValue.tag_ObjectId) {272values[i] = attrValueSet[i].getOID().toString();273} else if (attrValueSet[i].tag == DerValue.tag_GeneralizedTime) {274values[i] = attrValueSet[i].getGeneralizedTime().toString();275} else if (attrValueSet[i].tag == DerValue.tag_UtcTime) {276values[i] = attrValueSet[i].getUTCTime().toString();277} else if (attrValueSet[i].tag == DerValue.tag_Integer) {278values[i] = attrValueSet[i].getBigInteger().toString();279} else if (attrValueSet[i].tag == DerValue.tag_Boolean) {280values[i] = String.valueOf(attrValueSet[i].getBoolean());281} else {282values[i] = Debug.toString(attrValueSet[i].getDataBytes());283}284}285286this.name = type.toString();287this.value = values.length == 1 ? values[0] : Arrays.toString(values);288}289}290291292