How to: Sign a Message by Multiple Signers 

This example creates a CMS/PKCS #7 signed message by using System.Security.Cryptography.Pkcs. The message is signed by multiple signers. The signatures on that message are then verified.

Example

This example uses the following classes:

The following example begins by using a previously generated encoded SignedCms message (signed by a signer with the subject name "MessageSigner1"), and then signs it a second time by a signer with the subject name "MessageSigner2". This example requires that a public key certificate with the subject name "MessageSigner2" be contained in the My certificate store, and that is has an associated private key.

NoteNote

This example is only for illustrative purposes. Production environments might use a different model in which the sender and the recipient of the message execute in different processes with their unique public key credentials.

Set up this example by using the Makecert.exe utility, one of several ways to do so. Certificate Creation Tool (Makecert.exe) is a convenient utility for generating test certificates. In a production environment, certificates are generated by a certification authority.

The following Makecert command generates the required public key certificates and private keys.

Makecert -n "CN=MessageSigner2" -ss My

// Copyright (c) Microsoft Corporation.  All rights reserved.
#region Using directives

using System;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Text;

#endregion

namespace SigningAMessageByMultipleSigners
{
    class SignedCmsMultipleSigners
    {
        //  Subject name of the second signer of the message.
        const String signerName = "MessageSigner2";
     
