using System;
using System.Collections.Generic;
using System.IO;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Cms;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Cms;
using Org.BouncyCastle.Operators.Utilities;
using Org.BouncyCastle.Utilities.Collections;
using Org.BouncyCastle.X509;
namespace Org.BouncyCastle.Cms
{
/**
* general class for handling a pkcs7-signature message.
*
* A simple example of usage - note, in the example below the validity of
* the certificate isn't verified, just the fact that one of the certs
* matches the given signer...
*
*
* IX509Store certs = s.GetCertificates();
* SignerInformationStore signers = s.GetSignerInfos();
*
* foreach (SignerInformation signer in signers.GetSigners())
* {
* ArrayList certList = new ArrayList(certs.GetMatches(signer.SignerID));
* X509Certificate cert = (X509Certificate) certList[0];
*
* if (signer.Verify(cert.GetPublicKey()))
* {
* verified++;
* }
* }
*
*/
public class CmsSignedData
{
private readonly CmsProcessable signedContent;
private SignedData signedData;
private ContentInfo contentInfo;
private SignerInformationStore signerInfoStore;
private IDictionary m_hashes;
private CmsSignedData(CmsSignedData c)
{
this.signedData = c.signedData;
this.contentInfo = c.contentInfo;
this.signedContent = c.signedContent;
this.signerInfoStore = c.signerInfoStore;
}
public CmsSignedData(byte[] sigBlock)
: this(CmsUtilities.ReadContentInfo(new MemoryStream(sigBlock, false)))
{
}
public CmsSignedData(CmsProcessable signedContent, byte[] sigBlock)
: this(signedContent, CmsUtilities.ReadContentInfo(new MemoryStream(sigBlock, false)))
{
}
/**
* Content with detached signature, digests precomputed
*
* @param hashes a map of precomputed digests for content indexed by name of hash.
* @param sigBlock the signature object.
*/
public CmsSignedData(IDictionary hashes, byte[] sigBlock)
: this(hashes, CmsUtilities.ReadContentInfo(sigBlock))
{
}
/**
* base constructor - content with detached signature.
*
* @param signedContent the content that was signed.
* @param sigData the signature object.
*/
public CmsSignedData(CmsProcessable signedContent, Stream sigData)
: this(signedContent, CmsUtilities.ReadContentInfo(sigData))
{
}
/**
* base constructor - with encapsulated content
*/
public CmsSignedData(Stream sigData)
: this(CmsUtilities.ReadContentInfo(sigData))
{
}
public CmsSignedData(CmsProcessable signedContent, ContentInfo sigData)
{
this.signedContent = signedContent;
this.contentInfo = sigData;
this.signedData = SignedData.GetInstance(contentInfo.Content);
}
public CmsSignedData(IDictionary hashes, ContentInfo sigData)
{
this.m_hashes = hashes;
this.contentInfo = sigData;
this.signedData = SignedData.GetInstance(contentInfo.Content);
}
public CmsSignedData(ContentInfo sigData)
{
this.contentInfo = sigData;
this.signedData = SignedData.GetInstance(contentInfo.Content);
//
// this can happen if the signed message is sent simply to send a
// certificate chain.
//
if (signedData.EncapContentInfo.Content != null)
{
if (signedData.EncapContentInfo.Content is Asn1OctetString)
{
signedContent = new CmsProcessableByteArray(
((Asn1OctetString)(signedData.EncapContentInfo.Content)).GetOctets());
}
else
{
signedContent = new Pkcs7ProcessableObject(signedData.EncapContentInfo.ContentType,
signedData.EncapContentInfo.Content);
}
}
// else
// {
// this.signedContent = null;
// }
}
/// Return the version number for this object.
public int Version
{
get { return signedData.Version.IntValueExact; }
}
/**
* return the collection of signers that are associated with the
* signatures for the message.
*/
public SignerInformationStore GetSignerInfos()
{
if (signerInfoStore == null)
{
var signerInfos = new List();
Asn1Set s = signedData.SignerInfos;
foreach (object obj in s)
{
SignerInfo info = SignerInfo.GetInstance(obj);
DerObjectIdentifier contentType = signedData.EncapContentInfo.ContentType;
if (m_hashes == null)
{
signerInfos.Add(new SignerInformation(info, contentType, signedContent, null));
}
else if (m_hashes.TryGetValue(info.DigestAlgorithm.Algorithm.Id, out var hash))
{
signerInfos.Add(new SignerInformation(info, contentType, null, hash));
}
else
{
throw new InvalidOperationException();
}
}
signerInfoStore = new SignerInformationStore(signerInfos);
}
return signerInfoStore;
}
/**
* return a X509Store containing the attribute certificates, if any, contained
* in this message.
*
* @param type type of store to create
* @return a store of attribute certificates
* @exception NoSuchStoreException if the store type isn't available.
* @exception CmsException if a general exception prevents creation of the X509Store
*/
public IStore GetAttributeCertificates()
{
return CmsSignedHelper.GetAttributeCertificates(signedData.Certificates);
}
/**
* return a X509Store containing the public key certificates, if any, contained in this message.
*
* @return a store of public key certificates
* @exception NoSuchStoreException if the store type isn't available.
* @exception CmsException if a general exception prevents creation of the X509Store
*/
public IStore GetCertificates()
{
return CmsSignedHelper.GetCertificates(signedData.Certificates);
}
/**
* return a X509Store containing CRLs, if any, contained in this message.
*
* @return a store of CRLs
* @exception NoSuchStoreException if the store type isn't available.
* @exception CmsException if a general exception prevents creation of the X509Store
*/
public IStore GetCrls()
{
return CmsSignedHelper.GetCrls(signedData.CRLs);
}
public IStore GetOtherRevInfos(DerObjectIdentifier otherRevInfoFormat)
{
return CmsSignedHelper.GetOtherRevInfos(signedData.CRLs, otherRevInfoFormat);
}
/**
* Return the digest algorithm identifiers for the SignedData object
*
* @return the set of digest algorithm identifiers
*/
public ISet GetDigestAlgorithmIDs()
{
var digestAlgorithms = signedData.DigestAlgorithms;
HashSet result = new HashSet();
foreach (var entry in digestAlgorithms)
{
result.Add(AlgorithmIdentifier.GetInstance(entry));
}
return CollectionUtilities.ReadOnly(result);
}
///
/// Return the DerObjectIdentifier associated with the encapsulated
/// content info structure carried in the signed data.
///
public DerObjectIdentifier SignedContentType
{
get { return signedData.EncapContentInfo.ContentType; }
}
public CmsProcessable SignedContent
{
get { return signedContent; }
}
/**
* return the ContentInfo
*/
public ContentInfo ContentInfo
{
get { return contentInfo; }
}
/**
* return the ASN.1 encoded representation of this object.
*/
public byte[] GetEncoded()
{
return contentInfo.GetEncoded();
}
/**
* return the ASN.1 encoded representation of this object using the specified encoding.
*
* @param encoding the ASN.1 encoding format to use ("BER" or "DER").
*/
public byte[] GetEncoded(string encoding)
{
return contentInfo.GetEncoded(encoding);
}
/**
* Return a new CMSSignedData which guarantees to have the passed in digestAlgorithm
* in it. Uses the DefaultDigestAlgorithmFinder for creating the digest sets.
*
* @param signedData the signed data object to be used as a base.
* @param digestAlgorithm the digest algorithm to be added to the signed data.
* @return a new signed data object.
*/
public static CmsSignedData AddDigestAlgorithm(CmsSignedData signedData, AlgorithmIdentifier digestAlgorithm) =>
AddDigestAlgorithm(signedData, digestAlgorithm, DefaultDigestAlgorithmFinder.Instance);
/**
* Return a new CMSSignedData which guarantees to have the passed in digestAlgorithm
* in it. Uses the passed in IDigestAlgorithmFinder for creating the digest sets.
*
* @param signedData the signed data object to be used as a base.
* @param digestAlgorithm the digest algorithm to be added to the signed data.
* @param digestAlgorithmFinder the digest algorithm finder to generate the digest set with.
* @return a new signed data object.
*/
public static CmsSignedData AddDigestAlgorithm(CmsSignedData signedData, AlgorithmIdentifier digestAlgorithm,
IDigestAlgorithmFinder digestAlgorithmFinder)
{
ISet digestAlgorithms = signedData.GetDigestAlgorithmIDs();
AlgorithmIdentifier digestAlg = CmsSignedHelper.FixDigestAlgID(digestAlgorithm, digestAlgorithmFinder);
//
// if the algorithm is already present there is no need to add it.
//
if (digestAlgorithms.Contains(digestAlg))
return signedData;
//
// copy
//
CmsSignedData cms = new CmsSignedData(signedData);
//
// build up the new set
//
HashSet digestAlgs = new HashSet();
foreach (var entry in digestAlgs)
{
digestAlgs.Add(CmsSignedHelper.FixDigestAlgID(entry, digestAlgorithmFinder));
}
digestAlgs.Add(digestAlg);
Asn1Set digests = CmsUtilities.ConvertToDLSet(digestAlgs);
Asn1Sequence sD = (Asn1Sequence)signedData.signedData.ToAsn1Object();
//
// signers are the last item in the sequence.
//
Asn1EncodableVector vec = new Asn1EncodableVector(sD.Count);
vec.Add(sD[0]); // version
vec.Add(digests);
for (int i = 2; i != sD.Count; i++)
{
vec.Add(sD[i]);
}
cms.signedData = SignedData.GetInstance(new BerSequence(vec));
//
// replace the contentInfo with the new one
//
cms.contentInfo = new ContentInfo(cms.contentInfo.ContentType, cms.signedData);
return cms;
}
/**
* Replace the SignerInformation store associated with this CMSSignedData object with the new one passed in
* using the DefaultDigestAlgorithmFinder for creating the digest sets. You would probably only want
* to do this if you wanted to change the unsigned attributes associated with a signer, or perhaps delete one.
*
* @param signedData the signed data object to be used as a base.
* @param signerInformationStore the new signer information store to use.
* @return a new signed data object.
*/
public static CmsSignedData ReplaceSigners(CmsSignedData signedData,
SignerInformationStore signerInformationStore) =>
ReplaceSigners(signedData, signerInformationStore, DefaultDigestAlgorithmFinder.Instance);
/**
* Replace the SignerInformation store associated with this CMSSignedData object with the new one passed in
* using the passed in IDigestAlgorithmFinder for creating the digest sets. You would probably only
* want to do this if you wanted to change the unsigned attributes associated with a signer, or perhaps delete
* one.
*
* @param signedData the signed data object to be used as a base.
* @param signerInformationStore the new signer information store to use.
* @param digestAlgorithmFinder the digest algorithm finder to generate the digest set with.
* @return a new signed data object.
*/
public static CmsSignedData ReplaceSigners(CmsSignedData signedData,
SignerInformationStore signerInformationStore, IDigestAlgorithmFinder digestAlgorithmFinder)
{
//
// copy
//
CmsSignedData cms = new CmsSignedData(signedData);
//
// replace the store
//
cms.signerInfoStore = signerInformationStore;
//
// replace the signers in the SignedData object
//
HashSet digestAlgs = new HashSet();
var signers = signerInformationStore.GetSigners();
Asn1EncodableVector vec = new Asn1EncodableVector(signers.Count);
foreach (var signer in signers)
{
CmsUtilities.AddDigestAlgs(digestAlgs, signer, digestAlgorithmFinder);
vec.Add(signer.ToSignerInfo());
}
Asn1Set digestSet = CmsUtilities.ConvertToDLSet(digestAlgs);
Asn1Set signerSet = DLSet.FromVector(vec);
Asn1Sequence sD = (Asn1Sequence)signedData.signedData.ToAsn1Object();
//
// signers are the last item in the sequence.
//
vec = new Asn1EncodableVector(sD.Count);
vec.Add(sD[0]); // version
vec.Add(digestSet);
for (int i = 2; i != sD.Count - 1; i++)
{
vec.Add(sD[i]);
}
vec.Add(signerSet);
cms.signedData = SignedData.GetInstance(new BerSequence(vec));
//
// replace the contentInfo with the new one
//
cms.contentInfo = new ContentInfo(cms.contentInfo.ContentType, cms.signedData);
return cms;
}
/**
* Replace the certificate and CRL information associated with this
* CmsSignedData object with the new one passed in.
*
* @param signedData the signed data object to be used as a base.
* @param x509Certs the new certificates to be used.
* @param x509Crls the new CRLs to be used.
* @return a new signed data object.
* @exception CmsException if there is an error processing the stores
*/
public static CmsSignedData ReplaceCertificatesAndCrls(CmsSignedData signedData,
IStore x509Certs, IStore x509Crls)
{
return ReplaceCertificatesAndRevocations(signedData, x509Certs, x509Crls, null, null);
}
public static CmsSignedData ReplaceCertificatesAndCrls(CmsSignedData signedData,
IStore x509Certs, IStore x509Crls,
IStore x509AttrCerts)
{
return ReplaceCertificatesAndRevocations(signedData, x509Certs, x509Crls, x509AttrCerts, null);
}
public static CmsSignedData ReplaceCertificatesAndRevocations(CmsSignedData signedData,
IStore x509Certs, IStore x509Crls,
IStore x509AttrCerts, IStore otherRevocationInfos)
{
//
// copy
//
CmsSignedData cms = new CmsSignedData(signedData);
//
// replace the certs and crls in the SignedData object
//
Asn1Set certSet = null;
Asn1Set revocationSet = null;
if (x509Certs != null || x509AttrCerts != null)
{
var certificates = new List();
if (x509Certs != null)
{
certificates.AddRange(CmsUtilities.GetCertificatesFromStore(x509Certs));
}
if (x509AttrCerts != null)
{
certificates.AddRange(CmsUtilities.GetAttributeCertificatesFromStore(x509AttrCerts));
}
Asn1Set berSet = CmsUtilities.CreateBerSetFromList(certificates);
if (berSet.Count > 0)
{
certSet = berSet;
}
}
if (x509Crls != null || otherRevocationInfos != null)
{
var revocations = new List();
if (x509Crls != null)
{
revocations.AddRange(CmsUtilities.GetCrlsFromStore(x509Crls));
}
if (otherRevocationInfos != null)
{
revocations.AddRange(CmsUtilities.GetOtherRevocationInfosFromStore(otherRevocationInfos));
}
Asn1Set berSet = CmsUtilities.CreateBerSetFromList(revocations);
if (berSet.Count > 0)
{
revocationSet = berSet;
}
}
//
// replace the CMS structure.
//
SignedData old = signedData.signedData;
cms.signedData = new SignedData(
old.DigestAlgorithms,
old.EncapContentInfo,
certSet,
revocationSet,
old.SignerInfos);
//
// replace the contentInfo with the new one
//
cms.contentInfo = new ContentInfo(cms.contentInfo.ContentType, cms.signedData);
return cms;
}
}
}