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.Crypto;
using Org.BouncyCastle.Crypto.IO;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Security.Certificates;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Crypto.Operators;
namespace Org.BouncyCastle.Cms
{
/**
* general class for generating a pkcs7-signature message.
*
* A simple example of usage.
*
*
* IX509Store certs...
* IX509Store crls...
* CmsSignedDataGenerator gen = new CmsSignedDataGenerator();
*
* gen.AddSigner(privKey, cert, CmsSignedGenerator.DigestSha1);
* gen.AddCertificates(certs);
* gen.AddCrls(crls);
*
* CmsSignedData data = gen.Generate(content);
*
*
*/
public class CmsSignedDataGenerator
: CmsSignedGenerator
{
private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance;
private readonly IList signerInfs = new List();
private class SignerInf
{
private readonly CmsSignedGenerator outer;
private readonly ISignatureFactory sigCalc;
private readonly SignerIdentifier signerIdentifier;
private readonly string digestOID;
private readonly string encOID;
private readonly CmsAttributeTableGenerator sAttr;
private readonly CmsAttributeTableGenerator unsAttr;
private readonly Asn1.Cms.AttributeTable baseSignedTable;
internal SignerInf(
CmsSignedGenerator outer,
AsymmetricKeyParameter key,
SecureRandom random,
SignerIdentifier signerIdentifier,
string digestOID,
string encOID,
CmsAttributeTableGenerator sAttr,
CmsAttributeTableGenerator unsAttr,
Asn1.Cms.AttributeTable baseSignedTable)
{
string digestName = Helper.GetDigestAlgName(digestOID);
string signatureName = digestName + "with" + Helper.GetEncryptionAlgName(encOID);
this.outer = outer;
this.sigCalc = new Asn1SignatureFactory(signatureName, key, random);
this.signerIdentifier = signerIdentifier;
this.digestOID = digestOID;
this.encOID = encOID;
this.sAttr = sAttr;
this.unsAttr = unsAttr;
this.baseSignedTable = baseSignedTable;
}
internal SignerInf(
CmsSignedGenerator outer,
ISignatureFactory sigCalc,
SignerIdentifier signerIdentifier,
CmsAttributeTableGenerator sAttr,
CmsAttributeTableGenerator unsAttr,
Asn1.Cms.AttributeTable baseSignedTable)
{
this.outer = outer;
this.sigCalc = sigCalc;
this.signerIdentifier = signerIdentifier;
this.digestOID = new DefaultDigestAlgorithmIdentifierFinder().Find(
(AlgorithmIdentifier)sigCalc.AlgorithmDetails).Algorithm.Id;
this.encOID = ((AlgorithmIdentifier)sigCalc.AlgorithmDetails).Algorithm.Id;
this.sAttr = sAttr;
this.unsAttr = unsAttr;
this.baseSignedTable = baseSignedTable;
}
internal AlgorithmIdentifier DigestAlgorithmID
{
get { return new AlgorithmIdentifier(new DerObjectIdentifier(digestOID), DerNull.Instance); }
}
internal CmsAttributeTableGenerator SignedAttributes
{
get { return sAttr; }
}
internal CmsAttributeTableGenerator UnsignedAttributes
{
get { return unsAttr; }
}
internal SignerInfo ToSignerInfo(DerObjectIdentifier contentType, CmsProcessable content)
{
AlgorithmIdentifier digAlgId = DigestAlgorithmID;
string digestName = Helper.GetDigestAlgName(digestOID);
string signatureName = digestName + "with" + Helper.GetEncryptionAlgName(encOID);
if (!outer.m_digests.TryGetValue(digestOID, out var hash))
{
IDigest dig = Helper.GetDigestInstance(digestName);
if (content != null)
{
content.Write(new DigestSink(dig));
}
hash = DigestUtilities.DoFinal(dig);
outer.m_digests.Add(digestOID, (byte[])hash.Clone());
}
Asn1Set signedAttr = null;
IStreamCalculator calculator = sigCalc.CreateCalculator();
using (Stream sigStr = calculator.Stream)
{
if (sAttr != null)
{
var parameters = outer.GetBaseParameters(contentType, digAlgId, hash);
//Asn1.Cms.AttributeTable signed = sAttr.GetAttributes(Collections.unmodifiableMap(parameters));
Asn1.Cms.AttributeTable signed = sAttr.GetAttributes(parameters);
if (contentType == null) //counter signature
{
if (signed != null && signed[CmsAttributes.ContentType] != null)
{
signed = signed.Remove(CmsAttributes.ContentType);
}
}
// TODO Validate proposed signed attributes
signedAttr = outer.GetAttributeSet(signed);
// sig must be composed from the DER encoding.
signedAttr.EncodeTo(sigStr, Asn1Encodable.Der);
}
else if (content != null)
{
// TODO Use raw signature of the hash value instead
content.Write(sigStr);
}
}
byte[] sigBytes = calculator.GetResult().Collect();
Asn1Set unsignedAttr = null;
if (unsAttr != null)
{
var baseParameters = outer.GetBaseParameters(contentType, digAlgId, hash);
baseParameters[CmsAttributeTableParameter.Signature] = sigBytes.Clone();
// Asn1.Cms.AttributeTable unsigned = unsAttr.GetAttributes(Collections.unmodifiableMap(baseParameters));
Asn1.Cms.AttributeTable unsigned = unsAttr.GetAttributes(baseParameters);
// TODO Validate proposed unsigned attributes
unsignedAttr = outer.GetAttributeSet(unsigned);
}
// TODO[RSAPSS] Need the ability to specify non-default parameters
Asn1Encodable sigX509Parameters = SignerUtilities.GetDefaultX509Parameters(signatureName);
AlgorithmIdentifier encAlgId = Helper.GetEncAlgorithmIdentifier(
new DerObjectIdentifier(encOID), sigX509Parameters);
return new SignerInfo(signerIdentifier, digAlgId,
signedAttr, encAlgId, new DerOctetString(sigBytes), unsignedAttr);
}
}
public CmsSignedDataGenerator()
{
}
/// Constructor allowing specific source of randomness
/// Instance of SecureRandom to use.
public CmsSignedDataGenerator(SecureRandom random)
: base(random)
{
}
/**
* add a signer - no attributes other than the default ones will be
* provided here.
*
* @param key signing key to use
* @param cert certificate containing corresponding public key
* @param digestOID digest algorithm OID
*/
public void AddSigner(
AsymmetricKeyParameter privateKey,
X509Certificate cert,
string digestOID)
{
AddSigner(privateKey, cert, Helper.GetEncOid(privateKey, digestOID), digestOID);
}
/**
* add a signer, specifying the digest encryption algorithm to use - no attributes other than the default ones will be
* provided here.
*
* @param key signing key to use
* @param cert certificate containing corresponding public key
* @param encryptionOID digest encryption algorithm OID
* @param digestOID digest algorithm OID
*/
public void AddSigner(
AsymmetricKeyParameter privateKey,
X509Certificate cert,
string encryptionOID,
string digestOID)
{
doAddSigner(privateKey, GetSignerIdentifier(cert), encryptionOID, digestOID,
new DefaultSignedAttributeTableGenerator(), null, null);
}
/**
* add a signer - no attributes other than the default ones will be
* provided here.
*/
public void AddSigner(
AsymmetricKeyParameter privateKey,
byte[] subjectKeyID,
string digestOID)
{
AddSigner(privateKey, subjectKeyID, Helper.GetEncOid(privateKey, digestOID), digestOID);
}
/**
* add a signer, specifying the digest encryption algorithm to use - no attributes other than the default ones will be
* provided here.
*/
public void AddSigner(
AsymmetricKeyParameter privateKey,
byte[] subjectKeyID,
string encryptionOID,
string digestOID)
{
doAddSigner(privateKey, GetSignerIdentifier(subjectKeyID), encryptionOID, digestOID,
new DefaultSignedAttributeTableGenerator(), null, null);
}
/**
* add a signer with extra signed/unsigned attributes.
*
* @param key signing key to use
* @param cert certificate containing corresponding public key
* @param digestOID digest algorithm OID
* @param signedAttr table of attributes to be included in signature
* @param unsignedAttr table of attributes to be included as unsigned
*/
public void AddSigner(
AsymmetricKeyParameter privateKey,
X509Certificate cert,
string digestOID,
Asn1.Cms.AttributeTable signedAttr,
Asn1.Cms.AttributeTable unsignedAttr)
{
AddSigner(privateKey, cert, Helper.GetEncOid(privateKey, digestOID), digestOID,
signedAttr, unsignedAttr);
}
/**
* add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes.
*
* @param key signing key to use
* @param cert certificate containing corresponding public key
* @param encryptionOID digest encryption algorithm OID
* @param digestOID digest algorithm OID
* @param signedAttr table of attributes to be included in signature
* @param unsignedAttr table of attributes to be included as unsigned
*/
public void AddSigner(
AsymmetricKeyParameter privateKey,
X509Certificate cert,
string encryptionOID,
string digestOID,
Asn1.Cms.AttributeTable signedAttr,
Asn1.Cms.AttributeTable unsignedAttr)
{
doAddSigner(privateKey, GetSignerIdentifier(cert), encryptionOID, digestOID,
new DefaultSignedAttributeTableGenerator(signedAttr),
new SimpleAttributeTableGenerator(unsignedAttr),
signedAttr);
}
/**
* add a signer with extra signed/unsigned attributes.
*
* @param key signing key to use
* @param subjectKeyID subjectKeyID of corresponding public key
* @param digestOID digest algorithm OID
* @param signedAttr table of attributes to be included in signature
* @param unsignedAttr table of attributes to be included as unsigned
*/
public void AddSigner(
AsymmetricKeyParameter privateKey,
byte[] subjectKeyID,
string digestOID,
Asn1.Cms.AttributeTable signedAttr,
Asn1.Cms.AttributeTable unsignedAttr)
{
AddSigner(privateKey, subjectKeyID, Helper.GetEncOid(privateKey, digestOID), digestOID,
signedAttr, unsignedAttr);
}
/**
* add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes.
*
* @param key signing key to use
* @param subjectKeyID subjectKeyID of corresponding public key
* @param encryptionOID digest encryption algorithm OID
* @param digestOID digest algorithm OID
* @param signedAttr table of attributes to be included in signature
* @param unsignedAttr table of attributes to be included as unsigned
*/
public void AddSigner(
AsymmetricKeyParameter privateKey,
byte[] subjectKeyID,
string encryptionOID,
string digestOID,
Asn1.Cms.AttributeTable signedAttr,
Asn1.Cms.AttributeTable unsignedAttr)
{
doAddSigner(privateKey, GetSignerIdentifier(subjectKeyID), encryptionOID, digestOID,
new DefaultSignedAttributeTableGenerator(signedAttr),
new SimpleAttributeTableGenerator(unsignedAttr),
signedAttr);
}
/**
* add a signer with extra signed/unsigned attributes based on generators.
*/
public void AddSigner(
AsymmetricKeyParameter privateKey,
X509Certificate cert,
string digestOID,
CmsAttributeTableGenerator signedAttrGen,
CmsAttributeTableGenerator unsignedAttrGen)
{
AddSigner(privateKey, cert, Helper.GetEncOid(privateKey, digestOID), digestOID,
signedAttrGen, unsignedAttrGen);
}
/**
* add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes based on generators.
*/
public void AddSigner(
AsymmetricKeyParameter privateKey,
X509Certificate cert,
string encryptionOID,
string digestOID,
CmsAttributeTableGenerator signedAttrGen,
CmsAttributeTableGenerator unsignedAttrGen)
{
doAddSigner(privateKey, GetSignerIdentifier(cert), encryptionOID, digestOID, signedAttrGen,
unsignedAttrGen, null);
}
/**
* add a signer with extra signed/unsigned attributes based on generators.
*/
public void AddSigner(
AsymmetricKeyParameter privateKey,
byte[] subjectKeyID,
string digestOID,
CmsAttributeTableGenerator signedAttrGen,
CmsAttributeTableGenerator unsignedAttrGen)
{
AddSigner(privateKey, subjectKeyID, Helper.GetEncOid(privateKey, digestOID), digestOID,
signedAttrGen, unsignedAttrGen);
}
/**
* add a signer, including digest encryption algorithm, with extra signed/unsigned attributes based on generators.
*/
public void AddSigner(
AsymmetricKeyParameter privateKey,
byte[] subjectKeyID,
string encryptionOID,
string digestOID,
CmsAttributeTableGenerator signedAttrGen,
CmsAttributeTableGenerator unsignedAttrGen)
{
doAddSigner(privateKey, GetSignerIdentifier(subjectKeyID), encryptionOID, digestOID,
signedAttrGen, unsignedAttrGen, null);
}
public void AddSignerInfoGenerator(SignerInfoGenerator signerInfoGenerator)
{
signerInfs.Add(new SignerInf(this, signerInfoGenerator.contentSigner, signerInfoGenerator.sigId,
signerInfoGenerator.signedGen, signerInfoGenerator.unsignedGen, null));
}
private void doAddSigner(
AsymmetricKeyParameter privateKey,
SignerIdentifier signerIdentifier,
string encryptionOID,
string digestOID,
CmsAttributeTableGenerator signedAttrGen,
CmsAttributeTableGenerator unsignedAttrGen,
Asn1.Cms.AttributeTable baseSignedTable)
{
signerInfs.Add(new SignerInf(this, privateKey, m_random, signerIdentifier, digestOID, encryptionOID,
signedAttrGen, unsignedAttrGen, baseSignedTable));
}
/**
* generate a signed object that for a CMS Signed Data object
*/
public CmsSignedData Generate(
CmsProcessable content)
{
return Generate(content, false);
}
/**
* generate a signed object that for a CMS Signed Data
* object - if encapsulate is true a copy
* of the message will be included in the signature. The content type
* is set according to the OID represented by the string signedContentType.
*/
public CmsSignedData Generate(
string signedContentType,
// FIXME Avoid accessing more than once to support CmsProcessableInputStream
CmsProcessable content,
bool encapsulate)
{
Asn1EncodableVector digestAlgs = new Asn1EncodableVector();
Asn1EncodableVector signerInfos = new Asn1EncodableVector();
m_digests.Clear(); // clear the current preserved digest state
//
// add the precalculated SignerInfo objects.
//
foreach (SignerInformation signer in _signers)
{
digestAlgs.Add(Helper.FixAlgID(signer.DigestAlgorithmID));
// TODO Verify the content type and calculated digest match the precalculated SignerInfo
signerInfos.Add(signer.ToSignerInfo());
}
//
// add the SignerInfo objects
//
bool isCounterSignature = (signedContentType == null);
DerObjectIdentifier contentTypeOid = isCounterSignature
? null
: new DerObjectIdentifier(signedContentType);
foreach (SignerInf signer in signerInfs)
{
try
{
digestAlgs.Add(signer.DigestAlgorithmID);
signerInfos.Add(signer.ToSignerInfo(contentTypeOid, content));
}
catch (IOException e)
{
throw new CmsException("encoding error.", e);
}
catch (InvalidKeyException e)
{
throw new CmsException("key inappropriate for signature.", e);
}
catch (SignatureException e)
{
throw new CmsException("error creating signature.", e);
}
catch (CertificateEncodingException e)
{
throw new CmsException("error creating sid.", e);
}
}
Asn1Set certificates = null;
if (_certs.Count != 0)
{
certificates = UseDerForCerts
? CmsUtilities.CreateDerSetFromList(_certs)
: CmsUtilities.CreateBerSetFromList(_certs);
}
Asn1Set certrevlist = null;
if (_crls.Count != 0)
{
certrevlist = UseDerForCrls
? CmsUtilities.CreateDerSetFromList(_crls)
: CmsUtilities.CreateBerSetFromList(_crls);
}
Asn1OctetString octs = null;
if (encapsulate)
{
MemoryStream bOut = new MemoryStream();
if (content != null)
{
try
{
content.Write(bOut);
}
catch (IOException e)
{
throw new CmsException("encapsulation error.", e);
}
}
octs = new BerOctetString(bOut.ToArray());
}
ContentInfo encInfo = new ContentInfo(contentTypeOid, octs);
SignedData sd = new SignedData(
new DerSet(digestAlgs),
encInfo,
certificates,
certrevlist,
new DerSet(signerInfos));
ContentInfo contentInfo = new ContentInfo(CmsObjectIdentifiers.SignedData, sd);
return new CmsSignedData(content, contentInfo);
}
/**
* generate a signed object that for a CMS Signed Data
* object - if encapsulate is true a copy
* of the message will be included in the signature with the
* default content type "data".
*/
public CmsSignedData Generate(
CmsProcessable content,
bool encapsulate)
{
return this.Generate(Data, content, encapsulate);
}
/**
* generate a set of one or more SignerInformation objects representing counter signatures on
* the passed in SignerInformation object.
*
* @param signer the signer to be countersigned
* @param sigProvider the provider to be used for counter signing.
* @return a store containing the signers.
*/
public SignerInformationStore GenerateCounterSigners(
SignerInformation signer)
{
return this.Generate(null, new CmsProcessableByteArray(signer.GetSignature()), false).GetSignerInfos();
}
}
}