Creating a C# SCEP server
<p>I am trying to build a SCEP server to support Apple MDM Device Enrollment. This needs to be implemented into our current MDM Service, written in C#. </p>

<p>I have looked into the following for inspiration:  </p>

<ol>
<li>JSCEP, a java library for scep server implementation <a href="https://github.com/jscep/jscep" rel="nofollow noreferrer">https://github.com/jscep/jscep</a></li>
<li>Bouncy Castle (complicated, and not a lot of documentation on the C# side)</li>
<li>Cisco SCEP documentation <a href="http://www.cisco.com/c/en/us/support/docs/security-vpn/public-key-infrastructure-pki/116167-technote-scep-00.html" rel="nofollow noreferrer">http://www.cisco.com/c/en/us/support/docs/security-vpn/public-key-infrastructure-pki/116167-technote-scep-00.html</a></li>
</ol>

<p>Does anyone know of any solid examples, on creating a C# SCEP server? I haven't been able to find any good documentation for this.</p>

<p><strong>UPDATE</strong></p>

<p>This is what i have by now, it is still not working, but i think the issue lies with the signed pkcs7, but I am not sure what i am missing?  </p>

<pre><code>    public class ScepModule: NancyModule
{
    /// &lt;summary&gt;
    /// The _log.
    /// &lt;/summary&gt;
    private readonly ILog log = LogManager.GetLogger(typeof(ScepModule));
    /// &lt;summary&gt;
    /// Initializes a new instance of the &lt;see cref="ScepModule"/&gt; class.
    /// &lt;/summary&gt;
    /// &lt;param name="cp"&gt;
    /// The certificate provider.
    /// &lt;/param&gt;
    /// &lt;param name="config"&gt;
    /// The config.
    /// &lt;/param&gt;
    public ScepModule(MdmConfigDTO config)
        : base("/cimdm/scep")
    {
        this.log.Debug(m =&gt; m("Instanciating scep Module."));
        this.Get["/"] = result =&gt;
        {
            var message = Request.Query["message"];
            var operation = Request.Query["operation"];
            if (operation == "GetCACert")
            {
                return RespondWithCACert();
            }
            else if (operation == "GetCACaps")
            {
                return RespondWithCACaps();
            }
            return "";
        };
        this.Post["/"] = result =&gt;
        {
            var message = Request.Query["message"];
            var operation = Request.Query["operation"];

            byte[] requestData = null;

            using (var binaryReader = new BinaryReader(Request.Body))
            {
                requestData = binaryReader.ReadBytes((int)Request.Body.Length);
            }

            var headers = Request.Headers;
            foreach (var header in headers)
            {
                this.log.Debug(m =&gt; m("Header: {0}, Value: {1}", header.Key, String.Join(",", header.Value)));
            }

            var caCert = getSignerCert();

            var signingCert = createSigningCert(caCert, requestData, config);

            return signingCert;
        };
        this.log.Debug(m =&gt; m("Finished Instanciating scep Module."));
    }


    private Response RespondWithCACert()
    {
        var caCert = getSignerCert();

        var response = new Response();
        response.ContentType = "application/x-x509-ca-cert";
        response.Contents = stream =&gt;
        {
            byte[] data = caCert.Export(X509ContentType.Cert);
            stream.Write(data, 0, data.Length);
            stream.Flush();
            stream.Close();
        };
        return response;
    }

    private Response RespondWithCACaps()
    {
        var response = new Response();
        response.ContentType = "text/plain; charset=ISO-8859-1";
        //byte[] data = Encoding.UTF8.GetBytes("POSTPKIOperation\nSHA-512\nSHA-256\nSHA-1");
        byte[] data = Encoding.UTF8.GetBytes("POSTPKIOperation\nSHA-1");

        response.Contents = stream =&gt;
        {
            stream.Write(data, 0, data.Length);
            stream.Flush();
            stream.Close();
        };
        return response;
    }

    private Response createSigningCert(X509Certificate2 caCert, byte[] data, MdmConfigDTO config)
    {
        var signedResponse = new SignedCms();
        signedResponse.Decode(data);

        var caChain = new X509Certificate2Collection(caCert);

        signedResponse.CheckSignature(caChain, true);

        var attributes = signedResponse
            .SignerInfos
            .Cast&lt;System.Security.Cryptography.Pkcs.SignerInfo&gt;()
            .SelectMany(si =&gt; si.SignedAttributes.Cast&lt;CryptographicAttributeObject&gt;());

        // Any errors then return null
        if (attributes.Any(att =&gt; att.Oid.Value.Equals(Oids.Scep.FailInfo)))
        {
            return null;
        }

        byte[] msg = DecryptMsg(signedResponse.ContentInfo.Content, caChain);
        byte[] certResult = GenerateSelfSignedClientCertificate(msg, caCert, config);
        X509Certificate2Collection reqCerts = signedResponse.Certificates;

        //Create Enveloped PKCS#7 data
        var envelpeDataPkcs7 = createEnvelopedDataPkcs7(certResult);

        //Create Signed PKCS#7 data
        var signedDataPkcs7 = createSignedDataPkcs7(envelpeDataPkcs7, caCert, attributes);


        var response = new Response();
        response.ContentType = "application/x-pki-message";
        response.WithHeader("Cache-Control", "no-store, no-cache, must-revalidate");
        response.WithHeader("Pragma", "no-cache");

        var execPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
        File.WriteAllBytes(execPath + "\\signedDataPkcs7.p7b", signedDataPkcs7);
        File.WriteAllBytes(execPath + "\\envelpeDataPkcs7.p7b", envelpeDataPkcs7);

        response.Contents = stream =&gt;
        {
            stream.Write(signedDataPkcs7, 0, signedDataPkcs7.Length);
            stream.Flush();
            stream.Close();
        };
        return response;
    }

    private byte[] GenerateSelfSignedClientCertificate(byte[] encodedPkcs10, X509Certificate2 caCert, MdmConfigDTO config)
    {
        var execPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
        File.WriteAllText(execPath + "\\scepPkcs10.csr", "-----BEGIN CERTIFICATE REQUEST-----\n" + Convert.ToBase64String(encodedPkcs10) + "\n-----END CERTIFICATE REQUEST-----");

        Pkcs10CertificationRequest csr = new Pkcs10CertificationRequest(encodedPkcs10);

        CertificationRequestInfo csrInfo = csr.GetCertificationRequestInfo();
        SubjectPublicKeyInfo pki = csrInfo.SubjectPublicKeyInfo;

        Asn1Set attributes = csrInfo.Attributes;
        //pub key for the signed cert
        var publicKey = pki.GetPublicKey();

        // Build a Version3 Certificate
        DateTime startDate = DateTime.UtcNow.AddMonths(-1);
        DateTime expiryDate = startDate.AddYears(10);
        BigInteger serialNumber = new BigInteger(32, new Random());

        this.log.Debug(m =&gt; m("Certificate Signing Request Subject is: {0}", csrInfo.Subject));
        CmsSignedDataGenerator cmsGen = new CmsSignedDataGenerator();

        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
        string signerCN = caCert.GetNameInfo(X509NameType.SimpleName, true);

        certGen.SetSubjectDN(new X509Name(String.Format("CN={0}", config.HostName)));

        certGen.SetSerialNumber(serialNumber);
        certGen.SetIssuerDN(new X509Name(String.Format("CN={0}", signerCN)));
        certGen.SetNotBefore(startDate);
        certGen.SetNotAfter(expiryDate);
        certGen.SetSignatureAlgorithm("SHA1withRSA");
        certGen.SetPublicKey(PublicKeyFactory.CreateKey(pki));

        AsymmetricCipherKeyPair caPair = DotNetUtilities.GetKeyPair(caCert.PrivateKey);

        for(int i=0; i!=attributes.Count; i++)
        {
            AttributeX509 attr = AttributeX509.GetInstance(attributes[i]);

            //process extension request
            if (attr.AttrType.Id.Equals(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest.Id))
            {
                X509Extensions extensions = X509Extensions.GetInstance(attr.AttrValues[0]);

                var e = extensions.ExtensionOids.GetEnumerator();
                while (e.MoveNext())
                {
                    DerObjectIdentifier oid = (DerObjectIdentifier)e.Current;
                    Org.BouncyCastle.Asn1.X509.X509Extension ext = extensions.GetExtension(oid);

                    certGen.AddExtension(oid, ext.IsCritical, ext.GetParsedValue());
                }
            }
        }

        //certGen.AddExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.KeyEncipherment));
        certGen.AddExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeID.AnyExtendedKeyUsage));
        certGen.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(0));

        Org.BouncyCastle.X509.X509Certificate selfSignedCert = certGen.Generate(caPair.Private);

        //Check if the certificate can be verified
        selfSignedCert.Verify(caPair.Public);

        if (log.IsDebugEnabled)
        {
            //var execPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
            File.WriteAllBytes(execPath + "\\scepCertificate.pfx", selfSignedCert.GetEncoded());
        }

         return selfSignedCert.GetEncoded();
    }

    private byte[] createEnvelopedDataPkcs7(byte[] pkcs7RequestData)
    {
        var recipient = new CmsRecipient(new X509Certificate2(pkcs7RequestData));

        var envelopedContent = new System.Security.Cryptography.Pkcs.ContentInfo(new Oid(Oids.Pkcs7.EncryptedData, "envelopedData"), pkcs7RequestData);
        //var envelopedContent = new System.Security.Cryptography.Pkcs.ContentInfo(pkcs7RequestData);
        var envelopedMessage = new EnvelopedCms(envelopedContent);
        //var envelopedMessage = new EnvelopedCms(Con);

        envelopedMessage.Encrypt(recipient);
        var encryptedMessageData = envelopedMessage.Encode();

        return encryptedMessageData;
    }

    private byte[] createSignedDataPkcs7(byte[] encryptedMessageData, X509Certificate2 localPrivateKey, IEnumerable&lt;CryptographicAttributeObject&gt; attributes)
    {

        var senderNonce = attributes
            .Single(att =&gt; att.Oid.Value.Equals(Oids.Scep.SenderNonce))
            .Values[0];

        var transactionId = attributes
            .Single(att =&gt; att.Oid.Value.Equals(Oids.Scep.TransactionId))
            .Values[0];

        // Create the outer envelope, signed with the local private key
        var signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, localPrivateKey);
        //{
        //    DigestAlgorithm = new Oid(Oids.Pkcs.SHA1, "digestAlorithm")
        //};

        //signer.SignedAttributes.Add(signingTime);
        var messageType = new AsnEncodedData(Oids.Scep.MessageType, DerEncoding.EncodePrintableString("3"));
        signer.SignedAttributes.Add(messageType);
        signer.SignedAttributes.Add(transactionId);
        var recipientNonce = new Pkcs9AttributeObject(Oids.Scep.RecipientNonce, DerEncoding.EncodeOctet(senderNonce.RawData));
        signer.SignedAttributes.Add(recipientNonce);

        var pkiStatus = new AsnEncodedData(Oids.Scep.PkiStatus, DerEncoding.EncodePrintableString("0"));
        signer.SignedAttributes.Add(pkiStatus);

        var nonceBytes = new byte[16];
        RNGCryptoServiceProvider.Create().GetBytes(nonceBytes);
        senderNonce = new Pkcs9AttributeObject(Oids.Scep.SenderNonce, DerEncoding.EncodeOctet(nonceBytes));
        signer.SignedAttributes.Add(senderNonce);


        //var failInfo = new Pkcs9AttributeObject(Oids.Scep.FailInfo, DerEncoding.EncodePrintableString("2"));
        //signer.SignedAttributes.Add(failInfo);

        // Seems that the oid is not needed for this envelope
        var contentInfo = new System.Security.Cryptography.Pkcs.ContentInfo(encryptedMessageData); //new Oid("1.2.840.113549.1.7.1", "data"), encryptedMessageData);
        var signedCms = new SignedCms(contentInfo, false);
        signedCms.ComputeSignature(signer);

        return signedCms.Encode();
    }

    private X509Certificate2 getSignerCert()
    {
        string thumbprint = "CACertThumbprint";

        thumbprint = Regex.Replace(thumbprint, @"[^\da-zA-z]", string.Empty).ToUpper();

        // Get a local private key for sigining the outer envelope 
        var certStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
        certStore.Open(OpenFlags.ReadOnly);

        var signerCert = certStore
            .Certificates
            .Find(X509FindType.FindByThumbprint, thumbprint, false)
            .Cast&lt;X509Certificate2&gt;()
            .Single();

        certStore.Close();
        return signerCert;
    }

    //  Decrypt the encoded EnvelopedCms message for one of the
    //  recipients.
    public Byte[] DecryptMsg(byte[] encodedEnvelopedCms, X509Certificate2Collection caChain)
    {
        //  Prepare object in which to decode and decrypt.
        EnvelopedCms envelopedCms = new EnvelopedCms();

        //  Decode the message.
        envelopedCms.Decode(encodedEnvelopedCms);

        //  Display the number of recipients
        DisplayEnvelopedCms(envelopedCms, false);

        //  Decrypt the message.
        this.log.Debug("Decrypting Data for one recipient ... ");
        envelopedCms.Decrypt(envelopedCms.RecipientInfos[0], caChain);
        this.log.Debug("Done.");

        //  The decrypted message occupies the ContentInfo property
        //  after the Decrypt method is invoked.
        return envelopedCms.ContentInfo.Content;
    }

    //  Display the ContentInfo property of an EnvelopedCms object.
    private void DisplayEnvelopedCmsContent(String desc,
        EnvelopedCms envelopedCms)
    {
        this.log.Debug(string.Format(desc + " (length {0}):  ",
            envelopedCms.ContentInfo.Content.Length));
        foreach (byte b in envelopedCms.ContentInfo.Content)
        {
            this.log.Debug(b.ToString() + " ");
        }
    }

    //  Display some properties of an EnvelopedCms object.
    private void DisplayEnvelopedCms(EnvelopedCms e,
        Boolean displayContent)
    {
        this.log.Debug("\nEnveloped PKCS #7 Message Information:");
        this.log.Debug(string.Format(
            "\tThe number of recipients for the Enveloped PKCS #7 " +
            "is:  {0}", e.RecipientInfos.Count));
        for (int i = 0; i &lt; e.RecipientInfos.Count; i++)
        {
            this.log.Debug(string.Format(
                "\tRecipient #{0} has type {1}.",
                i + 1,
                e.RecipientInfos[i].RecipientIdentifier.Type));
        }
        if (displayContent)
        {
            DisplayEnvelopedCmsContent("Enveloped PKCS #7 Content", e);
        }

    }
}
</code></pre>