632 lines
16 KiB
C#
632 lines
16 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
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.Engines;
|
|
using Org.BouncyCastle.Security;
|
|
using Org.BouncyCastle.Utilities;
|
|
using Org.BouncyCastle.X509;
|
|
|
|
namespace Org.BouncyCastle.Cms
|
|
{
|
|
/**
|
|
* an expanded SignerInfo block from a CMS Signed message
|
|
*/
|
|
public class SignerInformation
|
|
{
|
|
private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance;
|
|
|
|
private SignerID sid;
|
|
private SignerInfo info;
|
|
private AlgorithmIdentifier digestAlgorithm;
|
|
private AlgorithmIdentifier encryptionAlgorithm;
|
|
private Asn1Set signedAttributes;
|
|
private Asn1Set unsignedAttributes;
|
|
private CmsProcessable content;
|
|
private byte[] signature;
|
|
private DerObjectIdentifier contentType;
|
|
private IDigestCalculator digestCalculator;
|
|
private byte[] resultDigest;
|
|
|
|
internal SignerInformation(
|
|
SignerInfo info,
|
|
DerObjectIdentifier contentType,
|
|
CmsProcessable content,
|
|
IDigestCalculator digestCalculator)
|
|
{
|
|
this.info = info;
|
|
this.sid = new SignerID();
|
|
this.contentType = contentType;
|
|
|
|
try
|
|
{
|
|
SignerIdentifier s = info.SignerID;
|
|
|
|
if (s.IsTagged)
|
|
{
|
|
Asn1OctetString octs = Asn1OctetString.GetInstance(s.ID);
|
|
|
|
sid.SubjectKeyIdentifier = octs.GetOctets();
|
|
}
|
|
else
|
|
{
|
|
Asn1.Cms.IssuerAndSerialNumber iAnds =
|
|
Asn1.Cms.IssuerAndSerialNumber.GetInstance(s.ID);
|
|
|
|
sid.Issuer = iAnds.Name;
|
|
sid.SerialNumber = iAnds.SerialNumber.Value;
|
|
}
|
|
}
|
|
catch (IOException)
|
|
{
|
|
throw new ArgumentException("invalid sid in SignerInfo");
|
|
}
|
|
|
|
this.digestAlgorithm = info.DigestAlgorithm;
|
|
this.signedAttributes = info.AuthenticatedAttributes;
|
|
this.unsignedAttributes = info.UnauthenticatedAttributes;
|
|
this.encryptionAlgorithm = info.DigestEncryptionAlgorithm;
|
|
this.signature = info.EncryptedDigest.GetOctets();
|
|
|
|
this.content = content;
|
|
this.digestCalculator = digestCalculator;
|
|
}
|
|
|
|
public SignerID SignerID
|
|
{
|
|
get { return sid; }
|
|
}
|
|
|
|
/**
|
|
* return the version number for this objects underlying SignerInfo structure.
|
|
*/
|
|
public int Version
|
|
{
|
|
get { return info.Version.Value.IntValue; }
|
|
}
|
|
|
|
public AlgorithmIdentifier DigestAlgorithmID
|
|
{
|
|
get { return digestAlgorithm; }
|
|
}
|
|
|
|
/**
|
|
* return the object identifier for the signature.
|
|
*/
|
|
public string DigestAlgOid
|
|
{
|
|
get { return digestAlgorithm.ObjectID.Id; }
|
|
}
|
|
|
|
/**
|
|
* return the signature parameters, or null if there aren't any.
|
|
*/
|
|
public Asn1Object DigestAlgParams
|
|
{
|
|
get
|
|
{
|
|
Asn1Encodable ae = digestAlgorithm.Parameters;
|
|
|
|
return ae == null ? null : ae.ToAsn1Object();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* return the content digest that was calculated during verification.
|
|
*/
|
|
public byte[] GetContentDigest()
|
|
{
|
|
if (resultDigest == null)
|
|
{
|
|
throw new InvalidOperationException("method can only be called after verify.");
|
|
}
|
|
|
|
return (byte[])resultDigest.Clone();
|
|
}
|
|
|
|
public AlgorithmIdentifier EncryptionAlgorithmID
|
|
{
|
|
get { return encryptionAlgorithm; }
|
|
}
|
|
|
|
/**
|
|
* return the object identifier for the signature.
|
|
*/
|
|
public string EncryptionAlgOid
|
|
{
|
|
get { return encryptionAlgorithm.ObjectID.Id; }
|
|
}
|
|
|
|
/**
|
|
* return the signature/encryption algorithm parameters, or null if
|
|
* there aren't any.
|
|
*/
|
|
public Asn1Object EncryptionAlgParams
|
|
{
|
|
get
|
|
{
|
|
Asn1Encodable ae = encryptionAlgorithm.Parameters;
|
|
|
|
return ae == null ? null : ae.ToAsn1Object();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* return a table of the signed attributes - indexed by
|
|
* the OID of the attribute.
|
|
*/
|
|
public Asn1.Cms.AttributeTable SignedAttributes
|
|
{
|
|
get
|
|
{
|
|
return signedAttributes == null
|
|
? null
|
|
: new Asn1.Cms.AttributeTable(signedAttributes);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* return a table of the unsigned attributes indexed by
|
|
* the OID of the attribute.
|
|
*/
|
|
public Asn1.Cms.AttributeTable UnsignedAttributes
|
|
{
|
|
get
|
|
{
|
|
return unsignedAttributes == null
|
|
? null
|
|
: new Asn1.Cms.AttributeTable(unsignedAttributes);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* return the encoded signature
|
|
*/
|
|
public byte[] GetSignature()
|
|
{
|
|
return (byte[]) signature.Clone();
|
|
}
|
|
|
|
/**
|
|
* Return a SignerInformationStore containing the counter signatures attached to this
|
|
* signer. If no counter signatures are present an empty store is returned.
|
|
*/
|
|
public SignerInformationStore GetCounterSignatures()
|
|
{
|
|
Asn1.Cms.AttributeTable unsignedAttributeTable = UnsignedAttributes;
|
|
if (unsignedAttributeTable == null)
|
|
{
|
|
return new SignerInformationStore(new ArrayList(0));
|
|
}
|
|
|
|
IList counterSignatures = new ArrayList();
|
|
|
|
Asn1.Cms.Attribute counterSignatureAttribute = unsignedAttributeTable[CmsAttributes.CounterSignature];
|
|
if (counterSignatureAttribute != null)
|
|
{
|
|
Asn1Set values = counterSignatureAttribute.AttrValues;
|
|
counterSignatures = new ArrayList(values.Count);
|
|
|
|
foreach (Asn1Encodable asn1Obj in values)
|
|
{
|
|
SignerInfo si = SignerInfo.GetInstance(asn1Obj.ToAsn1Object());
|
|
|
|
string digestName = CmsSignedHelper.Instance.GetDigestAlgName(si.DigestAlgorithm.ObjectID.Id);
|
|
|
|
counterSignatures.Add(new SignerInformation(si, CmsAttributes.CounterSignature, null, new CounterSignatureDigestCalculator(digestName, GetSignature())));
|
|
}
|
|
}
|
|
|
|
return new SignerInformationStore(counterSignatures);
|
|
}
|
|
|
|
/**
|
|
* return the DER encoding of the signed attributes.
|
|
* @throws IOException if an encoding error occurs.
|
|
*/
|
|
public byte[] GetEncodedSignedAttributes()
|
|
{
|
|
return signedAttributes == null
|
|
? null
|
|
: signedAttributes.GetEncoded(Asn1Encodable.Der);
|
|
}
|
|
|
|
private bool DoVerify(
|
|
AsymmetricKeyParameter key,
|
|
Asn1.Cms.AttributeTable signedAttrTable)
|
|
{
|
|
string digestName = Helper.GetDigestAlgName(this.DigestAlgOid);
|
|
IDigest digest = Helper.GetDigestInstance(digestName);
|
|
|
|
DerObjectIdentifier sigAlgOid = this.encryptionAlgorithm.ObjectID;
|
|
Asn1Encodable sigParams = this.encryptionAlgorithm.Parameters;
|
|
ISigner sig;
|
|
|
|
if (sigAlgOid.Equals(Asn1.Pkcs.PkcsObjectIdentifiers.IdRsassaPss))
|
|
{
|
|
// RFC 4056
|
|
// When the id-RSASSA-PSS algorithm identifier is used for a signature,
|
|
// the AlgorithmIdentifier parameters field MUST contain RSASSA-PSS-params.
|
|
if (sigParams == null)
|
|
throw new CmsException("RSASSA-PSS signature must specify algorithm parameters");
|
|
|
|
try
|
|
{
|
|
// TODO Provide abstract configuration mechanism
|
|
|
|
Asn1.Pkcs.RsassaPssParameters pss = Asn1.Pkcs.RsassaPssParameters.GetInstance(
|
|
sigParams.ToAsn1Object());
|
|
|
|
if (!pss.HashAlgorithm.ObjectID.Equals(this.digestAlgorithm.ObjectID))
|
|
throw new CmsException("RSASSA-PSS signature parameters specified incorrect hash algorithm");
|
|
if (!pss.MaskGenAlgorithm.ObjectID.Equals(Asn1.Pkcs.PkcsObjectIdentifiers.IdMgf1))
|
|
throw new CmsException("RSASSA-PSS signature parameters specified unknown MGF");
|
|
|
|
IDigest pssDigest = DigestUtilities.GetDigest(pss.HashAlgorithm.ObjectID);
|
|
int saltLen = pss.SaltLength.Value.IntValue;
|
|
byte trailer = (byte) pss.TrailerField.Value.IntValue;
|
|
|
|
sig = new Crypto.Signers.PssSigner(new RsaBlindedEngine(), pssDigest, saltLen, trailer);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new CmsException("failed to set RSASSA-PSS signature parameters", e);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO Probably too strong a check at the moment
|
|
// if (sigParams != null)
|
|
// throw new CmsException("unrecognised signature parameters provided");
|
|
|
|
string signatureName = digestName + "with" + Helper.GetEncryptionAlgName(this.EncryptionAlgOid);
|
|
|
|
sig = Helper.GetSignatureInstance(signatureName);
|
|
}
|
|
|
|
try
|
|
{
|
|
sig.Init(false, key);
|
|
|
|
if (signedAttributes == null)
|
|
{
|
|
if (content != null)
|
|
{
|
|
content.Write(new CmsSignedDataGenerator.SigOutputStream(sig));
|
|
content.Write(new CmsSignedDataGenerator.DigOutputStream(digest));
|
|
|
|
resultDigest = DigestUtilities.DoFinal(digest);
|
|
}
|
|
else
|
|
{
|
|
resultDigest = digestCalculator.GetDigest();
|
|
|
|
// need to decrypt signature and check message bytes
|
|
return VerifyDigest(resultDigest, key, this.GetSignature());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
byte[] hash;
|
|
if (content != null)
|
|
{
|
|
content.Write(
|
|
new CmsSignedDataGenerator.DigOutputStream(digest));
|
|
|
|
hash = DigestUtilities.DoFinal(digest);
|
|
}
|
|
else if (digestCalculator != null)
|
|
{
|
|
hash = digestCalculator.GetDigest();
|
|
}
|
|
else
|
|
{
|
|
hash = null;
|
|
}
|
|
|
|
resultDigest = hash;
|
|
|
|
Asn1.Cms.Attribute dig = signedAttrTable[Asn1.Cms.CmsAttributes.MessageDigest];
|
|
Asn1.Cms.Attribute type = signedAttrTable[Asn1.Cms.CmsAttributes.ContentType];
|
|
|
|
if (dig == null)
|
|
{
|
|
throw new SignatureException("no hash for content found in signed attributes");
|
|
}
|
|
|
|
if (type == null && !contentType.Equals(CmsAttributes.CounterSignature))
|
|
{
|
|
throw new SignatureException("no content type id found in signed attributes");
|
|
}
|
|
|
|
Asn1Object hashObj = dig.AttrValues[0].ToAsn1Object();
|
|
|
|
if (hashObj is Asn1OctetString)
|
|
{
|
|
byte[] signedHash = ((Asn1OctetString)hashObj).GetOctets();
|
|
|
|
if (!Arrays.AreEqual(hash, signedHash))
|
|
{
|
|
throw new SignatureException("content hash found in signed attributes different");
|
|
}
|
|
}
|
|
else if (hashObj is DerNull)
|
|
{
|
|
if (hash != null)
|
|
{
|
|
throw new SignatureException("NULL hash found in signed attributes when one expected");
|
|
}
|
|
}
|
|
|
|
if (type != null)
|
|
{
|
|
DerObjectIdentifier typeOID = (DerObjectIdentifier)type.AttrValues[0];
|
|
|
|
if (!typeOID.Equals(contentType))
|
|
{
|
|
throw new SignatureException("contentType in signed attributes different");
|
|
}
|
|
}
|
|
|
|
byte[] tmp = this.GetEncodedSignedAttributes();
|
|
sig.BlockUpdate(tmp, 0, tmp.Length);
|
|
}
|
|
|
|
return sig.VerifySignature(this.GetSignature());
|
|
}
|
|
catch (InvalidKeyException e)
|
|
{
|
|
throw new CmsException(
|
|
"key not appropriate to signature in message.", e);
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
throw new CmsException(
|
|
"can't process mime object to create signature.", e);
|
|
}
|
|
catch (SignatureException e)
|
|
{
|
|
throw new CmsException(
|
|
"invalid signature format in message: " + e.Message, e);
|
|
}
|
|
}
|
|
|
|
private bool IsNull(
|
|
Asn1Encodable o)
|
|
{
|
|
return (o is Asn1Null) || (o == null);
|
|
}
|
|
|
|
private DigestInfo DerDecode(
|
|
byte[] encoding)
|
|
{
|
|
if (encoding[0] != (int)(Asn1Tags.Constructed | Asn1Tags.Sequence))
|
|
{
|
|
throw new IOException("not a digest info object");
|
|
}
|
|
|
|
DigestInfo digInfo = DigestInfo.GetInstance(Asn1Object.FromByteArray(encoding));
|
|
|
|
// length check to avoid Bleichenbacher vulnerability
|
|
|
|
if (digInfo.GetEncoded().Length != encoding.Length)
|
|
{
|
|
throw new CmsException("malformed RSA signature");
|
|
}
|
|
|
|
return digInfo;
|
|
}
|
|
|
|
private bool VerifyDigest(
|
|
byte[] digest,
|
|
AsymmetricKeyParameter key,
|
|
byte[] signature)
|
|
{
|
|
string algorithm = Helper.GetEncryptionAlgName(this.EncryptionAlgOid);
|
|
|
|
try
|
|
{
|
|
if (algorithm.Equals("RSA"))
|
|
{
|
|
IBufferedCipher c = CipherUtilities.GetCipher("RSA//PKCS1Padding");
|
|
|
|
c.Init(false, key);
|
|
|
|
byte[] decrypt = c.DoFinal(signature);
|
|
|
|
DigestInfo digInfo = DerDecode(decrypt);
|
|
|
|
if (!digInfo.AlgorithmID.ObjectID.Equals(digestAlgorithm.ObjectID))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!IsNull(digInfo.AlgorithmID.Parameters))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
byte[] sigHash = digInfo.GetDigest();
|
|
|
|
return Arrays.AreEqual(digest, sigHash);
|
|
}
|
|
else if (algorithm.Equals("DSA"))
|
|
{
|
|
ISigner sig = SignerUtilities.GetSigner("NONEwithDSA");
|
|
|
|
sig.Init(false, key);
|
|
|
|
sig.BlockUpdate(digest, 0, digest.Length);
|
|
|
|
return sig.VerifySignature(signature);
|
|
}
|
|
else
|
|
{
|
|
throw new CmsException("algorithm: " + algorithm + " not supported in base signatures.");
|
|
}
|
|
}
|
|
catch (SecurityUtilityException e)
|
|
{
|
|
throw e;
|
|
}
|
|
catch (GeneralSecurityException e)
|
|
{
|
|
throw new CmsException("Exception processing signature: " + e, e);
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
throw new CmsException("Exception decoding signature: " + e, e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* verify that the given public key succesfully handles and confirms the
|
|
* signature associated with this signer.
|
|
*/
|
|
public bool Verify(
|
|
AsymmetricKeyParameter pubKey)
|
|
{
|
|
if (pubKey.IsPrivate)
|
|
throw new ArgumentException("Expected public key", "pubKey");
|
|
|
|
return DoVerify(pubKey, this.SignedAttributes);
|
|
}
|
|
|
|
/**
|
|
* verify that the given certificate successfully handles and confirms
|
|
* the signature associated with this signer and, if a signingTime
|
|
* attribute is available, that the certificate was valid at the time the
|
|
* signature was generated.
|
|
*/
|
|
public bool Verify(
|
|
X509Certificate cert)
|
|
{
|
|
Asn1.Cms.AttributeTable attr = this.SignedAttributes;
|
|
|
|
if (attr != null)
|
|
{
|
|
Asn1EncodableVector v = attr.GetAll(CmsAttributes.SigningTime);
|
|
switch (v.Count)
|
|
{
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
{
|
|
Asn1.Cms.Attribute t = (Asn1.Cms.Attribute) v[0];
|
|
Debug.Assert(t != null);
|
|
|
|
Asn1Set attrValues = t.AttrValues;
|
|
if (attrValues.Count != 1)
|
|
throw new CmsException("A signing-time attribute MUST have a single attribute value");
|
|
|
|
Asn1.Cms.Time time = Asn1.Cms.Time.GetInstance(attrValues[0].ToAsn1Object());
|
|
|
|
cert.CheckValidity(time.Date);
|
|
break;
|
|
}
|
|
default:
|
|
throw new CmsException("The SignedAttributes in a signerInfo MUST NOT include multiple instances of the signing-time attribute");
|
|
}
|
|
}
|
|
|
|
return DoVerify(cert.GetPublicKey(), attr);
|
|
}
|
|
|
|
/**
|
|
* Return the base ASN.1 CMS structure that this object contains.
|
|
*
|
|
* @return an object containing a CMS SignerInfo structure.
|
|
*/
|
|
public SignerInfo ToSignerInfo()
|
|
{
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* Return a signer information object with the passed in unsigned
|
|
* attributes replacing the ones that are current associated with
|
|
* the object passed in.
|
|
*
|
|
* @param signerInformation the signerInfo to be used as the basis.
|
|
* @param unsignedAttributes the unsigned attributes to add.
|
|
* @return a copy of the original SignerInformationObject with the changed attributes.
|
|
*/
|
|
public static SignerInformation ReplaceUnsignedAttributes(
|
|
SignerInformation signerInformation,
|
|
Asn1.Cms.AttributeTable unsignedAttributes)
|
|
{
|
|
SignerInfo sInfo = signerInformation.info;
|
|
Asn1Set unsignedAttr = null;
|
|
|
|
if (unsignedAttributes != null)
|
|
{
|
|
unsignedAttr = new DerSet(unsignedAttributes.ToAsn1EncodableVector());
|
|
}
|
|
|
|
return new SignerInformation(
|
|
new SignerInfo(
|
|
sInfo.SignerID,
|
|
sInfo.DigestAlgorithm,
|
|
sInfo.AuthenticatedAttributes,
|
|
sInfo.DigestEncryptionAlgorithm,
|
|
sInfo.EncryptedDigest,
|
|
unsignedAttr),
|
|
signerInformation.contentType,
|
|
signerInformation.content,
|
|
null);
|
|
}
|
|
|
|
/**
|
|
* Return a signer information object with passed in SignerInformationStore representing counter
|
|
* signatures attached as an unsigned attribute.
|
|
*
|
|
* @param signerInformation the signerInfo to be used as the basis.
|
|
* @param counterSigners signer info objects carrying counter signature.
|
|
* @return a copy of the original SignerInformationObject with the changed attributes.
|
|
*/
|
|
public static SignerInformation AddCounterSigners(
|
|
SignerInformation signerInformation,
|
|
SignerInformationStore counterSigners)
|
|
{
|
|
SignerInfo sInfo = signerInformation.info;
|
|
Asn1.Cms.AttributeTable unsignedAttr = signerInformation.UnsignedAttributes;
|
|
Asn1EncodableVector v;
|
|
|
|
if (unsignedAttr != null)
|
|
{
|
|
v = unsignedAttr.ToAsn1EncodableVector();
|
|
}
|
|
else
|
|
{
|
|
v = new Asn1EncodableVector();
|
|
}
|
|
|
|
Asn1EncodableVector sigs = new Asn1EncodableVector();
|
|
|
|
foreach (SignerInformation sigInf in counterSigners.GetSigners())
|
|
{
|
|
sigs.Add(sigInf.ToSignerInfo());
|
|
}
|
|
|
|
v.Add(new Asn1.Cms.Attribute(CmsAttributes.CounterSignature, new DerSet(sigs)));
|
|
|
|
return new SignerInformation(
|
|
new SignerInfo(
|
|
sInfo.SignerID,
|
|
sInfo.DigestAlgorithm,
|
|
sInfo.AuthenticatedAttributes,
|
|
sInfo.DigestEncryptionAlgorithm,
|
|
sInfo.EncryptedDigest,
|
|
new DerSet(v)),
|
|
signerInformation.contentType,
|
|
signerInformation.content,
|
|
null);
|
|
}
|
|
}
|
|
}
|