// --- 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.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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 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 SignedData ContentInfo
* type, where to the type of data signed is plain Data.
* For signedData, crls, attributes 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);
// 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;
}
}