        static void Main(string[] args)
        {
            //  An encoded SignedCms message that was generated
            //  previously. It is the message "The Board of Directors
            //  hereby adopts the resolution." signed by one signer
            //  with the subject name "MessageSigner1". This example
            //  adds a second signature to this encoded
            //  SignedCms message.
            Byte[] signedMessage = {
            0x30, 0x82, 0x03, 0x34, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
            0xf7, 0x0d, 0x01, 0x07, 0x02, 0xa0, 0x82, 0x03, 0x25, 0x30,
            0x82, 0x03, 0x21, 0x02, 0x01, 0x01, 0x31, 0x0b, 0x30, 0x09,
            0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x30,
            0x77, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
            0x07, 0x01, 0xa0, 0x6a, 0x04, 0x68, 0x54, 0x00, 0x68, 0x00,
            0x65, 0x00, 0x20, 0x00, 0x42, 0x00, 0x6f, 0x00, 0x61, 0x00,
            0x72, 0x00, 0x64, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00,
            0x20, 0x00, 0x44, 0x00, 0x69, 0x00, 0x72, 0x00, 0x65, 0x00,
            0x63, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x73, 0x00,
            0x20, 0x00, 0x68, 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00,
            0x62, 0x00, 0x79, 0x00, 0x20, 0x00, 0x61, 0x00, 0x64, 0x00,
            0x6f, 0x00, 0x70, 0x00, 0x74, 0x00, 0x73, 0x00, 0x20, 0x00,
            0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x72, 0x00,
            0x65, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x75, 0x00,
            0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2e, 0x00,
            0xa0, 0x82, 0x01, 0xc2, 0x30, 0x82, 0x01, 0xbe, 0x30, 0x82,
            0x01, 0x68, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x04,
            0xd3, 0xc9, 0xdd, 0xb4, 0x38, 0x2a, 0xb5, 0x4d, 0x43, 0x4b,
            0x99, 0x65, 0x19, 0xab, 0xeb, 0x30, 0x0d, 0x06, 0x09, 0x2a,
            0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04, 0x05, 0x00,
            0x30, 0x16, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04,
            0x03, 0x13, 0x0b, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x41, 0x67,
            0x65, 0x6e, 0x63, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x33,
            0x31, 0x32, 0x32, 0x36, 0x32, 0x32, 0x34, 0x35, 0x33, 0x37,
            0x5a, 0x17, 0x0d, 0x33, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32,
            0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x19, 0x31, 0x17,
            0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0e, 0x4d,
            0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x69, 0x67, 0x6e,
            0x65, 0x72, 0x31, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09,
            0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
            0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81,
            0x81, 0x00, 0x9b, 0xb4, 0x92, 0x23, 0x35, 0x3f, 0x23, 0xec,
            0x4b, 0xcf, 0x4d, 0x5b, 0xed, 0x81, 0x22, 0x45, 0x62, 0x97,
            0xea, 0x38, 0xff, 0x32, 0xc6, 0xa0, 0xdd, 0xeb, 0xd1, 0x18,
            0x6a, 0x30, 0xec, 0x6e, 0x4b, 0x4f, 0xab, 0x2a, 0x41, 0xc7,
            0x0d, 0xbb, 0xcd, 0x80, 0xdc, 0xef, 0xf2, 0xd0, 0x00, 0xd6,
            0x82, 0x81, 0x7f, 0x9a, 0x9c, 0xc9, 0x41, 0xf3, 0xa8, 0x0b,
            0xa3, 0x9d, 0xed, 0x9a, 0xee, 0x23, 0xb8, 0xf0, 0xe6, 0x27,
            0x65, 0x30, 0x10, 0x13, 0x65, 0x75, 0x33, 0x64, 0x0b, 0x0b,
            0xea, 0x7f, 0xf8, 0x3b, 0x49, 0xa7, 0xea, 0xd0, 0x2d, 0xc1,
            0xf8, 0xa1, 0x66, 0xb9, 0x6d, 0xa2, 0x8d, 0x36, 0x43, 0x2e,
            0xe1, 0x91, 0xe2, 0x41, 0xa1, 0xe6, 0x80, 0xc4, 0xa5, 0xf6,
            0x1a, 0xa4, 0x1e, 0x1a, 0x47, 0x3e, 0x5e, 0xf1, 0x97, 0xc9,
            0x26, 0x6a, 0x0c, 0xf1, 0x0f, 0xcb, 0x55, 0x03, 0xb2, 0xb7,
            0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x4b, 0x30, 0x49, 0x30,
            0x47, 0x06, 0x03, 0x55, 0x1d, 0x01, 0x04, 0x40, 0x30, 0x3e,
            0x80, 0x10, 0x12, 0xe4, 0x09, 0x2d, 0x06, 0x1d, 0x1d, 0x4f,
            0x00, 0x8d, 0x61, 0x21, 0xdc, 0x16, 0x64, 0x63, 0xa1, 0x18,
            0x30, 0x16, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04,
            0x03, 0x13, 0x0b, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x41, 0x67,
            0x65, 0x6e, 0x63, 0x79, 0x82, 0x10, 0x06, 0x37, 0x6c, 0x00,
            0xaa, 0x00, 0x64, 0x8a, 0x11, 0xcf, 0xb8, 0xd4, 0xaa, 0x5c,
            0x35, 0xf4, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
            0xf7, 0x0d, 0x01, 0x01, 0x04, 0x05, 0x00, 0x03, 0x41, 0x00,
            0x1c, 0x97, 0xe5, 0x69, 0xac, 0x34, 0xa5, 0xa0, 0xbb, 0xc5,
            0x65, 0x2e, 0xdf, 0x14, 0xa8, 0x8d, 0x4e, 0xf1, 0x86, 0x6c,
            0x05, 0x5f, 0x51, 0xf3, 0xcc, 0x09, 0x8e, 0xaa, 0xa9, 0x43,
            0x85, 0x11, 0x3b, 0xa9, 0xc3, 0x7d, 0x46, 0x58, 0x6b, 0xae,
            0xf5, 0x6b, 0xd4, 0xef, 0xdf, 0xa5, 0x0f, 0xdb, 0x37, 0x78,
            0xfd, 0x79, 0xf3, 0x31, 0x61, 0x26, 0x44, 0x98, 0x8b, 0xa4,
            0xab, 0x3a, 0x89, 0x6e, 0x31, 0x81, 0xcf, 0x30, 0x81, 0xcc,
            0x02, 0x01, 0x01, 0x30, 0x2a, 0x30, 0x16, 0x31, 0x14, 0x30,
            0x12, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0b, 0x52, 0x6f,
            0x6f, 0x74, 0x20, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79, 0x02,
            0x10, 0x04, 0xd3, 0xc9, 0xdd, 0xb4, 0x38, 0x2a, 0xb5, 0x4d,
            0x43, 0x4b, 0x99, 0x65, 0x19, 0xab, 0xeb, 0x30, 0x09, 0x06,
            0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x30, 0x0d,
            0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
            0x01, 0x05, 0x00, 0x04, 0x81, 0x80, 0x4f, 0x71, 0x32, 0xc8,
            0x5f, 0x2f, 0xe3, 0x8e, 0xc7, 0x8d, 0x85, 0x96, 0x28, 0x1d,
            0x6e, 0xa6, 0x6e, 0x76, 0x63, 0x64, 0xae, 0x8d, 0xdc, 0x06,
            0x64, 0x14, 0xeb, 0xcf, 0xcb, 0x2a, 0xd2, 0x17, 0xaa, 0xa5,
            0x24, 0xd9, 0x17, 0x05, 0x07, 0x35, 0x8e, 0xa0, 0xce, 0x48,
            0xed, 0x4b, 0x9d, 0xe4, 0x6c, 0xfa, 0xdc, 0x00, 0x82, 0x15,
            0x6b, 0xde, 0x29, 0x90, 0x28, 0xe9, 0x53, 0xcb, 0x6b, 0xb7,
            0xac, 0xa0, 0xf5, 0xd7, 0x75, 0x92, 0x89, 0x5e, 0x3d, 0x71,
            0x46, 0x0c, 0x38, 0xee, 0xfb, 0x51, 0xfc, 0x4b, 0x7e, 0x70,
            0x87, 0xde, 0x09, 0x8f, 0x0d, 0x63, 0x27, 0x26, 0x81, 0x43,
            0xce, 0xcb, 0x03, 0x44, 0xe9, 0x70, 0xf5, 0xf0, 0x80, 0xcd,
            0xe1, 0x89, 0x2b, 0xd8, 0x84, 0xd2, 0xc4, 0x90, 0xef, 0xbc,
            0xad, 0x31, 0x2a, 0x3a, 0x29, 0xa0, 0xb0, 0x84, 0x9f, 0x65,
            0x5b, 0x0d, 0x6f, 0x61};
     
            Console.WriteLine("System.Security.Cryptography.Pkcs " +
                "Sample: Multiple-signer signed " +
                "and verified message");
     
            Console.WriteLine("\n\n------------------------------");
            Console.WriteLine("     SETUP OF CREDENTIALS     ");
            Console.WriteLine("------------------------------\n");
     
            //  Get the certificate of the additional message signer.
            X509Certificate2Collection signerCerts = GetSignerCerts();
     
            Console.WriteLine("\n\n----------------------");
            Console.WriteLine("     SENDER SIDE      ");
            Console.WriteLine("----------------------\n");
     
            //  Decode the existing SignedCms message and add a signature
            //  to it.
            byte[] encodedSignedCms = SignMsg(signedMessage, signerCerts);
     
            Console.WriteLine("\n\n------------------------");
            Console.WriteLine("     RECIPIENT SIDE     ");
            Console.WriteLine("------------------------\n");
     
            //  Verify all signatures in the message.
            if (VerifyMsg(encodedSignedCms))
            {
                Console.WriteLine("\nMessage verified");
            }
            else
            {
                Console.WriteLine("\nMessage failed to verify");
            }
        }
     
