diff options
Diffstat (limited to 'pki/base/util/src/netscape/security/pkcs')
11 files changed, 3638 insertions, 0 deletions
diff --git a/pki/base/util/src/netscape/security/pkcs/ContentInfo.java b/pki/base/util/src/netscape/security/pkcs/ContentInfo.java new file mode 100644 index 000000000..91cc413bb --- /dev/null +++ b/pki/base/util/src/netscape/security/pkcs/ContentInfo.java @@ -0,0 +1,152 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package netscape.security.pkcs; + +import java.io.*; + +import netscape.security.util.*; + +/** + * A ContentInfo type, as defined in PKCS#7. + * + * @version 1.12 + * @author Benjamin Renaud + */ + +public class ContentInfo { + + // pkcs7 pre-defined content types + private static int[] pkcs7 = {1, 2, 840, 113549, 1, 7}; + private static int[] data = {1, 2, 840, 113549, 1, 7, 1}; + private static int[] sdata = {1, 2, 840, 113549, 1, 7, 2}; + private static int[] edata = {1, 2, 840, 113549, 1, 7, 3}; + private static int[] sedata = {1, 2, 840, 113549, 1, 7, 4}; + private static int[] ddata = {1, 2, 840, 113549, 1, 7, 5}; + private static int[] crdata = {1, 2, 840, 113549, 1, 7, 6}; + + public static final ObjectIdentifier PKCS7_OID = + new ObjectIdentifier(pkcs7); + + public static final ObjectIdentifier DATA_OID = + new ObjectIdentifier(data); + + public static final ObjectIdentifier SIGNED_DATA_OID = + new ObjectIdentifier(sdata); + + public static final ObjectIdentifier ENVELOPED_DATA_OID = + new ObjectIdentifier(edata); + + public static final ObjectIdentifier SIGNED_AND_ENVELOPED_DATA_OID = + new ObjectIdentifier(sedata); + + public static final ObjectIdentifier DIGESTED_DATA_OID = + new ObjectIdentifier(ddata); + + public static final ObjectIdentifier ENCRYPTED_DATA_OID = + new ObjectIdentifier(crdata); + + ObjectIdentifier contentType; + DerValue content; // OPTIONAL + + public ContentInfo(ObjectIdentifier contentType, DerValue content) { + this.contentType = contentType; + this.content = content; + } + + /** + * Make a contentInfo of type data. + */ + public ContentInfo(byte[] bytes) { + DerValue octetString = new DerValue(DerValue.tag_OctetString, bytes); + this.contentType = DATA_OID; + this.content = octetString; + } + + public ContentInfo(DerInputStream derin) + throws IOException, ParsingException { + DerInputStream disType; + DerInputStream disTaggedContent; + DerValue type; + DerValue taggedContent; + DerValue[] typeAndContent; + DerValue[] contents; + + typeAndContent = derin.getSequence(2); + + // Parse the content type + type = typeAndContent[0]; + disType = new DerInputStream(type.toByteArray()); + contentType = disType.getOID(); + + // Parse the content (OPTIONAL field). + // Skip the [0] EXPLICIT tag by pretending that the content is the one + // and only element in an implicitly tagged set + if (typeAndContent.length > 1) { // content is OPTIONAL + taggedContent = typeAndContent[1]; + disTaggedContent = new DerInputStream(taggedContent.toByteArray()); + contents = disTaggedContent.getSet(1, true); + content = contents[0]; + } + } + + public DerValue getContent() { + return content; + } + + public byte[] getData() throws IOException { + if (contentType.equals(DATA_OID)) { + return content.getOctetString(); + } + throw new IOException("content type is not DATA: " + contentType); + } + + public void encode(DerOutputStream out) throws IOException { + DerOutputStream contentDerCode; + DerOutputStream seq; + DerValue taggedContent; + + contentDerCode = new DerOutputStream(); + content.encode(contentDerCode); + // Add the [0] EXPLICIT tag in front of the content encoding + taggedContent = new DerValue((byte)0xA0, + contentDerCode.toByteArray()); + + seq = new DerOutputStream(); + seq.putOID(contentType); + seq.putDerValue(taggedContent); + + out.write(DerValue.tag_Sequence, seq); + } + + /** + * Returns a byte array representation of the data held in + * the content field. + */ + public byte[] getContentBytes() throws IOException { + DerInputStream dis = new DerInputStream(content.toByteArray()); + return dis.getOctetString(); + } + + public String toString() { + String out = ""; + + out += "Content Info Sequence\n\tContent type: " + contentType + "\n"; + out += "\tContent: " + content; + return out; + } +} diff --git a/pki/base/util/src/netscape/security/pkcs/EncodingException.java b/pki/base/util/src/netscape/security/pkcs/EncodingException.java new file mode 100644 index 000000000..7f383443a --- /dev/null +++ b/pki/base/util/src/netscape/security/pkcs/EncodingException.java @@ -0,0 +1,28 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package netscape.security.pkcs; + +public class EncodingException extends Exception { + public EncodingException() { + super(); + } + + public EncodingException(String s) { + super(s); + } +} diff --git a/pki/base/util/src/netscape/security/pkcs/PKCS10.java b/pki/base/util/src/netscape/security/pkcs/PKCS10.java new file mode 100644 index 000000000..832741faf --- /dev/null +++ b/pki/base/util/src/netscape/security/pkcs/PKCS10.java @@ -0,0 +1,350 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package netscape.security.pkcs; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.IOException; +import java.security.cert.CertificateException; +import java.security.NoSuchAlgorithmException; +import java.security.InvalidKeyException; +import java.security.Signature; +import java.security.SignatureException; +import java.security.PublicKey; + + +import netscape.security.util.*; // DER +import netscape.security.x509.AlgorithmId; +import netscape.security.x509.X509Key; +import netscape.security.x509.X500Name; +import netscape.security.x509.X500Signer; +import netscape.security.x509.X500Signer; + +/** + * PKCS #10 certificate requests are created and sent to Certificate + * Authorities, which then create X.509 certificates and return them to + * the entity which created the certificate request. These cert requests + * basically consist of the subject's X.500 name and public key, signed + * using the corresponding private key. + * + * The ASN.1 syntax for a Certification Request is: + * <pre> + * CertificationRequest ::= SEQUENCE { + * certificationRequestInfo CertificationRequestInfo, + * signatureAlgorithm SignatureAlgorithmIdentifier, + * signature Signature + * } + * + * SignatureAlgorithmIdentifier ::= AlgorithmIdentifier + * Signature ::= BIT STRING + * + * CertificationRequestInfo ::= SEQUENCE { + * version Version, + * subject Name, + * subjectPublicKeyInfo SubjectPublicKeyInfo, + * attributes [0] IMPLICIT Attributes + * } + * Attributes ::= SET OF Attribute + * </pre> + * + * @author David Brownell + * @author Amit Kapoor + * @author Hemma Prafullchandra + * @version 1.28 + */ +public class PKCS10 +{ + /** + * Constructs an unsigned PKCS #10 certificate request. Before this + * request may be used, it must be encoded and signed. Then it + * must be retrieved in some conventional format (e.g. string). + * + * @param publicKey the public key that should be placed + * into the certificate generated by the CA. + */ + public PKCS10 (X509Key publicKey) + { + subjectPublicKeyInfo = publicKey; + attributeSet = new PKCS10Attributes(); + } + + + /** + * Constructs an unsigned PKCS #10 certificate request. Before this + * request may be used, it must be encoded and signed. Then it + * must be retrieved in some conventional format (e.g. string). + * + * @param publicKey the public key that should be placed + * into the certificate generated by the CA. + * @param attributes additonal set of PKCS10 attributes requested + * for in the certificate. + */ + public PKCS10 (X509Key publicKey, PKCS10Attributes attributes) + { + subjectPublicKeyInfo = publicKey; + if (attributes != null) + attributeSet = attributes; + else + attributeSet = new PKCS10Attributes(); + } + + + /** + * Parses an encoded, signed PKCS #10 certificate request, verifying + * the request's signature as it does so. This constructor would + * typically be used by a Certificate Authority, from which a new + * certificate would then be constructed. + * + * @param data the DER-encoded PKCS #10 request. + * @param sigver boolean specifies signature verification enabled or not + * @exception IOException for low level errors reading the data + * @exception SignatureException when the signature is invalid + * @exception NoSuchAlgorithmException when the signature + * algorithm is not supported in this environment + */ + public PKCS10 (byte data [], boolean sigver) + throws IOException, SignatureException, NoSuchAlgorithmException,java.security.NoSuchProviderException + { + DerInputStream in; + DerValue seq []; + AlgorithmId id; + byte sigData []; + Signature sig; + + certificateRequest = data; + + // + // Outer sequence: request, signature algorithm, signature. + // Parse, and prepare to verify later. + // + in = new DerInputStream (data); + seq = in.getSequence (3); + + if (seq.length != 3) + throw new IllegalArgumentException ("not a PKCS #10 request"); + + data = seq [0].toByteArray (); // reusing this variable + certRequestInfo = seq[0].toByteArray(); // make a copy + id = AlgorithmId.parse (seq [1]); + sigData = seq [2].getBitString (); + + // + // Inner sequence: version, name, key, attributes + // + BigInt serial; + DerValue val; + + serial = seq [0].data.getInteger (); +/* + if (serial.toInt () != 0) + throw new IllegalArgumentException ("not PKCS #10 v1"); +*/ + + subject = new X500Name (seq [0].data); + + + byte val1[] = seq [0].data.getDerValue ().toByteArray(); + subjectPublicKeyInfo = X509Key.parse (new DerValue(val1)); + PublicKey publicKey = X509Key.parsePublicKey (new DerValue(val1)); + + String keystr = subjectPublicKeyInfo.toString(); + + // Cope with a somewhat common illegal PKCS #10 format + if (seq [0].data.available () != 0) + attributeSet = new PKCS10Attributes(seq [0].data); + else + attributeSet = new PKCS10Attributes(); + + // + // OK, we parsed it all ... validate the signature using the + // key and signature algorithm we found. + // temporary commented out + try { + String idName = id.getName (); + if(idName.equals("MD5withRSA")) + idName = "MD5/RSA"; + else if(idName.equals("MD2withRSA")) + idName = "MD2/RSA"; + else if(idName.equals("SHA1withRSA")) + idName = "SHA1/RSA"; + else if(idName.equals("SHA1withDSA")) + idName = "SHA1/DSA"; + + if (sigver) { + sig = Signature.getInstance(idName,"Mozilla-JSS"); + + sig.initVerify (publicKey); + sig.update (data); + if (!sig.verify (sigData)) + throw new SignatureException ("Invalid PKCS #10 signature"); + } + } catch (InvalidKeyException e) { + throw new SignatureException ("invalid key"); + } + } + + public PKCS10 (byte data []) + throws IOException, SignatureException, NoSuchAlgorithmException,java.security.NoSuchProviderException + { + this(data, true); + } + + /** + * Create the signed certificate request. This will later be + * retrieved in either string or binary format. + * + * @param requester identifies the signer (by X.500 name) + * and provides the private key used to sign. + * @exception IOException on errors. + * @exception CertificateException on certificate handling errors. + * @exception SignatureException on signature handling errors. + */ + public void encodeAndSign (X500Signer requester) + throws CertificateException, IOException, SignatureException + { + DerOutputStream out, scratch; + byte certificateRequestInfo []; + byte sig []; + + if (certificateRequest != null) + throw new SignatureException ("request is already signed"); + + subject = requester.getSigner (); + + /* + * Encode cert request info, wrap in a sequence for signing + */ + scratch = new DerOutputStream (); + scratch.putInteger (new BigInt (0)); // version zero + subject.encode (scratch); // X.500 name + subjectPublicKeyInfo.encode (scratch); // public key + attributeSet.encode (scratch); + + out = new DerOutputStream (); + out.write (DerValue.tag_Sequence, scratch); // wrap it! + certificateRequestInfo = out.toByteArray (); + scratch = out; + + /* + * Sign it ... + */ + requester.update (certificateRequestInfo, 0, + certificateRequestInfo.length); + sig = requester.sign (); + + /* + * Build guts of SIGNED macro + */ + requester.getAlgorithmId ().encode (scratch); // sig algorithm + scratch.putBitString (sig); // sig + + /* + * Wrap those guts in a sequence + */ + out = new DerOutputStream (); + out.write (DerValue.tag_Sequence, scratch); + certificateRequest = out.toByteArray (); + } + + + /** + * Returns the subject's name. + */ + public X500Name getSubjectName () + { return subject; } + + + /** + * Returns the subject's public key. + */ + public X509Key getSubjectPublicKeyInfo () + { return subjectPublicKeyInfo; } + + + /** + * Returns the additional attributes requested. + */ + public PKCS10Attributes getAttributes () + { return attributeSet; } + + /** + * Returns the encoded and signed certificate request as a + * DER-encoded byte array. + * + * @return the certificate request, or null if encodeAndSign() + * has not yet been called. + */ + public byte [] toByteArray () + { + return certificateRequest; + } + + + /** + * Prints an E-Mailable version of the certificate request on the print + * stream passed. The format is a common base64 encoded one, supported + * by most Certificate Authorities because Netscape web servers have + * used this for some time. Some certificate authorities expect some + * more information, in particular contact information for the web + * server administrator. + * + * @param out the print stream where the certificate request + * will be printed. + * @exception IOException when an output operation failed + * @exception SignatureException when the certificate request was + * not yet signed. + */ + public void print (PrintStream out) + throws IOException, SignatureException + { + if (certificateRequest == null) + throw new SignatureException ("Cert request was not signed"); + + + out.println ("-----BEGIN NEW CERTIFICATE REQUEST-----"); + out.println (com.netscape.osutil.OSUtil.BtoA(certificateRequest)); + out.println ("-----END NEW CERTIFICATE REQUEST-----"); + } + + /** + * Provides a short description of this request. + */ + public String toString () + { + return "[PKCS #10 certificate request:\n" + + subjectPublicKeyInfo.toString() + + " subject: <" + subject + ">" + "\n" + + " attributes: " + attributeSet.toString() + + "\n]"; + } + + /** + * Retrieve the PKCS10 CertificateRequestInfo as a byte array + */ + public byte[] getCertRequestInfo() + { + return certRequestInfo; + } + + private X500Name subject; + private X509Key subjectPublicKeyInfo; + private PKCS10Attributes attributeSet; + + private byte certificateRequest []; // signed + private byte certRequestInfo []; // inner content signed +} diff --git a/pki/base/util/src/netscape/security/pkcs/PKCS10Attribute.java b/pki/base/util/src/netscape/security/pkcs/PKCS10Attribute.java new file mode 100644 index 000000000..cadfa0cef --- /dev/null +++ b/pki/base/util/src/netscape/security/pkcs/PKCS10Attribute.java @@ -0,0 +1,236 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package netscape.security.pkcs; + +import java.io.OutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.security.cert.CertificateException; + +import netscape.security.util.*; +import netscape.security.x509.CertAttrSet; +import netscape.security.x509.OIDMap; +import netscape.security.x509.Extension; +import netscape.security.x509.ACertAttrSet; +import netscape.security.x509.Extensions; + + +/** + * Represent a PKCS Attribute. + * + * <p>Attributes are addiitonal attributes which can be inserted in a PKCS + * certificate request. For example a "Driving License Certificate" could have + * the driving license number as a attribute. + * + * <p>Attributes are represented as a sequence of the attribute identifier + * (Object Identifier) and a set of DER encoded attribute values. The current + * implementation only supports one value per attribute. + * + * ASN.1 definition of Attribute: + * <pre> + * Attribute :: SEQUENCE { + * type AttributeValue, + * values SET OF AttributeValue + * } + * AttributeValue ::= ANY + * </pre> + * + * @author Amit Kapoor + * @author Hemma Prafullchandra + * @version 1.13 + */ +public class PKCS10Attribute implements DerEncoder, Serializable { + protected ObjectIdentifier attributeId = null; + protected CertAttrSet attributeValue = null; + + /** + * Default constructor. Used only by sub-classes. + */ + public PKCS10Attribute() { + } + + /** + * Constructs an attribute from a DER encoded array of bytes. + */ + public PKCS10Attribute(DerValue derVal) throws IOException { + if (derVal.tag != DerValue.tag_Sequence) { + throw new IOException("Sequence tag missing for PKCS10Attribute."); + } + + DerInputStream in = derVal.toDerInputStream(); + // Object identifier + attributeId = in.getOID(); + // System.out.println("attribute ID in pkcs10 "+attributeId.toString()); + + // Rest of the stuff is attribute value(s), wrapped in a SET. + // For now, assume there is only one attribute value present. + DerValue[] inAttrValues = in.getSet(1); + int attrValueNum = inAttrValues.length; + if (attrValueNum > 1) { + throw new IOException("More than one value per attribute not supported"); + } + + // Read the first attribute value + DerValue inAttrValue = inAttrValues[0]; + + if (attributeId.equals(PKCS9Attribute.EXTENSION_REQUEST_OID)) { + //pkcs9 extensionAttr + try{ + // remove the tag + //DerValue dv = inAttrValue.data.getDerValue(); + // hack. toDerInputStream only gives one extension. + DerInputStream fi = new DerInputStream(inAttrValue.toByteArray()); + attributeValue = (CertAttrSet) new + Extensions(fi); + //CertificateExtensions(fi); + return; + } catch(Exception e) { + throw new IOException(e.toString()); + } + } + byte[] val = inAttrValue.toByteArray(); + Class[] params = { Object.class }; + try { + Class extClass = OIDMap.getClass(attributeId); + if (extClass != null) { + Constructor cons = extClass.getConstructor(params); + Object value = Array.newInstance(byte.class,val.length); + for (int i = 0; i < val.length; i++) { + Array.setByte(value,i,val[i]); + } + Object[] passed = new Object[] {value}; + attributeValue = (CertAttrSet) cons.newInstance(passed); + } else { + // attribute classes are usable for PKCS10 attributes. + // this is used where the attributes are not actual + // implemented extensions. + attributeValue = new ACertAttrSet(inAttrValue); + } + } + catch (InvocationTargetException invk) { + throw new IOException(invk.getTargetException().getMessage()); + } + catch (Exception e) { + throw new IOException(e.toString()); + } + } + + /** + * Constructs an attribute from individual components of ObjectIdentifier + * and the DER encoded value. + * + * @param attributeId the ObjectIdentifier of the attribute. + * @param attributeValue the CertAttrSet. + */ + public PKCS10Attribute(ObjectIdentifier attributeId, + CertAttrSet attributeValue) { + this.attributeId = attributeId; + this.attributeValue = attributeValue; + } + + /** + * Constructs an attribute from another attribute. To be used for + * creating decoded subclasses. + * + * @param attr the attribute to create from. + */ + public PKCS10Attribute(PKCS10Attribute attr) { + this.attributeId = attr.attributeId; + this.attributeValue = attr.attributeValue; + } + + /** + * Write the output to the DerOutputStream. + * + * @param out the OutputStream to write the attribute to. + * @exception CertificateException on certificate encoding errors. + * @exception IOException on encoding errors. + */ + public void encode(OutputStream out) + throws CertificateException, IOException { + // Encode the attribute value + DerOutputStream outAttrValue = new DerOutputStream(); + attributeValue.encode(outAttrValue); + + // Wrap the encoded attribute value into a SET + DerValue outAttrValueSet = new DerValue(DerValue.tag_Set, + outAttrValue.toByteArray()); + + // Create the attribute + DerOutputStream outAttr = new DerOutputStream(); + outAttr.putOID(attributeId); + outAttr.putDerValue(outAttrValueSet); + + // Wrap the OID and the set of attribute values into a SEQUENCE + DerOutputStream tmp = new DerOutputStream(); + tmp.write(DerValue.tag_Sequence, outAttr); + + // write the results to out + out.write(tmp.toByteArray()); + } + + /** + * DER encode this object onto an output stream. + * Implements the <code>DerEncoder</code> interface. + * + * @param out + * the OutputStream on which to write the DER encoding. + * + * @exception IOException on encoding errors. + */ + public void derEncode (OutputStream out) throws IOException + { + try { + encode(out); + } catch (CertificateException ce) { + IOException ioe = new IOException(ce.toString()); + ioe.fillInStackTrace(); + throw ioe; + } + } + + /** + * Returns the ObjectIdentifier of the attribute. + */ + public ObjectIdentifier getAttributeId() { + return (attributeId); + } + + /** + * Returns the attribute value as an byte array for further processing. + */ + public CertAttrSet getAttributeValue() { + return (attributeValue); + } + + /** + * Returns the attribute in user readable form. + */ + public String toString() { + String s = "AttributeId: " + attributeId.toString() + "\n"; + s += "AttributeValue: " + attributeValue.toString(); + + return (s); + } +} + + + diff --git a/pki/base/util/src/netscape/security/pkcs/PKCS10Attributes.java b/pki/base/util/src/netscape/security/pkcs/PKCS10Attributes.java new file mode 100644 index 000000000..9e7e2f7a4 --- /dev/null +++ b/pki/base/util/src/netscape/security/pkcs/PKCS10Attributes.java @@ -0,0 +1,142 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package netscape.security.pkcs; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.cert.CertificateException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import netscape.security.util.*; + +/** + * This class defines the PKCS10 attributes for the request. + * + * @author Amit Kapoor + * @author Hemma Prafullchandra + * @version 1.10 + */ +public class PKCS10Attributes extends Vector implements DerEncoder { + + private Hashtable map; + + /** + * Default constructor for the certificate attribute. + */ + public PKCS10Attributes() { + map = new Hashtable(); + } + + /** + * Create the object, decoding the values from the passed DER stream. + * + * @param in the DerInputStream to read the attributes from. + * @exception IOException on decoding errors. + */ + public PKCS10Attributes(DerInputStream in) + throws IOException { + + map = new Hashtable(); + DerValue [] attrs = in.getSet(5,true); + + if (attrs != null) { + for (int i = 0; i < attrs.length; i++) { + PKCS10Attribute attr = new PKCS10Attribute(attrs[i]); + addElement(attr); + map.put(attr.getAttributeValue().getName(),attr); + } + } + } + + + /** + * Encode the attributes in DER form to the stream. + * + * @param out the OutputStream to marshal the contents to. + * + * @exception IOException on encoding errors. + */ + public void encode(OutputStream out) + throws IOException { + derEncode(out); + } + + /** + * Encode the attributes in DER form to the stream. + * Implements the <code>DerEncoder</code> interface. + * + * @param out the OutputStream to marshal the contents to. + * @exception IOException on encoding errors. + */ + public void derEncode(OutputStream out) + throws IOException { + + // first copy the elements into an array + PKCS10Attribute[] attribs = new PKCS10Attribute[size()]; + copyInto(attribs); + + DerOutputStream attrOut = new DerOutputStream(); + attrOut.putOrderedSetOf(DerValue.createTag(DerValue.TAG_CONTEXT,true,(byte)0), + attribs); + + out.write(attrOut.toByteArray()); + } + + /** + * Set the attribute value. + */ + public void setAttribute(String name, Object obj) throws IOException { + map.put(name,obj); + addElement(obj); + } + + /** + * Get the attribute value. + */ + public Object getAttribute(String name) throws IOException { + Object obj = map.get(name); + /* + if (obj == null) { + throw new IOException("No attribute found with name " + name); + } + */ + return (obj); + } + + /** + * Delete the attribute value. + */ + public void deleteAttribute(String name) throws IOException { + Object obj = map.get(name); + if (obj == null) { + throw new IOException("No attribute found with name " + name); + } + map.remove(name); + removeElement(obj); + } + + /** + * Return an enumeration of names of attributes existing within this + * attribute. + */ + public Enumeration getElements () { + return (map.elements()); + } +} diff --git a/pki/base/util/src/netscape/security/pkcs/PKCS7.java b/pki/base/util/src/netscape/security/pkcs/PKCS7.java new file mode 100644 index 000000000..6e64b1686 --- /dev/null +++ b/pki/base/util/src/netscape/security/pkcs/PKCS7.java @@ -0,0 +1,443 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package netscape.security.pkcs; + +import java.io.*; +import java.util.*; +import java.math.BigInteger; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.security.cert.CertificateException; +import java.security.*; + +import netscape.security.util.*; +import netscape.security.x509.AlgorithmId; +import netscape.security.x509.X509CertImpl; +import netscape.security.x509.X500Name; + +/** + * PKCS7 as defined in RSA Laboratories PKCS7 Technical Note. Profile + * Supports only <tt>SignedData</tt> ContentInfo + * type, where to the type of data signed is plain Data. + * For signedData, <tt>crls</tt>, <tt>attributes</tt> and + * PKCS#6 Extended Certificates are not supported. + * + * @version 1.33 97/12/10 + * @author Benjamin Renaud + */ +public class PKCS7 { + + private ObjectIdentifier contentType; + + // the ASN.1 members for a signedData (and other) contentTypes + private BigInt version; + private AlgorithmId[] digestAlgorithmIds; + private ContentInfo contentInfo; + private X509Certificate[] certificates; + private SignerInfo[] signerInfos; + + /** + * Unmarshals a PKCS7 block from its encoded form, parsing the + * encoded bytes from the InputStream. + * + * @param in an input stream holding at least one PKCS7 block. + * @exception ParsingException on parsing errors. + * @exception IOException on other errors. + */ + public PKCS7(InputStream in) throws ParsingException, IOException { + DataInputStream dis = new DataInputStream(in); + + int len = 0; + byte[] newbuf = new byte[len]; + byte[] oldbuf = new byte[len]; + byte[] data = new byte[len]; + + do { + newbuf = new byte[dis.available()]; + len += dis.available(); + dis.readFully(newbuf); + data = new byte[len]; + + System.arraycopy(oldbuf, 0, data, 0, oldbuf.length); + System.arraycopy(newbuf, 0, data, oldbuf.length, newbuf.length); + oldbuf = new byte[len]; + System.arraycopy(data, 0, oldbuf, 0, data.length); + + } while (dis.available() > 0); + + parse(new DerInputStream(data)); + } + + /** + * Unmarshals a PKCS7 block from its encoded form, parsing the + * encoded bytes from the DerInputStream. + * + * @param derin a DerInputStream holding at least one PKCS7 block. + * @exception ParsingException on parsing errors. + */ + public PKCS7(DerInputStream derin) throws ParsingException { + parse(derin); + } + + /** + * Unmarshals a PKCS7 block from its encoded form, parsing the + * encoded bytes. + * + * @param bytes the encoded bytes. + * @exception ParsingException on parsing errors. + */ + public PKCS7(byte[] bytes) throws ParsingException { + DerInputStream derin = new DerInputStream(bytes); + parse(derin); + } + + private void parse(DerInputStream derin) throws ParsingException { + try { + ContentInfo contentInfo = new ContentInfo(derin); + contentType = contentInfo.contentType; + if (contentType.equals(ContentInfo.SIGNED_DATA_OID)) { + parseSignedData(contentInfo.getContent()); + } else { + throw new ParsingException("content type " + contentType + + " not supported."); + } + } catch (IOException e) { + ParsingException pe = + new ParsingException("IOException: " + e.getMessage()); + pe.fillInStackTrace(); + throw pe; + } + } + + /** + * Construct an initialized PKCS7 block. + * + * @param digestAlgorithmIds the message digest algorithm identifiers. + * @param contentInfo the content information. + * @param certificates an array of X.509 certificates. + * @param signerInfos an array of signer information. + */ + public PKCS7(AlgorithmId[] digestAlgorithmIds, + ContentInfo contentInfo, + X509Certificate[] certificates, + SignerInfo[] signerInfos) { + + version = new BigInt(1); + this.digestAlgorithmIds = digestAlgorithmIds; + this.contentInfo = contentInfo; + this.certificates = certificates; + this.signerInfos = signerInfos; + } + + private void parseSignedData(DerValue val) + throws ParsingException, IOException { + + DerInputStream dis = val.toDerInputStream(); + + // Version + version = dis.getInteger(); + + // digestAlgorithmIds + DerValue[] digestAlgorithmIdVals = dis.getSet(1); + int len = digestAlgorithmIdVals.length; + digestAlgorithmIds = new AlgorithmId[len]; + try { + for (int i = 0; i < len; i++) { + DerValue oid = digestAlgorithmIdVals[i]; + digestAlgorithmIds[i] = AlgorithmId.parse(oid); + } + + } catch (IOException e) { + ParsingException pe = + new ParsingException("Error parsing digest AlgorithmId IDs: " + + e.getMessage()); + pe.fillInStackTrace(); + throw pe; + } + // contentInfo + contentInfo = new ContentInfo(dis); + + /* + * check if certificates (implicit tag) are provided + * (certificates are OPTIONAL) + */ + if ((byte)(dis.peekByte()) == (byte)0xA0) { + DerValue[] certificateVals = dis.getSet(2, true); + + len = certificateVals.length; + certificates = new X509Certificate[len]; + + for (int i = 0; i < len; i++) { + try { + X509Certificate cert = (X509Certificate) new + X509CertImpl(certificateVals[i]); + certificates[i] = cert; + } catch (CertificateException e) { + ParsingException pe = + new ParsingException("CertificateException: " + + e.getMessage()); + pe.fillInStackTrace(); + throw pe; + } + } + } + + // check if crls (implicit tag) are provided (crls are OPTIONAL) + if ((byte)(dis.peekByte()) == (byte)0xA1) { + dis.getSet(0, true); + } + + // signerInfos + DerValue[] signerInfoVals = dis.getSet(1); + + len = signerInfoVals.length; + signerInfos = new SignerInfo[len]; + + for (int i = 0; i < len; i++) { + DerInputStream in = signerInfoVals[i].toDerInputStream(); + signerInfos[i] = new SignerInfo(in); + } + + } + + /** + * Encodes the signed data to an output stream. + * + * @param out the output stream to write the encoded data to. + * @exception IOException on encoding errors. + */ + public void encodeSignedData(OutputStream out) throws IOException { + DerOutputStream derout = new DerOutputStream(); + encodeSignedData(derout, true); + out.write(derout.toByteArray()); + } + + /** + * Like method above but not sorted. + */ + public void encodeSignedData(OutputStream out, boolean sort) + throws IOException { + DerOutputStream derout = new DerOutputStream(); + encodeSignedData(derout, sort); + out.write(derout.toByteArray()); + } + + /** + * encode signed data, sort certs by default. + */ + public void encodeSignedData(DerOutputStream out) + throws IOException { + encodeSignedData(out, true); + } + + /** + * Encodes the signed data to a DerOutputStream. + * + * @param out the DerOutputStream to write the encoded data to. + * @exception IOException on encoding errors. + */ + public void encodeSignedData(DerOutputStream out, boolean sort) + throws IOException { + + DerOutputStream signedData = new DerOutputStream(); + + // version + signedData.putInteger(version); + + // digestAlgorithmIds + signedData.putOrderedSetOf(DerValue.tag_Set, digestAlgorithmIds); + + // contentInfo + contentInfo.encode(signedData); + + // certificates + DerOutputStream certs = new DerOutputStream(); + + // cast to X509CertImpl[] since X509CertImpl implements DerEncoder + X509CertImpl implCerts[] = new X509CertImpl[certificates.length]; + try { + for (int i = 0; i < certificates.length; i++) { + implCerts[i] = (X509CertImpl) certificates[i]; + } + } catch (ClassCastException e) { + IOException ioe = + new IOException("Certificates in PKCS7 " + + "must be of class " + + "netscape.security.X509CertImpl"); + ioe.fillInStackTrace(); + } + + // Add the certificate set (tagged with [0] IMPLICIT) + // to the signed data + if (sort) { + signedData.putOrderedSetOf((byte)0xA0, implCerts); + } + else { + signedData.putSet((byte)0xA0, implCerts); + } + + // no crls (OPTIONAL field) + + // signerInfos + signedData.putOrderedSetOf(DerValue.tag_Set, signerInfos); + + // making it a signed data block + DerValue signedDataSeq = new DerValue(DerValue.tag_Sequence, + signedData.toByteArray()); + + // making it a content info sequence + ContentInfo block = new ContentInfo(ContentInfo.SIGNED_DATA_OID, + signedDataSeq); + + // writing out the contentInfo sequence + block.encode(out); + } + + /** + * This verifies a given SignerInfo. + * + * @param info the signer information. + * @param bytes the DER encoded content information. + * + * @exception NoSuchAlgorithmException on unrecognized algorithms. + * @exception SignatureException on signature handling errors. + */ + public SignerInfo verify(SignerInfo info, byte[] bytes) + throws NoSuchAlgorithmException, SignatureException { + return info.verify(this, bytes); + } + + /** + * Returns all signerInfos which self-verify. + * + * @param bytes the DER encoded content information. + * + * @exception NoSuchAlgorithmException on unrecognized algorithms. + * @exception SignatureException on signature handling errors. + */ + public SignerInfo[] verify(byte[] bytes) + throws NoSuchAlgorithmException, SignatureException { + + Vector intResult = new Vector(); + for (int i = 0; i < signerInfos.length; i++) { + + SignerInfo signerInfo = verify(signerInfos[i], bytes); + if (signerInfo != null) { + intResult.addElement(signerInfo); + } + } + if (intResult.size() != 0) { + + SignerInfo[] result = new SignerInfo[intResult.size()]; + intResult.copyInto(result); + return result; + } + return null; + } + + /** + * Returns all signerInfos which self-verify. + * + * @exception NoSuchAlgorithmException on unrecognized algorithms. + * @exception SignatureException on signature handling errors. + */ + public SignerInfo[] verify() + throws NoSuchAlgorithmException, SignatureException { + return verify(null); + } + + /** + * Returns the version number of this PKCS7 block. + */ + public BigInt getVersion() { + return version; + } + + /** + * Returns the message digest algorithms specified in this PKCS7 block. + */ + public AlgorithmId[] getDigestAlgorithmIds() { + return digestAlgorithmIds; + } + + /** + * Returns the content information specified in this PKCS7 block. + */ + public ContentInfo getContentInfo() { + return contentInfo; + } + + /** + * Returns the X.509 certificates listed in this PKCS7 block. + */ + public X509Certificate[] getCertificates() { + return certificates; + } + + /** + * Returns the signer's information specified in this PKCS7 block. + */ + public SignerInfo[] getSignerInfos() { + return signerInfos; + } + + /** + * Returns the X.509 certificate listed in this PKCS7 block + * which has a matching serial number and Issuer name, or + * null if one is not found. + * + * @param serial the serial number of the certificate to retrieve. + * @param name the Distinguished Name of the Issuer. + */ + public X509Certificate getCertificate(BigInt serial, X500Name name) { + + for (int i = 0; i < certificates.length; i++) { + X509Certificate cert = certificates[i]; + X500Name thisName = (X500Name)cert.getIssuerDN(); + BigInteger tmpSerial = (BigInteger)cert.getSerialNumber(); + BigInt thisSerial = new BigInt(tmpSerial); + if (serial.equals(thisSerial) && name.equals(thisName)) { + return cert; + } + } + return null; + } + + /** + * Returns the PKCS7 block in a printable string form. + */ + public String toString() { + String out = ""; + + out += "PKCS7 :: version: " + version + "\n"; + out += "PKCS7 :: digest AlgorithmIds: \n"; + for (int i = 0; i < digestAlgorithmIds.length; i++) { + out += "\t" + digestAlgorithmIds[i] + "\n"; + } + out += contentInfo + "\n"; + out += "PKCS7 :: certificates: \n"; + for (int i = 0; i < certificates.length; i++) { + out += "\t" + i + ". " + certificates[i] + "\n"; + } + out += "PKCS7 :: signer infos: \n"; + for (int i = 0; i < signerInfos.length; i++) { + out += ("\t" + i + ". " + signerInfos[i] + "\n"); + } + return out; + } +} diff --git a/pki/base/util/src/netscape/security/pkcs/PKCS8Key.java b/pki/base/util/src/netscape/security/pkcs/PKCS8Key.java new file mode 100644 index 000000000..29d87caff --- /dev/null +++ b/pki/base/util/src/netscape/security/pkcs/PKCS8Key.java @@ -0,0 +1,449 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package netscape.security.pkcs; + +import java.io.*; +import java.util.Properties; +import java.math.*; +import java.security.Key; +import java.security.PrivateKey; +import java.security.KeyFactory; +import java.security.Security; +import java.security.Provider; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; + +import netscape.security.x509.*; +import netscape.security.util.*; + +/** + * Holds a PKCS#8 key, for example a private key + * + * @version 1.30, 97/12/10 + * @author Dave Brownell + * @author Benjamin Renaud + */ +public class PKCS8Key implements PrivateKey { + + /** use serialVersionUID from JDK 1.1. for interoperability */ + private static final long serialVersionUID = -3836890099307167124L; + + /* The algorithm information (name, parameters, etc). */ + protected AlgorithmId algid; + + /* The key bytes, without the algorithm information */ + protected byte[] key; + + /* The encoded for the key. */ + protected byte[] encodedKey; + + /* The version for this key */ + public static final BigInteger version = BigInteger.valueOf(0); + + /** + * Default constructor. The key constructed must have its key + * and algorithm initialized before it may be used, for example + * by using <code>decode</code>. + */ + public PKCS8Key() { } + + /* + * Build and initialize as a "default" key. All PKCS#8 key + * data is stored and transmitted losslessly, but no knowledge + * about this particular algorithm is available. + */ + private PKCS8Key (AlgorithmId algid, byte key []) + throws InvalidKeyException { + this.algid = algid; + this.key = key; + encode(); + } + + /** + * Construct PKCS#8 subject public key from a DER value. If + * the runtime environment is configured with a specific class for + * this kind of key, a subclass is returned. Otherwise, a generic + * PKCS8Key object is returned. + * + * <P>This mechanism gurantees that keys (and algorithms) may be + * freely manipulated and transferred, without risk of losing + * information. Also, when a key (or algorithm) needs some special + * handling, that specific need can be accomodated. + * + * @param in the DER-encoded SubjectPublicKeyInfo value + * @exception IOException on data format errors + */ + public static PKCS8Key parse (DerValue in) throws IOException + { + AlgorithmId algorithm; + PKCS8Key subjectKey; + + if (in.tag != DerValue.tag_Sequence) + throw new IOException ("corrupt private key"); + + BigInteger parsedVersion = in.data.getInteger().toBigInteger(); + if (!version.equals(parsedVersion)) { + throw new IOException("version mismatch: (supported: " + + version + ", parsed: " + + parsedVersion); + } + + algorithm = AlgorithmId.parse (in.data.getDerValue ()); + + try { + subjectKey = buildPKCS8Key (algorithm, in.data.getOctetString ()); + + } catch (InvalidKeyException e) { + throw new IOException("corrupt private key"); + } + + if (in.data.available () != 0) + throw new IOException ("excess private key"); + return subjectKey; + } + + /** + * Parse the key bits. This may be redefined by subclasses to take + * advantage of structure within the key. For example, RSA public + * keys encapsulate two unsigned integers (modulus and exponent) as + * DER values within the <code>key</code> bits; Diffie-Hellman and + * DSS/DSA keys encapsulate a single unsigned integer. + * + * <P>This function is called when creating PKCS#8 SubjectPublicKeyInfo + * values using the PKCS8Key member functions, such as <code>parse</code> + * and <code>decode</code>. + * + * @exception IOException if a parsing error occurs. + * @exception InvalidKeyException if the key encoding is invalid. + */ + protected void parseKeyBits () throws IOException, InvalidKeyException { + encode(); + } + + /* + * Factory interface, building the kind of key associated with this + * specific algorithm ID or else returning this generic base class. + * See the description above. + */ + public static PKCS8Key buildPKCS8Key (AlgorithmId algid, byte[] key) + throws IOException, InvalidKeyException + { + /* + * Use the algid and key parameters to produce the ASN.1 encoding + * of the key, which will then be used as the input to the + * key factory. + */ + DerOutputStream pkcs8EncodedKeyStream = new DerOutputStream(); + encode(pkcs8EncodedKeyStream, algid, key); + PKCS8EncodedKeySpec pkcs8KeySpec + = new PKCS8EncodedKeySpec(pkcs8EncodedKeyStream.toByteArray()); + + try { + // Instantiate the key factory of the appropriate algorithm + KeyFactory keyFac = KeyFactory.getInstance(algid.getName()); + + // Generate the private key + PrivateKey privKey = keyFac.generatePrivate(pkcs8KeySpec); + + if (privKey instanceof PKCS8Key) { + /* + * Return specialized PKCS8Key, where the structure within the + * key has been parsed + */ + return (PKCS8Key)privKey; + } + } catch (NoSuchAlgorithmException e) { + // Return generic PKCS8Key with opaque key data (see below) + } catch (InvalidKeySpecException e) { + // Return generic PKCS8Key with opaque key data (see below) + } + + /* + * Try again using JDK1.1-style for backwards compatibility. + */ + String classname = ""; + try { + Properties props; + String keytype; + Provider sunProvider; + + sunProvider = Security.getProvider("SUN"); + if (sunProvider == null) + throw new InstantiationException(); + classname = sunProvider.getProperty("PrivateKey.PKCS#8." + + algid.getName()); + if (classname == null) { + throw new InstantiationException(); + } + + Class keyClass = Class.forName(classname); + Object inst; + PKCS8Key result; + + inst = keyClass.newInstance(); + if (inst instanceof PKCS8Key) { + result = (PKCS8Key) inst; + result.algid = algid; + result.key = key; + result.parseKeyBits(); + return result; + } + } catch (ClassNotFoundException e) { + } catch (InstantiationException e) { + } catch (IllegalAccessException e) { + // this should not happen. + throw new IOException (classname + " [internal error]"); + } + + PKCS8Key result = new PKCS8Key(); + result.algid = algid; + result.key = key; + return result; + } + + /** + * Returns the algorithm to be used with this key. + */ + public String getAlgorithm() { + return algid.getName(); + } + + /** + * Returns the algorithm ID to be used with this key. + */ + public AlgorithmId getAlgorithmId () { return algid; } + + /** + * PKCS#8 sequence on the DER output stream. + */ + public final void encode(DerOutputStream out) throws IOException + { + encode(out, this.algid, this.key); + } + + /** + * Returns the DER-encoded form of the key as a byte array. + */ + public synchronized byte[] getEncoded() { + byte[] result = null; + try { + result = encode(); + } catch (InvalidKeyException e) { + } + return result; + } + + /** + * Returns the format for this key: "PKCS#8" + */ + public String getFormat() { + return "PKCS#8"; + } + + /** + * Returns the DER-encoded form of the key as a byte array. + * + * @exception InvalidKeyException if an encoding error occurs. + */ + public byte[] encode() throws InvalidKeyException { + if (encodedKey == null) { + try { + DerOutputStream out; + + out = new DerOutputStream (); + encode (out); + encodedKey = out.toByteArray(); + + } catch (IOException e) { + throw new InvalidKeyException ("IOException : " + + e.getMessage()); + } + } + return copyEncodedKey(encodedKey); + } + + /* + * Returns a printable representation of the key + */ + public String toString () + { + netscape.security.util.PrettyPrintFormat pp = + new netscape.security.util.PrettyPrintFormat(" ", 20); + String keybits = pp.toHexString(key); + + return "algorithm = " + algid.toString () + + ", unparsed keybits = \n" + keybits; + } + + /** + * Initialize an PKCS8Key object from an input stream. The data + * on that input stream must be encoded using DER, obeying the + * PKCS#8 format: a sequence consisting of a version, an algorithm + * ID and a bit string which holds the key. (That bit string is + * often used to encapsulate another DER encoded sequence.) + * + * <P>Subclasses should not normally redefine this method; they should + * instead provide a <code>parseKeyBits</code> method to parse any + * fields inside the <code>key</code> member. + * + * @param in an input stream with a DER-encoded PKCS#8 + * SubjectPublicKeyInfo value + * + * @exception InvalidKeyException if a parsing error occurs. + */ + public void decode(InputStream in) throws InvalidKeyException + { + DerValue val; + + try { + val = new DerValue (in); + if (val.tag != DerValue.tag_Sequence) + throw new InvalidKeyException ("invalid key format"); + + + BigInteger version = val.data.getInteger().toBigInteger(); + if (!version.equals(this.version)) { + throw new IOException("version mismatch: (supported: " + + this.version + ", parsed: " + + version); + } + algid = AlgorithmId.parse (val.data.getDerValue ()); + key = val.data.getOctetString (); + parseKeyBits (); + if (val.data.available () != 0) + throw new InvalidKeyException ("excess key data"); + + } catch (IOException e) { + // e.printStackTrace (); + throw new InvalidKeyException("IOException : " + + e.getMessage()); + } + } + + public void decode(byte[] encodedKey) throws InvalidKeyException { + decode(new ByteArrayInputStream(encodedKey)); + } + + /** + * Serialization write ... PKCS#8 keys serialize as + * themselves, and they're parsed when they get read back. + */ + private synchronized void + writeObject (java.io.ObjectOutputStream stream) + throws IOException { + stream.write(getEncoded()); + } + + /** + * Serialization read ... PKCS#8 keys serialize as + * themselves, and they're parsed when they get read back. + */ + private synchronized void readObject (ObjectInputStream stream) + throws IOException { + + try { + decode(stream); + + } catch (InvalidKeyException e) { + e.printStackTrace(); + throw new IOException("deserialized key is invalid: " + + e.getMessage()); + } + } + + /* + * Make a copy of the encoded key. + */ + private byte[] copyEncodedKey(byte[] encodedKey) { + int len = encodedKey.length; + byte[] copy = new byte[len]; + System.arraycopy(encodedKey, 0, copy, 0, len); + return copy; + } + + /* + * Produce PKCS#8 encoding from algorithm id and key material. + */ + static void encode(DerOutputStream out, AlgorithmId algid, byte[] key) + throws IOException { + DerOutputStream tmp = new DerOutputStream(); + tmp.putInteger(new BigInt(version.toByteArray())); + algid.encode(tmp); + tmp.putOctetString(key); + out.write(DerValue.tag_Sequence, tmp); + } + + /** + * Compares two private keys. This returns false if the object with which + * to compare is not of type <code>Key</code>. + * Otherwise, the encoding of this key object is compared with the + * encoding of the given key object. + * + * @param object the object with which to compare + * @return <code>true</code> if this key has the same encoding as the + * object argument; <code>false</code> otherwise. + */ + public boolean equals(Object object) { + if (this == object) { + return true; + } + + if (object instanceof Key) { + + // this encoding + byte[] b1; + if (encodedKey != null) { + b1 = encodedKey; + } else { + b1 = getEncoded(); + } + + // that encoding + byte[] b2 = ((Key)object).getEncoded(); + + // do the comparison + int i; + if (b1.length != b2.length) + return false; + for (i = 0; i < b1.length; i++) { + if (b1[i] != b2[i]) { + return false; + } + } + return true; + } + + return false; + } + + /** + * Calculates a hash code value for this object. Objects + * which are equal will also have the same hashcode. + */ + public int hashCode() { + int retval = 0; + byte[] b1 = getEncoded(); + + for (int i = 1; i < b1.length; i++) { + retval += b1[i] * i; + } + return(retval); + } +} diff --git a/pki/base/util/src/netscape/security/pkcs/PKCS9Attribute.java b/pki/base/util/src/netscape/security/pkcs/PKCS9Attribute.java new file mode 100644 index 000000000..17eee2010 --- /dev/null +++ b/pki/base/util/src/netscape/security/pkcs/PKCS9Attribute.java @@ -0,0 +1,1145 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package netscape.security.pkcs; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; +import java.util.Hashtable; +import netscape.security.util.DerEncoder; +import netscape.security.util.DerValue; +import netscape.security.util.DerInputStream; +import netscape.security.util.DerOutputStream; +import netscape.security.util.ObjectIdentifier; +import netscape.security.x509.CertificateExtensions; +import java.security.cert.CertificateException; + +/** + * Class supporting any PKCS9 attribute except + * ExtendedCertificateAttribute. Supports DER decoding and access to + * attribute values, but not DER encoding or setting of values. + * + * @version 1.2 97/12/10 + * @author Douglas Hoover + */ +public class PKCS9Attribute implements DerEncoder { + + /* + * OIDs of PKCS #9 attribute types. + */ + private static final String RSADSI_str = "1.2.840.113549"; + private static final String PKCS_str = RSADSI_str + ".1"; + private static final String PKCS9_str = PKCS_str + ".9"; + + /** + * Array of attribute OIDs defined in PKCS9, by number. + */ + static final ObjectIdentifier[] PKCS9_OIDS = + //new ObjectIdentifier[10]; + // There are some Obsolete(?) attribute identifiers. + // This is mainly for extensionRequest (14) in pkcs10. + // We just add the other 4 as by products. + new ObjectIdentifier[15]; + + static { // static initializer for PKCS9_OIDS + for (int i = 1; i < PKCS9_OIDS.length; i++) { + PKCS9_OIDS[i] = new ObjectIdentifier(PKCS9_str + "." + i); + } + } + + public static final ObjectIdentifier EMAIL_ADDRESS_OID = PKCS9_OIDS[1]; + public static final ObjectIdentifier UNSTRUCTURED_NAME_OID = PKCS9_OIDS[2]; + public static final ObjectIdentifier CONTENT_TYPE_OID = PKCS9_OIDS[3]; + public static final ObjectIdentifier MESSAGE_DIGEST_OID = PKCS9_OIDS[4]; + public static final ObjectIdentifier SIGNING_TIME_OID = PKCS9_OIDS[5]; + public static final ObjectIdentifier COUNTERSIGNATURE_OID = PKCS9_OIDS[6]; + public static final ObjectIdentifier CHALLENGE_PASSWORD_OID = PKCS9_OIDS[7]; + public static final ObjectIdentifier UNSTRUCTURED_ADDRESS_OID = PKCS9_OIDS[8]; + public static final ObjectIdentifier + EXTENDED_CERTIFICATE_ATTRIBUTES_OID = PKCS9_OIDS[9]; + + public static final ObjectIdentifier + ISSUER_AND_SERIALNUMBER_OID = PKCS9_OIDS[10]; + public static final ObjectIdentifier + PASSWORD_CHECK_OID = PKCS9_OIDS[11]; + public static final ObjectIdentifier + PUBLIC_KEY_OID = PKCS9_OIDS[12]; + public static final ObjectIdentifier + SIGNING_DESCRIPTION_OID = PKCS9_OIDS[13]; + public static final ObjectIdentifier + EXTENSION_REQUEST_OID = PKCS9_OIDS[14]; + + public static final String EMAIL_ADDRESS_STR = "EmailAddress"; + public static final String UNSTRUCTURED_NAME_STR = "UnstructuredName"; + public static final String CONTENT_TYPE_STR = "ContentType"; + public static final String MESSAGE_DIGEST_STR = "MessageDigest"; + public static final String SIGNING_TIME_STR = "SigningTime"; + public static final String COUNTERSIGNATURE_STR = "Countersignature"; + public static final String CHALLENGE_PASSWORD_STR = "ChallengePassword"; + public static final String UNSTRUCTURED_ADDRESS_STR = "UnstructuredAddress"; + public static final String + EXTENDED_CERTIFICATE_ATTRIBUTES_STR = "ExtendedCertificateAttributes"; + + public static final String + ISSUER_AND_SERIALNUMBER_STR = "IssuerAndSerialNumber"; + public static final String + PASSWORD_CHECK_STR = "PasswordCheck"; + public static final String + PUBLIC_KEY_STR = "PublicKey"; + public static final String + SIGNING_DESCRIPTION_STR = "SigningDescription"; + public static final String + EXTENSION_REQUEST_STR = "ExtensionRequest"; + + /** + * Hashtable mapping names and variant names of supported + * attributes to their OIDs. This table contains all name forms + * that occur in PKCS9, in lower case. + */ + private static final Hashtable NAME_OID_TABLE = new Hashtable(28); + + static { // static initializer for PCKS9_NAMES + NAME_OID_TABLE.put("emailaddress", PKCS9_OIDS[1]); + NAME_OID_TABLE.put("unstructuredname", PKCS9_OIDS[2]); + NAME_OID_TABLE.put("contenttype", PKCS9_OIDS[3]); + NAME_OID_TABLE.put("messagedigest", PKCS9_OIDS[4]); + NAME_OID_TABLE.put("signingtime", PKCS9_OIDS[5]); + NAME_OID_TABLE.put("countersignature", PKCS9_OIDS[6]); + NAME_OID_TABLE.put("challengepassword", PKCS9_OIDS[7]); + NAME_OID_TABLE.put("unstructuredaddress", PKCS9_OIDS[8]); + NAME_OID_TABLE.put("extendedcertificateattributes", PKCS9_OIDS[9]); + + NAME_OID_TABLE.put("issuerandserialNumber", PKCS9_OIDS[10]); + NAME_OID_TABLE.put("passwordcheck", PKCS9_OIDS[11]); + NAME_OID_TABLE.put("publickey", PKCS9_OIDS[12]); + NAME_OID_TABLE.put("signingdescription", PKCS9_OIDS[13]); + NAME_OID_TABLE.put("extensionrequest", PKCS9_OIDS[14]); + }; + + /** + * Hashtable mapping attribute OIDs defined in PKCS9 to the + * corresponding attribute value type. + */ + private static final Hashtable OID_NAME_TABLE = new Hashtable(14); + static { + OID_NAME_TABLE.put(PKCS9_OIDS[1], EMAIL_ADDRESS_STR); + OID_NAME_TABLE.put(PKCS9_OIDS[2], UNSTRUCTURED_NAME_STR); + OID_NAME_TABLE.put(PKCS9_OIDS[3], CONTENT_TYPE_STR); + OID_NAME_TABLE.put(PKCS9_OIDS[4], MESSAGE_DIGEST_STR); + OID_NAME_TABLE.put(PKCS9_OIDS[5], SIGNING_TIME_STR); + OID_NAME_TABLE.put(PKCS9_OIDS[6], COUNTERSIGNATURE_STR); + OID_NAME_TABLE.put(PKCS9_OIDS[7], CHALLENGE_PASSWORD_STR); + OID_NAME_TABLE.put(PKCS9_OIDS[8], UNSTRUCTURED_ADDRESS_STR); + OID_NAME_TABLE.put(PKCS9_OIDS[9], EXTENDED_CERTIFICATE_ATTRIBUTES_STR); + + OID_NAME_TABLE.put(PKCS9_OIDS[10], ISSUER_AND_SERIALNUMBER_STR); + OID_NAME_TABLE.put(PKCS9_OIDS[11], PASSWORD_CHECK_STR); + OID_NAME_TABLE.put(PKCS9_OIDS[12], PUBLIC_KEY_STR); + OID_NAME_TABLE.put(PKCS9_OIDS[13], SIGNING_DESCRIPTION_STR); + OID_NAME_TABLE.put(PKCS9_OIDS[14], EXTENSION_REQUEST_STR); + } + + /** + * Acceptable ASN.1 tags for DER encodings of values of PKCS9 + * attributes, by index in <code>PKCS9_OIDS</code>. + * Sets of acceptable tags are represented as arrays. + */ + private static final Byte[][] PKCS9_VALUE_TAGS = { + null, + {Byte.valueOf(DerValue.tag_IA5String)}, // EMailAddress + {Byte.valueOf(DerValue.tag_IA5String)}, // UnstructuredName + {Byte.valueOf(DerValue.tag_ObjectId)}, // ContentType + {Byte.valueOf(DerValue.tag_OctetString)}, // MessageDigest + {Byte.valueOf(DerValue.tag_UtcTime)}, // SigningTime + {Byte.valueOf(DerValue.tag_Sequence)}, // Countersignature + {Byte.valueOf(DerValue.tag_PrintableString), + Byte.valueOf(DerValue.tag_T61String)}, // ChallengePassword + {Byte.valueOf(DerValue.tag_PrintableString), + Byte.valueOf(DerValue.tag_T61String)}, // UnstructuredAddress + {Byte.valueOf(DerValue.tag_SetOf)}, // ExtendedCertificateAttributes + + null, //IssuerAndSerialNumber + null, //PasswordCheck + null, //PublicKey + null, //SigningDescription + {Byte.valueOf(DerValue.tag_Sequence)} //ExtensionRequest + }; + + /** + * Class types required for values for a given PKCS9 + * attribute type. + * + * <P> The following table shows the correspondence between + * attribute types and value component classes. + * + * <P> + * <TABLE BORDER CELLPADDING=8 ALIGN=CENTER> + * + * <TR> + * <TH>OID</TH> + * <TH>Attribute Type Name</TH> + * <TH>Kind</TH> + * <TH>Value Class</TH> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.1</TD> + * <TD>EmailAddress</TD> + * <TD>Multiple-valued</TD> + * <TD><code>String[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.2</TD> + * <TD>UnstructuredName</TD> + * <TD>Multiple-valued</TD> + * <TD><code>String</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.3</TD> + * <TD>ContentType</TD> + * <TD>Single-valued</TD> + * <TD><code>ObjectIdentifier</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.4</TD> + * <TD>MessageDigest</TD> + * <TD>Single-valued</TD> + * <TD><code>byte[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.5</TD> + * <TD>SigningTime</TD> + * <TD>Single-valued</TD> + * <TD><code>Date</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.6</TD> + * <TD>Countersignature</TD> + * <TD>Multiple-valued</TD> + * <TD><code>SignerInfo</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.7</TD> + * <TD>ChallengePassword</TD> + * <TD>Single-valued</TD> + * <TD><code>String</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.8</TD> + * <TD>UnstructuredAddress</TD> + * <TD>Single-valued</TD> + * <TD><code>String</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.9</TD> + * <TD>ExtendedCertificateAttributes</TD> + * <TD>Multiple-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.10</TD> + * <TD>IssuerAndSerialNumber</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.11</TD> + * <TD>PasswordCheck</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.12</TD> + * <TD>PublicKey</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.13</TD> + * <TD>SigningDescription</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.14</TD> + * <TD>ExtensionRequest</TD> + * <TD>Single-valued</TD> + * <TD><code>Sequence</code></TD> + * </TR> + * + * </TABLE> + */ + private static final Class[] VALUE_CLASSES = new Class[15]; + + static { + try { + Class str = Class.forName("[Ljava.lang.String;"); + + VALUE_CLASSES[0] = null; // not used + VALUE_CLASSES[1] = str; // EMailAddress + VALUE_CLASSES[2] = str; // UnstructuredName + VALUE_CLASSES[3] = // ContentType + Class.forName("netscape.security.util.ObjectIdentifier"); + VALUE_CLASSES[4] = Class.forName("[B"); // MessageDigest (byte[]) + VALUE_CLASSES[5] = Class.forName("java.util.Date"); // SigningTime + VALUE_CLASSES[6] = // Countersignature + Class.forName("[Lnetscape.security.pkcs.SignerInfo;"); + VALUE_CLASSES[7] = // ChallengePassword + Class.forName("java.lang.String"); + VALUE_CLASSES[8] = str; // UnstructuredAddress + VALUE_CLASSES[9] = null; // ExtendedCertificateAttributes + + VALUE_CLASSES[10] = null; // IssuerAndSerialNumber + VALUE_CLASSES[11] = null; // PasswordCheck + VALUE_CLASSES[12] = null; // PublicKey + VALUE_CLASSES[13] = null; // SigningDescription + VALUE_CLASSES[14] = // ExtensionRequest + Class.forName("netscape.security.x509.CertificateExtensions"); //xxxx + } catch (ClassNotFoundException e) { + throw new ExceptionInInitializerError(e.toString()); + } + } + + /** + * Array indicating which PKCS9 attributes are single-valued, + * by index in <code>PKCS9_OIDS</code>. + */ + private static final boolean[] SINGLE_VALUED = + { false, + false, // EMailAddress + false, // UnstructuredName + true, // ContentType + true, // MessageDigest + true, // SigningTime + false, // Countersignature + true, // ChallengePassword + false, // UnstructuredAddress + false, // ExtendedCertificateAttributes + + true, // IssuerAndSerialNumber + true, // PasswordCheck + true, // PublicKey + true, // SigningDescription + true // ExtensionRequest + }; + + /** + * The OID of this attribute is <code>PKCS9_OIDS[index]</code>. + */ + private int index; + + /** + * Value set of this attribute. Its class is given by + * <code>VALUE_CLASSES[index]</code>. + */ + private Object value; + + /** + * Construct an attribute object from the attribute's OID and + * value. If the attribute is single-valued, provide only one + * value. If the attribute is + * multiple-valued, provide an array containing all the values. + * Arrays of length zero are accepted, though probably useless. + * + * <P> The following table gives the class that <code>value</code> + * must have for a given attribute. + * + * <P> + * <TABLE BORDER CELLPADDING=8 ALIGN=CENTER> + * + * <TR> + * <TH>OID</TH> + * <TH>Attribute Type Name</TH> + * <TH>Kind</TH> + * <TH>Value Class</TH> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.1</TD> + * <TD>EmailAddress</TD> + * <TD>Multiple-valued</TD> + * <TD><code>String[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.2</TD> + * <TD>UnstructuredName</TD> + * <TD>Multiple-valued</TD> + * <TD><code>String[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.3</TD> + * <TD>ContentType</TD> + * <TD>Single-valued</TD> + * <TD><code>ObjectIdentifier</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.4</TD> + * <TD>MessageDigest</TD> + * <TD>Single-valued</TD> + * <TD><code>byte[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.5</TD> + * <TD>SigningTime</TD> + * <TD>Single-valued</TD> + * <TD><code>Date</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.6</TD> + * <TD>Countersignature</TD> + * <TD>Multiple-valued</TD> + * <TD><code>SignerInfo[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.7</TD> + * <TD>ChallengePassword</TD> + * <TD>Single-valued</TD> + * <TD><code>String</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.8</TD> + * <TD>UnstructuredAddress</TD> + * <TD>Single-valued</TD> + * <TD><code>String[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.9</TD> + * <TD>ExtendedCertificateAttributes</TD> + * <TD>Multiple-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.10</TD> + * <TD>IssuerAndSerialNumber</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.11</TD> + * <TD>PasswordCheck</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.12</TD> + * <TD>PublicKey</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.13</TD> + * <TD>SigningDescription</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.14</TD> + * <TD>ExtensionRequest</TD> + * <TD>Single-valued</TD> + * <TD><code>Sequence</code></TD> + * </TR> + * + * </TABLE> + */ + public PKCS9Attribute(ObjectIdentifier oid, Object value) + throws IllegalArgumentException { + + init(oid, value); + } + + /** + * Construct an attribute object from the attribute's name and + * value. If the attribute is single-valued, provide only one + * value. If the attribute is + * multiple-valued, provide an array containing all the values. + * Arrays of length zero are accepted, though probably useless. + * + * <P> The following table gives the class that <code>value</code> + * must have for a given attribute. Reasonable variants of these + * attributes are accepted; in particular, case does not matter. + * + * <P> + * <TABLE BORDER CELLPADDING=8 ALIGN=CENTER> + * + * <TR> + * <TH>OID</TH> + * <TH>Attribute Type Name</TH> + * <TH>Kind</TH> + * <TH>Value Class</TH> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.1</TD> + * <TD>EmailAddress</TD> + * <TD>Multiple-valued</TD> + * <TD><code>String[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.2</TD> + * <TD>UnstructuredName</TD> + * <TD>Multiple-valued</TD> + * <TD><code>String[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.3</TD> + * <TD>ContentType</TD> + * <TD>Single-valued</TD> + * <TD><code>ObjectIdentifier</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.4</TD> + * <TD>MessageDigest</TD> + * <TD>Single-valued</TD> + * <TD><code>byte[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.5</TD> + * <TD>SigningTime</TD> + * <TD>Single-valued</TD> + * <TD><code>Date</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.6</TD> + * <TD>Countersignature</TD> + * <TD>Multiple-valued</TD> + * <TD><code>SignerInfo[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.7</TD> + * <TD>ChallengePassword</TD> + * <TD>Single-valued</TD> + * <TD><code>String</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.8</TD> + * <TD>UnstructuredAddress</TD> + * <TD>Single-valued</TD> + * <TD><code>String[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.9</TD> + * <TD>ExtendedCertificateAttributes</TD> + * <TD>Multiple-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.10</TD> + * <TD>IssuerAndSerialNumber</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.11</TD> + * <TD>PasswordCheck</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.12</TD> + * <TD>PublicKey</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.13</TD> + * <TD>SigningDescription</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.14</TD> + * <TD>ExtensionRequest</TD> + * <TD>Single-valued</TD> + * <TD><code>Sequence</code></TD> + * </TR> + * + * </TABLE> + * + * @exception IllegalArgumentException + * if the <code>name</code> is not recognized of the + * <code>value</code> has the wrong type. + */ + public PKCS9Attribute(String name, Object value) + throws IllegalArgumentException { + ObjectIdentifier oid = getOID(name); + + if (oid == null) + throw new IllegalArgumentException( + "Unrecognized attribute name " + name + + " constructing PKCS9Attribute."); + + init(oid,value); + } + + private void init(ObjectIdentifier oid, Object value) + throws IllegalArgumentException { + + index = indexOf(oid,PKCS9_OIDS,1); + + if (index == -1) + throw new IllegalArgumentException( + "Unsupported OID " + oid + + " constructing PKCS9Attribute."); + + if (!VALUE_CLASSES[index].isInstance(value)) + throw new IllegalArgumentException( + "Wrong value class " + + " for attribute " + oid + + " constructing PKCS9Attribute; was " + + value.getClass().toString() + ", should be " + + VALUE_CLASSES[index].toString()); + + this.value = value; + } + + + /** + * Construct a PKCS9Attribute from its encoding on an input + * stream. + * + * @exception IOException on parsing error. + */ + public PKCS9Attribute(DerValue derVal) throws IOException { + + decode(derVal); + } + + /** + * Decode a PKCS9 attribute. + * + * @param val + * the DerValue representing the DER encoding of the attribute. + */ + private void decode(DerValue derVal) throws IOException { + DerInputStream derIn = new DerInputStream(derVal.toByteArray()); + DerValue[] val = derIn.getSequence(2); + + if (derIn.available() != 0) + throw new IOException("Excess data parsing PKCS9Attribute"); + + if (val.length != 2) + throw new IOException("PKCS9Attribute doesn't have two components"); + + DerValue[] elems; + + // get the oid + ObjectIdentifier oid = val[0].getOID(); + + index = indexOf(oid,PKCS9_OIDS,1); + Byte tag; + + if (index == -1) + throw new IOException("Invalid OID for PKCS9 attribute: " + + oid); + + elems = new DerInputStream(val[1].toByteArray()).getSet(1); + + // check single valued have only one value + if (SINGLE_VALUED[index] && elems.length > 1) + throwSingleValuedException(); + + // check for illegal element tags + for (int i=0; i < elems.length; i++) { + tag = Byte.valueOf(elems[i].tag); + + if (indexOf(tag, PKCS9_VALUE_TAGS[index], 0) == -1) + throwTagException(tag); + } + + switch (index) { + case 1: // email address + case 2: // unstructured name + case 8: // unstructured address + { // open scope + String[] values = new String[elems.length]; + + for (int i=0; i < elems.length; i++) + values[i] = elems[i].getAsString(); + value = values; + } // close scope + break; + + case 3: // content type + value = elems[0].getOID(); + break; + + case 4: // message digest + value = elems[0].getOctetString(); + break; + + case 5: // signing time + value = (new DerInputStream(elems[0].toByteArray())).getUTCTime(); + break; + + case 6: // countersignature + { // open scope + SignerInfo[] values = new SignerInfo[elems.length]; + for (int i=0; i < elems.length; i++) + values[i] = + new SignerInfo(elems[i].toDerInputStream()); + value = values; + } // close scope + break; + + case 7: // challenge password + value = elems[0].getAsString(); + break; + + case 9: // extended-certificate attribute -- not + // supported + throw new IOException("PKCS9 extended-certificate " + + "attribute not supported."); + + case 10: // IssuerAndSerialNumber attribute -- not + // supported + throw new IOException("PKCS9 IssuerAndSerialNumber " + + "attribute not supported."); + + case 11: // passwordCheck attribute -- not + // supported + throw new IOException("PKCS9 passwordCheck " + + "attribute not supported."); + case 12: // PublicKey attribute -- not + // supported + throw new IOException("PKCS9 PublicKey " + + "attribute not supported."); + case 13: // SigningDescription attribute -- not + // supported + throw new IOException("PKCS9 SigningDescription " + + "attribute not supported."); + case 14: // ExtensionRequest attribute + value = + new CertificateExtensions(elems[0].toDerInputStream()); + + // break unnecessary + + default: // can't happen + } + + } + + /** + * Write the DER encoding of this attribute to an output stream. + * + * <P> N.B.: This method always encodes values of + * ChallengePassword and UnstructuredAddress attributes as ASN.1 + * <code>PrintableString</code>s, without checking whether they + * should be encoded as <code>T61String</code>s. + */ + public void derEncode(OutputStream out) throws IOException { + DerOutputStream temp = new DerOutputStream(); + temp.putOID(getOID()); + switch (index) { + case 1: // email address + case 2: // unstructured name + { // open scope + String[] values = (String[]) value; + DerOutputStream[] temps = new + DerOutputStream[values.length]; + + for (int i=0; i < values.length; i++) { + temps[i] = new DerOutputStream(); + + temps[i].putIA5String( values[i]); + } + temp.putOrderedSetOf(DerValue.tag_Set, temps); + } // close scope + break; + + case 3: // content type + { + DerOutputStream temp2 = new DerOutputStream(); + temp2.putOID((ObjectIdentifier) value); + temp.write(DerValue.tag_Set, temp2.toByteArray()); + } + break; + + case 4: // message digest + { + DerOutputStream temp2 = new DerOutputStream(); + temp2.putOctetString((byte[]) value); + temp.write(DerValue.tag_Set, temp2.toByteArray()); + } + break; + + case 5: // signing time + { + DerOutputStream temp2 = new DerOutputStream(); + temp2.putUTCTime((Date) value); + temp.write(DerValue.tag_Set, temp2.toByteArray()); + } + break; + + case 6: // countersignature + temp.putOrderedSetOf(DerValue.tag_Set, (DerEncoder[]) value); + break; + + case 7: // challenge password + { + DerOutputStream temp2 = new DerOutputStream(); + temp2.putPrintableString((String) value); + temp.write(DerValue.tag_Set, temp2.toByteArray()); + } + break; + + case 8: // unstructured address + { // open scope + String[] values = (String[]) value; + DerOutputStream[] temps = new + DerOutputStream[values.length]; + + for (int i=0; i < values.length; i++) { + temps[i] = new DerOutputStream(); + + temps[i].putPrintableString(values[i]); + } + temp.putOrderedSetOf(DerValue.tag_Set, temps); + } // close scope + break; + + case 9: // extended-certificate attribute -- not + // supported + throw new IOException("PKCS9 extended-certificate " + + "attribute not supported."); + + case 10: // IssuerAndSerialNumber attribute -- not + // supported + throw new IOException("PKCS9 IssuerAndSerialNumber " + + "attribute not supported."); + + case 11: // passwordCheck attribute -- not + // supported + throw new IOException("PKCS9 passwordCheck " + + "attribute not supported."); + case 12: // PublicKey attribute -- not + // supported + throw new IOException("PKCS9 PublicKey " + + "attribute not supported."); + case 13: // SigningDescription attribute -- not + // supported + throw new IOException("PKCS9 SigningDescription " + + "attribute not supported."); + case 14: // ExtensionRequest attribute + try { + DerOutputStream temp2 = new DerOutputStream(); + //temp2.putSequence((CertificateExtensions) value); + ((CertificateExtensions)value).encode(temp2); + temp.write(DerValue.tag_Sequence, temp2.toByteArray()); + } catch (CertificateException e) { + throw new IOException("PKCS9 extension attributes not encoded"); + } + + // break unnecessary + default: // can't happen + } + + DerOutputStream derOut = new DerOutputStream(); + derOut.write(DerValue.tag_Sequence, temp.toByteArray()); + + out.write(derOut.toByteArray()); + + } + + /** + * Get the value of this attribute. If the attribute is + * single-valued, return just the one value. If the attribute is + * multiple-valued, return an array containing all the values. + * It is possible for this array to be of length 0. + * + * <P> The following table gives the class of the value returned, + * depending on the type of this attribute. + + * <P> + * <TABLE BORDER CELLPADDING=8 ALIGN=CENTER> + * + * <TR> + * <TH>OID</TH> + * <TH>Attribute Type Name</TH> + * <TH>Kind</TH> + * <TH>Value Class</TH> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.1</TD> + * <TD>EmailAddress</TD> + * <TD>Multiple-valued</TD> + * <TD><code>String[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.2</TD> + * <TD>UnstructuredName</TD> + * <TD>Multiple-valued</TD> + * <TD><code>String[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.3</TD> + * <TD>ContentType</TD> + * <TD>Single-valued</TD> + * <TD><code>ObjectIdentifier</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.4</TD> + * <TD>MessageDigest</TD> + * <TD>Single-valued</TD> + * <TD><code>byte[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.5</TD> + * <TD>SigningTime</TD> + * <TD>Single-valued</TD> + * <TD><code>Date</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.6</TD> + * <TD>Countersignature</TD> + * <TD>Multiple-valued</TD> + * <TD><code>SignerInfo[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.7</TD> + * <TD>ChallengePassword</TD> + * <TD>Single-valued</TD> + * <TD><code>String</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.8</TD> + * <TD>UnstructuredAddress</TD> + * <TD>Single-valued</TD> + * <TD><code>String[]</code></TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.9</TD> + * <TD>ExtendedCertificateAttributes</TD> + * <TD>Multiple-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.10</TD> + * <TD>IssuerAndSerialNumber</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.11</TD> + * <TD>PasswordCheck</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.12</TD> + * <TD>PublicKey</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.13</TD> + * <TD>SigningDescription</TD> + * <TD>Single-valued</TD> + * <TD>(not supported)</TD> + * </TR> + * + * <TR> + * <TD>1.2.840.113549.1.9.14</TD> + * <TD>ExtensionRequest</TD> + * <TD>Single-valued</TD> + * <TD><code>Sequence</code></TD> + * </TR> + * + * </TABLE> + * + */ + public Object getValue() { + return value; + } + + /** + * Show whether this attribute is single-valued. + */ + public boolean isSingleValued() { + return SINGLE_VALUED[index]; + } + + /** + * Return the OID of this attribute. + */ + public ObjectIdentifier getOID() { + return PKCS9_OIDS[index]; + } + + /** + * Return the name of this attribute. + */ + public String getName() { + return (String) OID_NAME_TABLE.get(PKCS9_OIDS[index]); + } + + /** + * Return the OID for a given attribute name or null if we don't recognize + * the name. + */ + public static ObjectIdentifier getOID(String name) { + return (ObjectIdentifier) NAME_OID_TABLE.get(name.toLowerCase()); + } + + /** + * Return the attribute name for a given OID or null if we don't recognize + * the oid. + */ + public static String getName(ObjectIdentifier oid) { + return (String) OID_NAME_TABLE.get(oid); + } + + /** + * Returns a string representation of this attribute. + */ + public String toString() { + StringBuffer buf = new StringBuffer(100); + + buf.append("["); + + buf.append(OID_NAME_TABLE.get(PKCS9_OIDS[index])); + buf.append(": "); + + if (SINGLE_VALUED[index]) { + if (value instanceof byte[]) { // special case for octet string + netscape.security.util.PrettyPrintFormat pp = + new netscape.security.util.PrettyPrintFormat(" ", 20); + String valuebits = pp.toHexString(((byte[])value)); + buf.append(valuebits); + } else { + buf.append(value.toString()); + } + buf.append("]"); + return buf.toString(); + } else { // multiple-valued + boolean first = true; + Object[] values = (Object[]) value; + + for (int j=0; j < values.length; j++) { + if (first) + first = false; + else + buf.append(", "); + + buf.append(values[j].toString()); + } + return buf.toString(); + } + } + + /** + * Beginning the search at <code>start</code>, find the first + * index <code>i</code> such that <code>a[i] = obj</code>. + * + * @return the index, if found, and -1 otherwise. + */ + static int indexOf(Object obj, Object[] a, int start) { + for (int i=start; i < a.length; i++) { + if (obj.equals(a[i])) return i; + } + return -1; + } + + /** + * Throw an exception when there are multiple values for + * a single-valued attribute. + */ + private void throwSingleValuedException() throws IOException { + throw new IOException("Single-value attribute " + + getOID() + " (" + getName() + ")" + + " has multiple values."); + } + + /** + * Throw an exception when the tag on a value encoding is + * wrong for the attribute whose value it is. + */ + private void throwTagException(Byte tag) + throws IOException { + Byte[] expectedTags = PKCS9_VALUE_TAGS[index]; + StringBuffer msg = new StringBuffer(100); + msg.append("Value of attribute "); + msg.append(getOID().toString()); + msg.append(" ("); + msg.append(getName()); + msg.append(") has wrong tag: "); + msg.append(tag.toString()); + msg.append(". Expected tags: "); + + msg.append(expectedTags[0].toString()); + + for (int i = 1; i < expectedTags.length; i++) { + msg.append(", "); + msg.append(expectedTags[i].toString()); + } + msg.append("."); + throw new IOException(msg.toString()); + } + +} diff --git a/pki/base/util/src/netscape/security/pkcs/PKCS9Attributes.java b/pki/base/util/src/netscape/security/pkcs/PKCS9Attributes.java new file mode 100644 index 000000000..c979e489c --- /dev/null +++ b/pki/base/util/src/netscape/security/pkcs/PKCS9Attributes.java @@ -0,0 +1,319 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package netscape.security.pkcs; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Hashtable; +import netscape.security.util.DerEncoder; +import netscape.security.util.DerValue; +import netscape.security.util.DerInputStream; +import netscape.security.util.DerOutputStream; +import netscape.security.util.ObjectIdentifier; + + +/** + * A set of attributes of class PKCS9Attribute. + * + * @version 1.2 97/12/10 + * @author Douglas Hoover + */ +public class PKCS9Attributes { + + /** + * Attributes in this set indexed by OID. + */ + private final Hashtable attributes = new Hashtable(3); + + /** + * The keys of this hashtable are the OIDs of permitted attributes. + */ + private final Hashtable permittedAttributes; + + /** + * The DER encoding of this attribute set. The tag byte must be + * DerValue.tag_SetOf. + */ + private final byte[] derEncoding; + + /** + * Construct a set of PKCS9 Attributes from its + * DER encoding on a DerInputStream, accepting only attributes + * with OIDs on the given + * list. If the array is null, accept all attributes supported by + * class PKCS9Attribute. + * + * @param permittedAttributes + * Array of attribute OIDs that will be accepted. + * @param buf + * the contents of the DER encoding of the attribute set. + * + * @exception IOException + * on i/o error, encoding syntax error, unacceptable or + * unsupported attribute, or duplicate attribute. + * + * @see PKCS9Attribute + */ + public PKCS9Attributes(ObjectIdentifier[] permittedAttributes, + DerInputStream in) throws IOException { + if (permittedAttributes != null) { + this.permittedAttributes = + new Hashtable(permittedAttributes.length); + + for (int i = 0; i < permittedAttributes.length; i++) + this.permittedAttributes.put(permittedAttributes[i], + permittedAttributes[i]); + } else { + this.permittedAttributes = null; + } + + // derEncoding initialized in <code>decode()</code> + derEncoding = decode(in); + } + + /** + * Construct a set of PKCS9 Attributes from its contents of its + * DER encoding on a DerInputStream. Accept all attributes + * supported by class PKCS9Attribute. + * + * @exception IOException + * on i/o error, encoding syntax error, or unsupported or + * duplicate attribute. + * + * @see PKCS9Attribute + */ + public PKCS9Attributes(DerInputStream in) throws IOException { + // anything goes + // derEncoding initialized in <code>decode()</code> + derEncoding = decode(in); + permittedAttributes = null; + } + + /** + * Construct a set of PKCS9 Attributes from the given array of + * PCK9 attributes. + * DER encoding on a DerInputStream. All attributes in + * <code>attribs</code> must be + * supported by class PKCS9Attribute. + * + * @exception IOException + * on i/o error, encoding syntax error, or unsupported or + * duplicate attribute. + * + * @see PKCS9Attribute + */ + public PKCS9Attributes(PKCS9Attribute[] attribs) + throws IllegalArgumentException, IOException { + ObjectIdentifier oid; + for (int i=0; i < attribs.length; i++) { + oid = attribs[i].getOID(); + if (attributes.containsKey(oid)) + throw new IllegalArgumentException( + "PKCSAttribute " + attribs[i].getOID() + + " duplicated while constructing " + + "PKCS9Attributes."); + + attributes.put(oid, attribs[i]); + } + derEncoding = generateDerEncoding(); + permittedAttributes = null; + } + + + /** + * Decode this set of PKCS9 attribute set from the contents of its + * DER encoding. + * + * @param buf + * the contents of the DER encoding of the attribute set. + * + * @exception IOException + * on i/o error, encoding syntax error, unacceptable or + * unsupported attribute, or duplicate attribute. + */ + private byte[] decode(DerInputStream in) throws IOException { + + DerValue val = in.getDerValue(); + + // save the DER encoding with its proper tag byte. + byte[] derEncoding = val.toByteArray(); + derEncoding[0] = DerValue.tag_SetOf; + + DerInputStream derIn = new DerInputStream(derEncoding); + DerValue[] derVals = derIn.getSet(3,true); + + PKCS9Attribute attrib; + ObjectIdentifier oid; + int index; + + for (int i=0; i < derVals.length; i++) { + attrib = new PKCS9Attribute(derVals[i]); + oid = attrib.getOID(); + + if (attributes.get(oid) != null) + throw new IOException("Duplicate PKCS9 attribute: " + oid); + + if (permittedAttributes != null && + !permittedAttributes.containsKey(oid)) + throw new IOException("Attribute " + oid + + " not permitted in this attribute set"); + + attributes.put(oid,attrib); + } + return derEncoding; + + } + + /** + * Put the DER encoding of this PKCS9 attribute set on an + * DerOutputStream, tagged with the given implicit tag. + * + * @param tag the implicit tag to use in the DER encoding. + * @param out the output stream on which to put the DER encoding. + * + * @exception IOException on output error. + */ + public void encode(byte tag, OutputStream out) throws IOException { + out.write(tag); + out.write(derEncoding, 1, derEncoding.length -1); + } + + private byte[] generateDerEncoding() throws IOException { + DerOutputStream out = new DerOutputStream(); + Object[] attribVals = attributes.values().toArray(); + + out.putOrderedSetOf(DerValue.tag_SetOf, + castToDerEncoder(attribVals)); + return out.toByteArray(); + } + + /** + * Return the DER encoding of this attribute set, tagged with + * DerValue.tag_SetOf. + */ + public byte[] getDerEncoding() throws IOException { + return (byte[]) derEncoding.clone(); + + } + + /** + * Get an attribute from this set. + */ + public PKCS9Attribute getAttribute(ObjectIdentifier oid) { + return (PKCS9Attribute) attributes.get(oid); + } + + /** + * Get an attribute from this set. + */ + public PKCS9Attribute getAttribute(String name) { + return (PKCS9Attribute) attributes.get(PKCS9Attribute.getOID(name)); + } + + + /** + * Get an array of all attributes in this set, in order of OID. + */ + public PKCS9Attribute[] getAttributes() { + PKCS9Attribute[] attribs = new PKCS9Attribute[attributes.size()]; + ObjectIdentifier oid; + + int j = 0; + for (int i=1; i < PKCS9Attribute.PKCS9_OIDS.length && + j < attribs.length; i++) { + attribs[j] = getAttribute(PKCS9Attribute.PKCS9_OIDS[i]); + + if (attribs[j] != null) + j++; + } + return attribs; + } + + /** + * Get an attribute value by OID. + */ + public Object getAttributeValue(ObjectIdentifier oid) + throws IOException { + try { + Object value = getAttribute(oid).getValue(); + return value; + } catch (NullPointerException ex) { + throw new IOException("No value found for attribute " + oid); + } + + } + + /** + * Get an attribute value by type name. + */ + public Object getAttributeValue(String name) throws IOException { + ObjectIdentifier oid = PKCS9Attribute.getOID(name); + + if (oid == null) + throw new IOException("Attribute name " + name + + " not recognized or not supported."); + + return getAttributeValue(oid); + } + + + /** + * Returns the PKCS9 block in a printable string form. + */ + public String toString() { + StringBuffer buf = new StringBuffer(200); + buf.append("PKCS9 Attributes: [\n\t"); + + ObjectIdentifier oid; + PKCS9Attribute value; + + boolean first = true; + for (int i = 1; i < PKCS9Attribute.PKCS9_OIDS.length; i++) { + value = getAttribute(PKCS9Attribute.PKCS9_OIDS[i]); + + if (value == null) continue; + + // we have a value; print it + if (first) + first = false; + else + buf.append(";\n\t"); + + buf.append(value.toString()); + } + + buf.append("\n\t] (end PKCS9 Attributes)"); + + return buf.toString(); + } + + /** + * Cast an object array whose components are + * <code>DerEncoder</code>s to <code>DerEncoder[]</code>. + */ + static DerEncoder[] castToDerEncoder(Object[] objs) { + + DerEncoder[] encoders = new DerEncoder[objs.length]; + + for (int i=0; i < encoders.length; i++) + encoders[i] = (DerEncoder) objs[i]; + + return encoders; + } + +} diff --git a/pki/base/util/src/netscape/security/pkcs/ParsingException.java b/pki/base/util/src/netscape/security/pkcs/ParsingException.java new file mode 100644 index 000000000..15bb01a5a --- /dev/null +++ b/pki/base/util/src/netscape/security/pkcs/ParsingException.java @@ -0,0 +1,30 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package netscape.security.pkcs; + +import java.io.IOException; + +public class ParsingException extends IOException { + public ParsingException() { + super(); + } + + public ParsingException(String s) { + super(s); + } +} diff --git a/pki/base/util/src/netscape/security/pkcs/SignerInfo.java b/pki/base/util/src/netscape/security/pkcs/SignerInfo.java new file mode 100644 index 000000000..e03c32190 --- /dev/null +++ b/pki/base/util/src/netscape/security/pkcs/SignerInfo.java @@ -0,0 +1,344 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package netscape.security.pkcs; + +import java.io.OutputStream; +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.security.*; + +import netscape.security.util.*; +import netscape.security.x509.AlgorithmId; +import netscape.security.x509.X500Name; + +/** + * A SignerInfo, as defined in PKCS#7's signedData type. + * + * @author Benjamin Renaud + * @version 1.27 97/12/10 + */ +public class SignerInfo implements DerEncoder { + + BigInt version; + X500Name issuerName; + BigInt certificateSerialNumber; + AlgorithmId digestAlgorithmId; + AlgorithmId digestEncryptionAlgorithmId; + byte[] encryptedDigest; + + PKCS9Attributes authenticatedAttributes; + PKCS9Attributes unauthenticatedAttributes; + + public SignerInfo(X500Name issuerName, + BigInt serial, + AlgorithmId digestAlgorithmId, + AlgorithmId digestEncryptionAlgorithmId, + byte[] encryptedDigest) { + this.version = new BigInt(1); + this.issuerName = issuerName; + this.certificateSerialNumber = serial; + this.digestAlgorithmId = digestAlgorithmId; + this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId; + this.encryptedDigest = encryptedDigest; + } + + public SignerInfo(X500Name issuerName, + BigInt serial, + AlgorithmId digestAlgorithmId, + PKCS9Attributes authenticatedAttributes, + AlgorithmId digestEncryptionAlgorithmId, + byte[] encryptedDigest, + PKCS9Attributes unauthenticatedAttributes) { + this.version = new BigInt(1); + this.issuerName = issuerName; + this.certificateSerialNumber = serial; + this.digestAlgorithmId = digestAlgorithmId; + this.authenticatedAttributes = authenticatedAttributes; + this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId; + this.encryptedDigest = encryptedDigest; + this.unauthenticatedAttributes = unauthenticatedAttributes; + } + + public SignerInfo(DerInputStream derin) + throws IOException, ParsingException { + + // version + version = derin.getInteger(); + + // issuerAndSerialNumber + DerValue[] issuerAndSerialNumber = derin.getSequence(2); + byte[] issuerBytes = issuerAndSerialNumber[0].toByteArray(); + issuerName = new X500Name(new DerValue(DerValue.tag_Sequence, + issuerBytes)); + certificateSerialNumber = issuerAndSerialNumber[1].getInteger(); + + // digestAlgorithmId + DerValue tmp = derin.getDerValue(); + + digestAlgorithmId = AlgorithmId.parse(tmp); + + /* + * check if set of auth attributes (implicit tag) is provided + * (auth attributes are OPTIONAL) + */ + if ((byte)(derin.peekByte()) == (byte)0xA0) { + authenticatedAttributes = new PKCS9Attributes(derin); + } + + // digestEncryptionAlgorithmId - little RSA naming scheme - + // signature == encryption... + tmp = derin.getDerValue(); + + digestEncryptionAlgorithmId = AlgorithmId.parse(tmp); + + // encryptedDigest + encryptedDigest = derin.getOctetString(); + + /* + * check if set of unauth attributes (implicit tag) is provided + * (unauth attributes are OPTIONAL) + */ + if (derin.available() != 0 && (byte)(derin.peekByte()) == (byte)0xA1) { + unauthenticatedAttributes = new PKCS9Attributes(derin); + } + + // all done + if (derin.available() != 0) { + throw new ParsingException("extra data at the end"); + } + } + + public void encode(DerOutputStream out) throws IOException { + + derEncode(out); + } + + /** + * DER encode this object onto an output stream. + * Implements the <code>DerEncoder</code> interface. + * + * @param out + * the output stream on which to write the DER encoding. + * + * @exception IOException on encoding error. + */ + public void derEncode(OutputStream out) throws IOException { + DerOutputStream seq = new DerOutputStream(); + seq.putInteger(version); + DerOutputStream issuerAndSerialNumber = new DerOutputStream(); + issuerName.encode(issuerAndSerialNumber); + issuerAndSerialNumber.putInteger(certificateSerialNumber); + seq.write(DerValue.tag_Sequence, issuerAndSerialNumber); + + digestAlgorithmId.encode(seq); + + // encode authenticated attributes if there are any + if (authenticatedAttributes != null) + authenticatedAttributes.encode((byte)0xA0, seq); + + digestEncryptionAlgorithmId.encode(seq); + + seq.putOctetString(encryptedDigest); + + // encode unauthenticated attributes if there are any + if (unauthenticatedAttributes != null) + unauthenticatedAttributes.encode((byte)0xA1, seq); + + DerOutputStream tmp = new DerOutputStream(); + tmp.write(DerValue.tag_Sequence, seq); + + out.write(tmp.toByteArray()); + } + + + + public X509Certificate getCertificate(PKCS7 block) + throws IOException { + return block.getCertificate(certificateSerialNumber, issuerName); + } + + /* Returns null if verify fails, this signerInfo if + verify succeeds. */ + SignerInfo verify(PKCS7 block, byte[] data) + throws NoSuchAlgorithmException, SignatureException { + + try { + + ContentInfo content = block.getContentInfo(); + if (data == null) { + data = content.getContentBytes(); + } + + String digestAlgname = + getDigestAlgorithmId().getName(); + + byte[] dataSigned; + + // if there are authenticate attributes, get the message + // digest and compare it with the digest of data + if (authenticatedAttributes == null) { + dataSigned = data; + } else { + + // first, check content type + ObjectIdentifier contentType = (ObjectIdentifier) + authenticatedAttributes.getAttributeValue( + PKCS9Attribute.CONTENT_TYPE_OID); + if (contentType == null || + !contentType.equals(content.contentType)) + return null; // contentType does not match, bad SignerInfo + + // now, check message digest + byte[] messageDigest = (byte[]) + authenticatedAttributes.getAttributeValue( + PKCS9Attribute.MESSAGE_DIGEST_OID); + + if (messageDigest == null) // fail if there is no message digest + return null; + + MessageDigest md = MessageDigest.getInstance(digestAlgname); + byte[] computedMessageDigest = md.digest(data); + + if (messageDigest.length != computedMessageDigest.length) + return null; + for (int i = 0; i < messageDigest.length; i++) { + if (messageDigest[i] != computedMessageDigest[i]) + return null; + } + + // message digest attribute matched + // digest of original data + + // the data actually signed is the DER encoding of + // the authenticated attributes (tagged with + // the "SET OF" tag, not 0xA0). + dataSigned = authenticatedAttributes.getDerEncoding(); + } + + // put together digest algorithm and encryption algorithm + // to form signing algorithm + String encryptionAlgname = + getDigestEncryptionAlgorithmId().getName(); + + String algname; + if (encryptionAlgname.equals("DSA") || + encryptionAlgname.equals("SHA1withDSA")) { + algname = "DSA"; + } else { + algname = digestAlgname + "/" + encryptionAlgname; + } + + Signature sig = Signature.getInstance(algname); + X509Certificate cert = getCertificate(block); + + if (cert == null) { + return null; + } + + PublicKey key = cert.getPublicKey(); + sig.initVerify(key); + + sig.update(dataSigned); + + if (sig.verify(encryptedDigest)) { + return this; + } + + } catch (IOException e) { + throw new SignatureException("IO error verifying signature:\n" + + e.getMessage()); + + } catch (InvalidKeyException e) { + throw new SignatureException("InvalidKey: " + e.getMessage()); + + } + return null; + } + + /* Verify the content of the pkcs7 block. */ + SignerInfo verify(PKCS7 block) + throws NoSuchAlgorithmException, SignatureException { + return verify(block, null); + } + + + public BigInt getVersion() { + return version; + } + + public X500Name getIssuerName() { + return issuerName; + } + + public BigInt getCertificateSerialNumber() { + return certificateSerialNumber; + } + + public AlgorithmId getDigestAlgorithmId() { + return digestAlgorithmId; + } + + public PKCS9Attributes getAuthenticatedAttributes() { + return authenticatedAttributes; + } + + public AlgorithmId getDigestEncryptionAlgorithmId() { + return digestEncryptionAlgorithmId; + } + + public byte[] getEncryptedDigest() { + return encryptedDigest; + } + + public PKCS9Attributes getUnauthenticatedAttributes() { + return unauthenticatedAttributes; + } + + public String toString() { + netscape.security.util.PrettyPrintFormat pp = + new netscape.security.util.PrettyPrintFormat(" ", 20); + String digestbits = pp.toHexString(encryptedDigest); + + String out = ""; + + out += "Signer Info for (issuer): " + issuerName + "\n"; + out += "\tversion: " + version + "\n"; + out += "\tcertificateSerialNumber: " + certificateSerialNumber + + "\n"; + out += "\tdigestAlgorithmId: " + digestAlgorithmId + "\n"; + if (authenticatedAttributes != null) { + out += "\tauthenticatedAttributes: " + authenticatedAttributes + + "\n"; + } + out += "\tdigestEncryptionAlgorithmId: " + digestEncryptionAlgorithmId + + "\n"; + + out += "\tencryptedDigest: " + "\n" + + digestbits + "\n"; + if (unauthenticatedAttributes != null) { + out += "\tunauthenticatedAttributes: " + + unauthenticatedAttributes + "\n"; + } + return out; + } + +} + + + + |