Java tutorial
// --- 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.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.math.BigInteger; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Vector; import org.apache.commons.codec.binary.Base64; import com.netscape.cmsutil.util.Utils; import netscape.security.util.BigInt; import netscape.security.util.DerInputStream; import netscape.security.util.DerOutputStream; import netscape.security.util.DerValue; import netscape.security.util.ObjectIdentifier; import netscape.security.x509.AlgorithmId; import netscape.security.x509.X500Name; import netscape.security.x509.X509CertImpl; /** * 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 { public final static String HEADER = "-----BEGIN PKCS7-----"; public final static String FOOTER = "-----END 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); } /** * Unmarshals a PKCS7 block from PEM format. * * @param input the PKCS7 block in PEM format. * @exception ParsingException on parsing errors. */ public PKCS7(String input) throws ParsingException, IOException { StringBuilder sb = new StringBuilder(); try (StringReader sr = new StringReader(input.trim()); BufferedReader in = new BufferedReader(sr)) { String line; // skip everything up to header String header = null; while ((line = in.readLine()) != null) { if (HEADER.equals(line)) { header = line; break; } } if (header == null) { throw new ParsingException("Missing PKCS #7 header"); } // store everything up to footer String footer = null; while ((line = in.readLine()) != null) { if (FOOTER.equals(line)) { footer = line; break; } sb.append(line); } if (footer == null) { throw new ParsingException("Missing PKCS #7 footer"); } } byte[] bytes = Base64.decodeBase64(sb.toString()); parse(new DerInputStream(bytes)); } 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 = 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); // 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<SignerInfo> intResult = new Vector<SignerInfo>(); 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 = cert.getSerialNumber(); BigInt thisSerial = new BigInt(tmpSerial); if (serial.equals(thisSerial) && name.equals(thisName)) { return cert; } } return null; } public byte[] getBytes() throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); encodeSignedData(out); return out.toByteArray(); } public String toPEMString() throws IOException { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw, true); pw.println(HEADER); pw.print(Utils.base64encode(getBytes(), true)); pw.println(FOOTER); return sw.toString(); } /** * Returns the PKCS7 block in a printable string form. */ public String toString() { StringBuffer out = new StringBuffer("PKCS7 :: version: " + version + "\n"); out.append("PKCS7 :: digest AlgorithmIds: " + "\n"); for (int i = 0; i < digestAlgorithmIds.length; i++) { out.append("\t" + digestAlgorithmIds[i] + "\n"); } out.append(contentInfo + "\n"); out.append("PKCS7 :: certificates: " + "\n"); for (int i = 0; i < certificates.length; i++) { out.append("\t" + i + ". " + certificates[i] + "\n"); } out.append("PKCS7 :: signer infos: " + "\n"); for (int i = 0; i < signerInfos.length; i++) { out.append("\t" + i + ". " + signerInfos[i] + "\n"); } return out.toString(); } }