        //  Open the My (or Personal) certificate store and search for
        //  credentials to sign the message with. There must be
        //  a certificate with subject name "MessageSigner2" in that
        //  certificate store.
        static public X509Certificate2Collection GetSignerCerts()
        {
            //  Open the My certificate store.
            X509Store storeMy = new X509Store(StoreName.My,
                StoreLocation.CurrentUser);
            storeMy.Open(OpenFlags.ReadOnly);
     
            //  Display certificates to help troubleshoot 
            //  the example's setup.
            Console.WriteLine("Found certs with the following subject " +
                "names in the {0} store:", storeMy.Name.ToString());
            foreach (X509Certificate2 cert in storeMy.Certificates)
            {
                Console.WriteLine("\t{0}", cert.SubjectName.Name);
            }

            //  Find the signer's certificate.
            //  Add to the signers' certificate collection.
            X509Certificate2Collection signerCertsColl =
                storeMy.Certificates.Find(X509FindType.FindBySubjectName,
                signerName, false);
            Console.WriteLine(
                "Found {0} certificates in the {1} store with name {2}",
                signerCertsColl.Count, storeMy.Name, signerName);
     
            //  Check to see if the certificate suggested by the example
            //  requirements is not present.
            if (signerCertsColl.Count == 0)
            {
                Console.WriteLine(
                    "A suggested certificate to use for this example " +
                    "is not in the certificate store. Select " +
                    "an alternate certificate to use for " +
                    "signing the message.");
            }
     
            storeMy.Close();
     
            return signerCertsColl;
        }
     
