You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
937 lines
29 KiB
937 lines
29 KiB
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) |
|
#pragma warning disable |
|
using System; |
|
using System.Collections; |
|
using System.Diagnostics; |
|
using System.IO; |
|
|
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Asn1; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Asn1.Cms; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Asn1.X509; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.IO; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Security; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Security.Certificates; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Collections; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.IO; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.X509; |
|
|
|
namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Cms |
|
{ |
|
/** |
|
* General class for generating a pkcs7-signature message stream. |
|
* <p> |
|
* A simple example of usage. |
|
* </p> |
|
* <pre> |
|
* IX509Store certs... |
|
* CmsSignedDataStreamGenerator gen = new CmsSignedDataStreamGenerator(); |
|
* |
|
* gen.AddSigner(privateKey, cert, CmsSignedDataStreamGenerator.DIGEST_SHA1); |
|
* |
|
* gen.AddCertificates(certs); |
|
* |
|
* Stream sigOut = gen.Open(bOut); |
|
* |
|
* sigOut.Write(Encoding.UTF8.GetBytes("Hello World!")); |
|
* |
|
* sigOut.Close(); |
|
* </pre> |
|
*/ |
|
public class CmsSignedDataStreamGenerator |
|
: CmsSignedGenerator |
|
{ |
|
private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance; |
|
|
|
private readonly IList _signerInfs = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateArrayList(); |
|
private readonly ISet _messageDigestOids = new HashSet(); |
|
private readonly IDictionary _messageDigests = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable(); |
|
private readonly IDictionary _messageHashes = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable(); |
|
private bool _messageDigestsLocked; |
|
private int _bufferSize; |
|
|
|
private class DigestAndSignerInfoGeneratorHolder |
|
{ |
|
internal readonly ISignerInfoGenerator signerInf; |
|
internal readonly string digestOID; |
|
|
|
internal DigestAndSignerInfoGeneratorHolder(ISignerInfoGenerator signerInf, String digestOID) |
|
{ |
|
this.signerInf = signerInf; |
|
this.digestOID = digestOID; |
|
} |
|
|
|
internal AlgorithmIdentifier DigestAlgorithm |
|
{ |
|
get { return new AlgorithmIdentifier(new DerObjectIdentifier(this.digestOID), DerNull.Instance); } |
|
} |
|
} |
|
|
|
private class SignerInfoGeneratorImpl : ISignerInfoGenerator |
|
{ |
|
private readonly CmsSignedDataStreamGenerator outer; |
|
|
|
private readonly SignerIdentifier _signerIdentifier; |
|
private readonly string _digestOID; |
|
private readonly string _encOID; |
|
private readonly CmsAttributeTableGenerator _sAttr; |
|
private readonly CmsAttributeTableGenerator _unsAttr; |
|
private readonly string _encName; |
|
private readonly ISigner _sig; |
|
|
|
internal SignerInfoGeneratorImpl( |
|
CmsSignedDataStreamGenerator outer, |
|
AsymmetricKeyParameter key, |
|
SignerIdentifier signerIdentifier, |
|
string digestOID, |
|
string encOID, |
|
CmsAttributeTableGenerator sAttr, |
|
CmsAttributeTableGenerator unsAttr) |
|
{ |
|
this.outer = outer; |
|
|
|
_signerIdentifier = signerIdentifier; |
|
_digestOID = digestOID; |
|
_encOID = encOID; |
|
_sAttr = sAttr; |
|
_unsAttr = unsAttr; |
|
_encName = Helper.GetEncryptionAlgName(_encOID); |
|
|
|
string digestName = Helper.GetDigestAlgName(_digestOID); |
|
string signatureName = digestName + "with" + _encName; |
|
|
|
if (_sAttr != null) |
|
{ |
|
_sig = Helper.GetSignatureInstance(signatureName); |
|
} |
|
else |
|
{ |
|
// Note: Need to use raw signatures here since we have already calculated the digest |
|
if (_encName.Equals("RSA")) |
|
{ |
|
_sig = Helper.GetSignatureInstance("RSA"); |
|
} |
|
else if (_encName.Equals("DSA")) |
|
{ |
|
_sig = Helper.GetSignatureInstance("NONEwithDSA"); |
|
} |
|
// TODO Add support for raw PSS |
|
// else if (_encName.equals("RSAandMGF1")) |
|
// { |
|
// _sig = CMSSignedHelper.INSTANCE.getSignatureInstance("NONEWITHRSAPSS", _sigProvider); |
|
// try |
|
// { |
|
// // Init the params this way to avoid having a 'raw' version of each PSS algorithm |
|
// Signature sig2 = CMSSignedHelper.INSTANCE.getSignatureInstance(signatureName, _sigProvider); |
|
// PSSParameterSpec spec = (PSSParameterSpec)sig2.getParameters().getParameterSpec(PSSParameterSpec.class); |
|
// _sig.setParameter(spec); |
|
// } |
|
// catch (Exception e) |
|
// { |
|
// throw new SignatureException("algorithm: " + _encName + " could not be configured."); |
|
// } |
|
// } |
|
else |
|
{ |
|
throw new SignatureException("algorithm: " + _encName + " not supported in base signatures."); |
|
} |
|
} |
|
|
|
_sig.Init(true, new ParametersWithRandom(key, outer.rand)); |
|
} |
|
|
|
public SignerInfo Generate(DerObjectIdentifier contentType, AlgorithmIdentifier digestAlgorithm, |
|
byte[] calculatedDigest) |
|
{ |
|
try |
|
{ |
|
string digestName = Helper.GetDigestAlgName(_digestOID); |
|
string signatureName = digestName + "with" + _encName; |
|
|
|
// AlgorithmIdentifier digAlgId = DigestAlgorithmID; |
|
// |
|
// byte[] hash = (byte[])outer._messageHashes[Helper.GetDigestAlgName(this._digestOID)]; |
|
// outer._digests[_digestOID] = hash.Clone(); |
|
|
|
byte[] bytesToSign = calculatedDigest; |
|
|
|
/* RFC 3852 5.4 |
|
* The result of the message digest calculation process depends on |
|
* whether the signedAttrs field is present. When the field is absent, |
|
* the result is just the message digest of the content as described |
|
* |
|
* above. When the field is present, however, the result is the message |
|
* digest of the complete DER encoding of the SignedAttrs value |
|
* contained in the signedAttrs field. |
|
*/ |
|
Asn1Set signedAttr = null; |
|
if (_sAttr != null) |
|
{ |
|
IDictionary parameters = outer.GetBaseParameters(contentType, digestAlgorithm, calculatedDigest); |
|
|
|
// 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) |
|
{ |
|
IDictionary tmpSigned = signed.ToDictionary(); |
|
tmpSigned.Remove(CmsAttributes.ContentType); |
|
signed = new Asn1.Cms.AttributeTable(tmpSigned); |
|
} |
|
} |
|
|
|
signedAttr = outer.GetAttributeSet(signed); |
|
|
|
// sig must be composed from the DER encoding. |
|
bytesToSign = signedAttr.GetEncoded(Asn1Encodable.Der); |
|
} |
|
else |
|
{ |
|
// Note: Need to use raw signatures here since we have already calculated the digest |
|
if (_encName.Equals("RSA")) |
|
{ |
|
DigestInfo dInfo = new DigestInfo(digestAlgorithm, calculatedDigest); |
|
bytesToSign = dInfo.GetEncoded(Asn1Encodable.Der); |
|
} |
|
} |
|
|
|
_sig.BlockUpdate(bytesToSign, 0, bytesToSign.Length); |
|
byte[] sigBytes = _sig.GenerateSignature(); |
|
|
|
Asn1Set unsignedAttr = null; |
|
if (_unsAttr != null) |
|
{ |
|
IDictionary parameters = outer.GetBaseParameters( |
|
contentType, digestAlgorithm, calculatedDigest); |
|
parameters[CmsAttributeTableParameter.Signature] = sigBytes.Clone(); |
|
|
|
// Asn1.Cms.AttributeTable unsigned = _unsAttr.getAttributes(Collections.unmodifiableMap(parameters)); |
|
Asn1.Cms.AttributeTable unsigned = _unsAttr.GetAttributes(parameters); |
|
|
|
unsignedAttr = outer.GetAttributeSet(unsigned); |
|
} |
|
|
|
// TODO[RSAPSS] Need the ability to specify non-default parameters |
|
Asn1Encodable sigX509Parameters = SignerUtilities.GetDefaultX509Parameters(signatureName); |
|
AlgorithmIdentifier digestEncryptionAlgorithm = Helper.GetEncAlgorithmIdentifier( |
|
new DerObjectIdentifier(_encOID), sigX509Parameters); |
|
|
|
return new SignerInfo(_signerIdentifier, digestAlgorithm, |
|
signedAttr, digestEncryptionAlgorithm, new DerOctetString(sigBytes), unsignedAttr); |
|
} |
|
catch (IOException e) |
|
{ |
|
throw new CmsStreamException("encoding error.", e); |
|
} |
|
catch (SignatureException e) |
|
{ |
|
throw new CmsStreamException("error creating signature.", e); |
|
} |
|
} |
|
} |
|
|
|
public CmsSignedDataStreamGenerator() |
|
{ |
|
} |
|
|
|
/// <summary>Constructor allowing specific source of randomness</summary> |
|
/// <param name="rand">Instance of <c>SecureRandom</c> to use.</param> |
|
public CmsSignedDataStreamGenerator( |
|
SecureRandom rand) |
|
: base(rand) |
|
{ |
|
} |
|
|
|
/** |
|
* Set the underlying string size for encapsulated data |
|
* |
|
* @param bufferSize length of octet strings to buffer the data. |
|
*/ |
|
public void SetBufferSize( |
|
int bufferSize) |
|
{ |
|
_bufferSize = bufferSize; |
|
} |
|
|
|
public void AddDigests( |
|
params string[] digestOids) |
|
{ |
|
AddDigests((IEnumerable) digestOids); |
|
} |
|
|
|
public void AddDigests( |
|
IEnumerable digestOids) |
|
{ |
|
foreach (string digestOid in digestOids) |
|
{ |
|
ConfigureDigest(digestOid); |
|
} |
|
} |
|
|
|
/** |
|
* add a signer - no attributes other than the default ones will be |
|
* provided here. |
|
* @throws NoSuchAlgorithmException |
|
* @throws InvalidKeyException |
|
*/ |
|
public void AddSigner( |
|
AsymmetricKeyParameter privateKey, |
|
X509Certificate cert, |
|
string digestOid) |
|
{ |
|
AddSigner(privateKey, cert, digestOid, |
|
new DefaultSignedAttributeTableGenerator(), null); |
|
} |
|
|
|
/** |
|
* add a signer, specifying the digest encryption algorithm - no attributes other than the default ones will be |
|
* provided here. |
|
* @throws NoSuchProviderException |
|
* @throws NoSuchAlgorithmException |
|
* @throws InvalidKeyException |
|
*/ |
|
public void AddSigner( |
|
AsymmetricKeyParameter privateKey, |
|
X509Certificate cert, |
|
string encryptionOid, |
|
string digestOid) |
|
{ |
|
AddSigner(privateKey, cert, encryptionOid, digestOid, |
|
new DefaultSignedAttributeTableGenerator(), |
|
(CmsAttributeTableGenerator)null); |
|
} |
|
|
|
/** |
|
* add a signer with extra signed/unsigned attributes. |
|
* @throws NoSuchAlgorithmException |
|
* @throws InvalidKeyException |
|
*/ |
|
public void AddSigner( |
|
AsymmetricKeyParameter privateKey, |
|
X509Certificate cert, |
|
string digestOid, |
|
Asn1.Cms.AttributeTable signedAttr, |
|
Asn1.Cms.AttributeTable unsignedAttr) |
|
{ |
|
AddSigner(privateKey, cert, digestOid, |
|
new DefaultSignedAttributeTableGenerator(signedAttr), |
|
new SimpleAttributeTableGenerator(unsignedAttr)); |
|
} |
|
|
|
/** |
|
* add a signer with extra signed/unsigned attributes - specifying digest |
|
* encryption algorithm. |
|
* @throws NoSuchProviderException |
|
* @throws NoSuchAlgorithmException |
|
* @throws InvalidKeyException |
|
*/ |
|
public void AddSigner( |
|
AsymmetricKeyParameter privateKey, |
|
X509Certificate cert, |
|
string encryptionOid, |
|
string digestOid, |
|
Asn1.Cms.AttributeTable signedAttr, |
|
Asn1.Cms.AttributeTable unsignedAttr) |
|
{ |
|
AddSigner(privateKey, cert, encryptionOid, digestOid, |
|
new DefaultSignedAttributeTableGenerator(signedAttr), |
|
new SimpleAttributeTableGenerator(unsignedAttr)); |
|
} |
|
|
|
public void AddSigner( |
|
AsymmetricKeyParameter privateKey, |
|
X509Certificate cert, |
|
string digestOid, |
|
CmsAttributeTableGenerator signedAttrGenerator, |
|
CmsAttributeTableGenerator unsignedAttrGenerator) |
|
{ |
|
AddSigner(privateKey, cert, Helper.GetEncOid(privateKey, digestOid), digestOid, |
|
signedAttrGenerator, unsignedAttrGenerator); |
|
} |
|
|
|
public void AddSigner( |
|
AsymmetricKeyParameter privateKey, |
|
X509Certificate cert, |
|
string encryptionOid, |
|
string digestOid, |
|
CmsAttributeTableGenerator signedAttrGenerator, |
|
CmsAttributeTableGenerator unsignedAttrGenerator) |
|
{ |
|
DoAddSigner(privateKey, GetSignerIdentifier(cert), encryptionOid, digestOid, |
|
signedAttrGenerator, unsignedAttrGenerator); |
|
} |
|
|
|
/** |
|
* add a signer - no attributes other than the default ones will be |
|
* provided here. |
|
* @throws NoSuchAlgorithmException |
|
* @throws InvalidKeyException |
|
*/ |
|
public void AddSigner( |
|
AsymmetricKeyParameter privateKey, |
|
byte[] subjectKeyID, |
|
string digestOid) |
|
{ |
|
AddSigner(privateKey, subjectKeyID, digestOid, new DefaultSignedAttributeTableGenerator(), |
|
(CmsAttributeTableGenerator)null); |
|
} |
|
|
|
/** |
|
* add a signer - no attributes other than the default ones will be |
|
* provided here. |
|
* @throws NoSuchProviderException |
|
* @throws NoSuchAlgorithmException |
|
* @throws InvalidKeyException |
|
*/ |
|
public void AddSigner( |
|
AsymmetricKeyParameter privateKey, |
|
byte[] subjectKeyID, |
|
string encryptionOid, |
|
string digestOid) |
|
{ |
|
AddSigner(privateKey, subjectKeyID, encryptionOid, digestOid, |
|
new DefaultSignedAttributeTableGenerator(), |
|
(CmsAttributeTableGenerator)null); |
|
} |
|
|
|
/** |
|
* add a signer with extra signed/unsigned attributes. |
|
* @throws NoSuchAlgorithmException |
|
* @throws InvalidKeyException |
|
*/ |
|
public void AddSigner( |
|
AsymmetricKeyParameter privateKey, |
|
byte[] subjectKeyID, |
|
string digestOid, |
|
Asn1.Cms.AttributeTable signedAttr, |
|
Asn1.Cms.AttributeTable unsignedAttr) |
|
{ |
|
AddSigner(privateKey, subjectKeyID, digestOid, |
|
new DefaultSignedAttributeTableGenerator(signedAttr), |
|
new SimpleAttributeTableGenerator(unsignedAttr)); |
|
} |
|
|
|
public void AddSigner( |
|
AsymmetricKeyParameter privateKey, |
|
byte[] subjectKeyID, |
|
string digestOid, |
|
CmsAttributeTableGenerator signedAttrGenerator, |
|
CmsAttributeTableGenerator unsignedAttrGenerator) |
|
{ |
|
AddSigner(privateKey, subjectKeyID, Helper.GetEncOid(privateKey, digestOid), |
|
digestOid, signedAttrGenerator, unsignedAttrGenerator); |
|
} |
|
|
|
public void AddSigner( |
|
AsymmetricKeyParameter privateKey, |
|
byte[] subjectKeyID, |
|
string encryptionOid, |
|
string digestOid, |
|
CmsAttributeTableGenerator signedAttrGenerator, |
|
CmsAttributeTableGenerator unsignedAttrGenerator) |
|
{ |
|
DoAddSigner(privateKey, GetSignerIdentifier(subjectKeyID), encryptionOid, digestOid, |
|
signedAttrGenerator, unsignedAttrGenerator); |
|
} |
|
|
|
private void DoAddSigner( |
|
AsymmetricKeyParameter privateKey, |
|
SignerIdentifier signerIdentifier, |
|
string encryptionOid, |
|
string digestOid, |
|
CmsAttributeTableGenerator signedAttrGenerator, |
|
CmsAttributeTableGenerator unsignedAttrGenerator) |
|
{ |
|
ConfigureDigest(digestOid); |
|
|
|
SignerInfoGeneratorImpl signerInf = new SignerInfoGeneratorImpl(this, privateKey, |
|
signerIdentifier, digestOid, encryptionOid, signedAttrGenerator, unsignedAttrGenerator); |
|
|
|
_signerInfs.Add(new DigestAndSignerInfoGeneratorHolder(signerInf, digestOid)); |
|
} |
|
|
|
internal override void AddSignerCallback( |
|
SignerInformation si) |
|
{ |
|
// FIXME If there were parameters in si.DigestAlgorithmID.Parameters, they are lost |
|
// NB: Would need to call FixAlgID on the DigestAlgorithmID |
|
|
|
// For precalculated signers, just need to register the algorithm, not configure a digest |
|
RegisterDigestOid(si.DigestAlgorithmID.Algorithm.Id); |
|
} |
|
|
|
/** |
|
* generate a signed object that for a CMS Signed Data object |
|
*/ |
|
public Stream Open( |
|
Stream outStream) |
|
{ |
|
return Open(outStream, 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 with the |
|
* default content type "data". |
|
*/ |
|
public Stream Open( |
|
Stream outStream, |
|
bool encapsulate) |
|
{ |
|
return Open(outStream, Data, encapsulate); |
|
} |
|
|
|
/** |
|
* generate a signed object that for a CMS Signed Data |
|
* object using the given provider - if encapsulate is true a copy |
|
* of the message will be included in the signature with the |
|
* default content type "data". If dataOutputStream is non null the data |
|
* being signed will be written to the stream as it is processed. |
|
* @param out stream the CMS object is to be written to. |
|
* @param encapsulate true if data should be encapsulated. |
|
* @param dataOutputStream output stream to copy the data being signed to. |
|
*/ |
|
public Stream Open( |
|
Stream outStream, |
|
bool encapsulate, |
|
Stream dataOutputStream) |
|
{ |
|
return Open(outStream, Data, encapsulate, dataOutputStream); |
|
} |
|
|
|
/** |
|
* 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 Stream Open( |
|
Stream outStream, |
|
string signedContentType, |
|
bool encapsulate) |
|
{ |
|
return Open(outStream, signedContentType, encapsulate, null); |
|
} |
|
|
|
/** |
|
* generate a signed object that for a CMS Signed Data |
|
* object using the given provider - 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. |
|
* @param out stream the CMS object is to be written to. |
|
* @param signedContentType OID for data to be signed. |
|
* @param encapsulate true if data should be encapsulated. |
|
* @param dataOutputStream output stream to copy the data being signed to. |
|
*/ |
|
public Stream Open( |
|
Stream outStream, |
|
string signedContentType, |
|
bool encapsulate, |
|
Stream dataOutputStream) |
|
{ |
|
if (outStream == null) |
|
throw new ArgumentNullException("outStream"); |
|
if (!outStream.CanWrite) |
|
throw new ArgumentException("Expected writeable stream", "outStream"); |
|
if (dataOutputStream != null && !dataOutputStream.CanWrite) |
|
throw new ArgumentException("Expected writeable stream", "dataOutputStream"); |
|
|
|
_messageDigestsLocked = true; |
|
|
|
// |
|
// ContentInfo |
|
// |
|
BerSequenceGenerator sGen = new BerSequenceGenerator(outStream); |
|
|
|
sGen.AddObject(CmsObjectIdentifiers.SignedData); |
|
|
|
// |
|
// Signed Data |
|
// |
|
BerSequenceGenerator sigGen = new BerSequenceGenerator( |
|
sGen.GetRawOutputStream(), 0, true); |
|
|
|
bool isCounterSignature = (signedContentType == null); |
|
|
|
DerObjectIdentifier contentTypeOid = isCounterSignature |
|
? null |
|
: new DerObjectIdentifier(signedContentType); |
|
|
|
sigGen.AddObject(CalculateVersion(contentTypeOid)); |
|
|
|
Asn1EncodableVector digestAlgs = new Asn1EncodableVector(); |
|
|
|
foreach (string digestOid in _messageDigestOids) |
|
{ |
|
digestAlgs.Add( |
|
new AlgorithmIdentifier(new DerObjectIdentifier(digestOid), DerNull.Instance)); |
|
} |
|
|
|
{ |
|
byte[] tmp = new DerSet(digestAlgs).GetEncoded(); |
|
sigGen.GetRawOutputStream().Write(tmp, 0, tmp.Length); |
|
} |
|
|
|
BerSequenceGenerator eiGen = new BerSequenceGenerator(sigGen.GetRawOutputStream()); |
|
eiGen.AddObject(contentTypeOid); |
|
|
|
// If encapsulating, add the data as an octet string in the sequence |
|
Stream encapStream = encapsulate |
|
? CmsUtilities.CreateBerOctetOutputStream(eiGen.GetRawOutputStream(), 0, true, _bufferSize) |
|
: null; |
|
|
|
// Also send the data to 'dataOutputStream' if necessary |
|
Stream teeStream = GetSafeTeeOutputStream(dataOutputStream, encapStream); |
|
|
|
// Let all the digests see the data as it is written |
|
Stream digStream = AttachDigestsToOutputStream(_messageDigests.Values, teeStream); |
|
|
|
return new CmsSignedDataOutputStream(this, digStream, signedContentType, sGen, sigGen, eiGen); |
|
} |
|
|
|
private void RegisterDigestOid( |
|
string digestOid) |
|
{ |
|
if (_messageDigestsLocked) |
|
{ |
|
if (!_messageDigestOids.Contains(digestOid)) |
|
throw new InvalidOperationException("Cannot register new digest OIDs after the data stream is opened"); |
|
} |
|
else |
|
{ |
|
_messageDigestOids.Add(digestOid); |
|
} |
|
} |
|
|
|
private void ConfigureDigest( |
|
string digestOid) |
|
{ |
|
RegisterDigestOid(digestOid); |
|
|
|
string digestName = Helper.GetDigestAlgName(digestOid); |
|
IDigest dig = (IDigest)_messageDigests[digestName]; |
|
if (dig == null) |
|
{ |
|
if (_messageDigestsLocked) |
|
throw new InvalidOperationException("Cannot configure new digests after the data stream is opened"); |
|
|
|
dig = Helper.GetDigestInstance(digestName); |
|
_messageDigests[digestName] = dig; |
|
} |
|
} |
|
|
|
// TODO Make public? |
|
internal void Generate( |
|
Stream outStream, |
|
string eContentType, |
|
bool encapsulate, |
|
Stream dataOutputStream, |
|
CmsProcessable content) |
|
{ |
|
Stream signedOut = Open(outStream, eContentType, encapsulate, dataOutputStream); |
|
if (content != null) |
|
{ |
|
content.Write(signedOut); |
|
} |
|
BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.Dispose(signedOut); |
|
} |
|
|
|
// RFC3852, section 5.1: |
|
// IF ((certificates is present) AND |
|
// (any certificates with a type of other are present)) OR |
|
// ((crls is present) AND |
|
// (any crls with a type of other are present)) |
|
// THEN version MUST be 5 |
|
// ELSE |
|
// IF (certificates is present) AND |
|
// (any version 2 attribute certificates are present) |
|
// THEN version MUST be 4 |
|
// ELSE |
|
// IF ((certificates is present) AND |
|
// (any version 1 attribute certificates are present)) OR |
|
// (any SignerInfo structures are version 3) OR |
|
// (encapContentInfo eContentType is other than id-data) |
|
// THEN version MUST be 3 |
|
// ELSE version MUST be 1 |
|
// |
|
private DerInteger CalculateVersion( |
|
DerObjectIdentifier contentOid) |
|
{ |
|
bool otherCert = false; |
|
bool otherCrl = false; |
|
bool attrCertV1Found = false; |
|
bool attrCertV2Found = false; |
|
|
|
if (_certs != null) |
|
{ |
|
foreach (object obj in _certs) |
|
{ |
|
if (obj is Asn1TaggedObject) |
|
{ |
|
Asn1TaggedObject tagged = (Asn1TaggedObject) obj; |
|
|
|
if (tagged.TagNo == 1) |
|
{ |
|
attrCertV1Found = true; |
|
} |
|
else if (tagged.TagNo == 2) |
|
{ |
|
attrCertV2Found = true; |
|
} |
|
else if (tagged.TagNo == 3) |
|
{ |
|
otherCert = true; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (otherCert) |
|
{ |
|
return new DerInteger(5); |
|
} |
|
|
|
if (_crls != null) |
|
{ |
|
foreach (object obj in _crls) |
|
{ |
|
if (obj is Asn1TaggedObject) |
|
{ |
|
otherCrl = true; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if (otherCrl) |
|
{ |
|
return new DerInteger(5); |
|
} |
|
|
|
if (attrCertV2Found) |
|
{ |
|
return new DerInteger(4); |
|
} |
|
|
|
if (attrCertV1Found || !CmsObjectIdentifiers.Data.Equals(contentOid) || CheckForVersion3(_signers)) |
|
{ |
|
return new DerInteger(3); |
|
} |
|
|
|
return new DerInteger(1); |
|
} |
|
|
|
private bool CheckForVersion3( |
|
IList signerInfos) |
|
{ |
|
foreach (SignerInformation si in signerInfos) |
|
{ |
|
SignerInfo s = SignerInfo.GetInstance(si.ToSignerInfo()); |
|
|
|
if (s.Version.IntValueExact == 3) |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
private static Stream AttachDigestsToOutputStream(ICollection digests, Stream s) |
|
{ |
|
Stream result = s; |
|
foreach (IDigest digest in digests) |
|
{ |
|
result = GetSafeTeeOutputStream(result, new DigestSink(digest)); |
|
} |
|
return result; |
|
} |
|
|
|
private static Stream GetSafeOutputStream(Stream s) |
|
{ |
|
if (s == null) |
|
return new NullOutputStream(); |
|
return s; |
|
} |
|
|
|
private static Stream GetSafeTeeOutputStream(Stream s1, Stream s2) |
|
{ |
|
if (s1 == null) |
|
return GetSafeOutputStream(s2); |
|
if (s2 == null) |
|
return GetSafeOutputStream(s1); |
|
return new TeeOutputStream(s1, s2); |
|
} |
|
|
|
private class CmsSignedDataOutputStream |
|
: BaseOutputStream |
|
{ |
|
private readonly CmsSignedDataStreamGenerator outer; |
|
|
|
private Stream _out; |
|
private DerObjectIdentifier _contentOID; |
|
private BerSequenceGenerator _sGen; |
|
private BerSequenceGenerator _sigGen; |
|
private BerSequenceGenerator _eiGen; |
|
|
|
public CmsSignedDataOutputStream( |
|
CmsSignedDataStreamGenerator outer, |
|
Stream outStream, |
|
string contentOID, |
|
BerSequenceGenerator sGen, |
|
BerSequenceGenerator sigGen, |
|
BerSequenceGenerator eiGen) |
|
{ |
|
this.outer = outer; |
|
|
|
_out = outStream; |
|
_contentOID = new DerObjectIdentifier(contentOID); |
|
_sGen = sGen; |
|
_sigGen = sigGen; |
|
_eiGen = eiGen; |
|
} |
|
|
|
public override void WriteByte( |
|
byte b) |
|
{ |
|
_out.WriteByte(b); |
|
} |
|
|
|
public override void Write( |
|
byte[] bytes, |
|
int off, |
|
int len) |
|
{ |
|
_out.Write(bytes, off, len); |
|
} |
|
|
|
#if PORTABLE || NETFX_CORE |
|
protected override void Dispose(bool disposing) |
|
{ |
|
if (disposing) |
|
{ |
|
DoClose(); |
|
} |
|
base.Dispose(disposing); |
|
} |
|
#else |
|
public override void Close() |
|
{ |
|
DoClose(); |
|
base.Close(); |
|
} |
|
#endif |
|
|
|
private void DoClose() |
|
{ |
|
BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.Dispose(_out); |
|
|
|
// TODO Parent context(s) should really be be closed explicitly |
|
|
|
_eiGen.Close(); |
|
|
|
outer._digests.Clear(); // clear the current preserved digest state |
|
|
|
if (outer._certs.Count > 0) |
|
{ |
|
Asn1Set certs = outer.UseDerForCerts |
|
? CmsUtilities.CreateDerSetFromList(outer._certs) |
|
: CmsUtilities.CreateBerSetFromList(outer._certs); |
|
|
|
WriteToGenerator(_sigGen, new BerTaggedObject(false, 0, certs)); |
|
} |
|
|
|
if (outer._crls.Count > 0) |
|
{ |
|
Asn1Set crls = outer.UseDerForCrls |
|
? CmsUtilities.CreateDerSetFromList(outer._crls) |
|
: CmsUtilities.CreateBerSetFromList(outer._crls); |
|
|
|
WriteToGenerator(_sigGen, new BerTaggedObject(false, 1, crls)); |
|
} |
|
|
|
// |
|
// Calculate the digest hashes |
|
// |
|
foreach (DictionaryEntry de in outer._messageDigests) |
|
{ |
|
outer._messageHashes.Add(de.Key, DigestUtilities.DoFinal((IDigest)de.Value)); |
|
} |
|
|
|
// TODO If the digest OIDs for precalculated signers weren't mixed in with |
|
// the others, we could fill in outer._digests here, instead of SignerInfoGenerator.Generate |
|
|
|
// |
|
// collect all the SignerInfo objects |
|
// |
|
Asn1EncodableVector signerInfos = new Asn1EncodableVector(); |
|
|
|
// |
|
// add the generated SignerInfo objects |
|
// |
|
{ |
|
foreach (DigestAndSignerInfoGeneratorHolder holder in outer._signerInfs) |
|
{ |
|
AlgorithmIdentifier digestAlgorithm = holder.DigestAlgorithm; |
|
|
|
byte[] calculatedDigest = (byte[])outer._messageHashes[ |
|
Helper.GetDigestAlgName(holder.digestOID)]; |
|
outer._digests[holder.digestOID] = calculatedDigest.Clone(); |
|
|
|
signerInfos.Add(holder.signerInf.Generate(_contentOID, digestAlgorithm, calculatedDigest)); |
|
} |
|
} |
|
|
|
// |
|
// add the precalculated SignerInfo objects. |
|
// |
|
{ |
|
foreach (SignerInformation signer in outer._signers) |
|
{ |
|
// TODO Verify the content type and calculated digest match the precalculated SignerInfo |
|
// if (!signer.ContentType.Equals(_contentOID)) |
|
// { |
|
// // TODO The precalculated content type did not match - error? |
|
// } |
|
// |
|
// byte[] calculatedDigest = (byte[])outer._digests[signer.DigestAlgOid]; |
|
// if (calculatedDigest == null) |
|
// { |
|
// // TODO We can't confirm this digest because we didn't calculate it - error? |
|
// } |
|
// else |
|
// { |
|
// if (!Arrays.AreEqual(signer.GetContentDigest(), calculatedDigest)) |
|
// { |
|
// // TODO The precalculated digest did not match - error? |
|
// } |
|
// } |
|
|
|
signerInfos.Add(signer.ToSignerInfo()); |
|
} |
|
} |
|
|
|
WriteToGenerator(_sigGen, new DerSet(signerInfos)); |
|
|
|
_sigGen.Close(); |
|
_sGen.Close(); |
|
} |
|
|
|
private static void WriteToGenerator( |
|
Asn1Generator ag, |
|
Asn1Encodable ae) |
|
{ |
|
byte[] encoded = ae.GetEncoded(); |
|
ag.GetRawOutputStream().Write(encoded, 0, encoded.Length); |
|
} |
|
} |
|
} |
|
} |
|
#pragma warning restore |
|
#endif
|
|
|