        //  Sign the message with the private key of the signer.
        static public byte[] SignMsg(
            Byte[] signedCmsMsg,
            X509Certificate2Collection signerCerts)
        {
     
            //  Instantiate a SignedCms object, and then decode the
            //  input encoded SignedCms message.
            //  Has default SubjectIdentifierType IssuerAndSerialNumber.
            //  Has default Detached property value false, so message is
            //  included in the encoded SignedCms.
            SignedCms signedCms = new SignedCms();
            signedCms.Decode(signedCmsMsg);
            DisplaySignedCmsProperties("Original message", signedCms);
     
            //  Sign the PKCS #7/CMS message once for each
            //  signer certificate. In this example, one additional
            //  signer is added to the SignedCms object.
            foreach (X509Certificate2 cert in signerCerts)
            {
                Console.Write("Computing signature with signer subject "
                    + "name {0} ... ", cert.SubjectName.Name);
                signedCms.ComputeSignature(new CmsSigner(cert));
                Console.WriteLine("Done.");
            }
            DisplaySignedCmsProperties("Message with additional " +
                "signer", signedCms);
     
            //  Encode the PKCS #7/CMS message.
            return signedCms.Encode();
        }
     
        //  Verify the encoded SignedCms message and return a Boolean
        //  value that specifies whether the verification was successful.
        static public bool VerifyMsg(byte[] encodedSignedCms)
        {
            //  Prepare an object in which to decode and verify.
            SignedCms signedCms = new SignedCms();
     
            signedCms.Decode(encodedSignedCms);
     
            //  Catch a verification exception if you want to
            //  advise the message recipient that security actions might 
            //  be appropriate.
            try
            {
                //  Verify signature. Do not validate signer
                //  certificate chain for the purposes of this example.
                //  Note that in a production environment, validating
                //  the signer certificate chain will probably
                //  be necessary.
                Console.Write("Checking signatures on message ... ");
                signedCms.CheckSignature(true);
                Console.WriteLine("Done.");
            }
            catch (System.Security.Cryptography.CryptographicException e)
            {
                Console.WriteLine("VerifyMsg caught exception:  {0}",
                    e.Message);
                Console.WriteLine("Verification of the signed PKCS #7 " +
                    "failed. The message, signatures, or " +
                    "countersignatures might have been modified " +
                    "in transit or storage. The message signers or " +
                    "countersigners might not be who they claim to be. " +
                    "The message's authenticity or integrity, or both, " +
                    "are not guaranteed.");
                return false;
            }
     
            return true;
        }
     
        //  This method displays some properties of a signed 
        //  CMS/PKCS #7 message. These properties include the number of  
        //  signers and countersigners for each signer, whether the 
        //  message is detached, and the version of the message.
        static void DisplaySignedCmsProperties(String info, SignedCms s)
        {
            Console.WriteLine();
            Console.WriteLine("\n>>>>> SignedCms Signer Info: {0}", info);
            Console.WriteLine("\tNumber of signers:\t\t\t{0}",
                s.SignerInfos.Count);
            for (int i = 0; i < s.SignerInfos.Count; i++)
            {
                Console.WriteLine("\tSubject name of signer #{0}:\t\t{1}",
                    i + 1, s.SignerInfos[i].Certificate.SubjectName.Name);
                Console.WriteLine("\tNumber of countersigners for " +
                    "signer #{0}:\t{1}",
                    i + 1, s.SignerInfos[i].CounterSignerInfos.Count);
            }

            Console.WriteLine("\tMessage detached state:\t\t\t{0}",
                s.Detached);
            Console.WriteLine("\tMessage version:\t\t\t{0}", s.Version);
            Console.WriteLine();
        }
    }
}

See Also

Tasks

How to: Sign Messages by One Signer
How to: Countersign a Message
How to: Envelope a Message for One Recipient
How to: Envelope a Message for Multiple Recipients

Concepts

How to: Sign and Envelop a Message