Cannot create an HttpRequestStream with PKCS#7 SignedData MIME entity

+1 vote
asked Jan 4, 2014 by markgd (210 points)
edited Jan 6, 2014

I am attempting to create a HttpRequestStream with PKCS#7 SignedData MIME entity. However, I have not been able to create it with a valid signature.

            // first, create the root MIME entity and add the payload entity to it  
            MimeEntity meMultipartSigned = new MimeEntity();
            meMultipartSigned.ContentType = new ContentType(MediaTypeNames.Multipart.Signed);
            meMultipartSigned.ContentType.Parameters.Add("protocol", "application/pkcs7-signature");
            meMultipartSigned.ContentType.Parameters.Add("micalg", "sha-1");

            MimeEntity mePayload = new MimeEntity();
            _payload.Seek(0, 0);
            mePayload.SetContent(_payload, "", "application/xml", TransferEncoding.Binary);
            meMultipartSigned.Parts.Add(mePayload);
            mePayload.ReadOnly = true;

            // now sign the data in the payload entity
            ContentInfo contentInfo = new ContentInfo(Utility.IO.DataHandling.ReadFully(mePayload.GetContentStream())); // is there a more efficient way?
            SignedData signedData = new SignedData(contentInfo, true);
            signedData.IncludeOption = CertificateIncludeOption.EndCertificateOnly;

            SignerInfo signer = new SignerInfo(_certificate, SubjectIdentifierType.IssuerAndSerialNumber, SignatureHashAlgorithm.SHA1);
            signedData.SignerInfos.Add(signer);
            signedData.Sign(SignatureOptions.SkipCertificateUsageCheck);

            // store the signed data
            MimeEntity meSignedData = new MimeEntity();
            meSignedData.ContentType = new ContentType("application/pkcs7-signature");
            meSignedData.SetContent(new MemoryStream(signedData.Encode()),"smime.7ps","application/pkcs7-signature",TransferEncoding.Base64);
            meSignedData.ContentType.Parameters.Add("smime-type","signed-data");
            meMultipartSigned.Parts.Add(meSignedData);


            MemoryStream ms = new MemoryStream();
            meMultipartSigned.Save(ms);
            ms.Seek(0, 0);

            this.Content = new StreamContent(ms);

I am then validating, by using this function, which I have verified works with an object signed by an external process.

    public bool ValidateSignature()
    {
        MimeEntity me = new MimeEntity();
        me.Load(this.Content.ReadAsStreamAsync().Result);

        ValidationOptions vo = ValidationOptions.IgnoreWrongUsage;
        SignatureValidationResult validationResult = me.ValidateSignature(false,vo);
        return validationResult.Valid;
    }

What is the correct way to ensemble this multipart/signed entity and sign it correctly?

Applies to: Rebex Secure Mail
commented Jan 6, 2014 by Tomas Knopp (58,890 points)
edited Jan 6, 2014

Now I shall explain in more details what is going on under the hood and provide a solution.

You were on a right path, however the non-working signature was caused by the fact that the MimeEntity.GetContentStream() method does not return the headers of a mime entity, butit only returns the actual MimeEntity content (_payload). So the first needed change in your code (if you still do want to dive as deep) would be to replace the GetContentStream method with MimeEntity.GetStream() method. However, as the GetStream method was not designed for making signatures (rather it was designed in respect to saving to files or streams), you would have to discard the excessive two bytes of newline which would as well prevent the signature from working correctly.

Here is the modified working version of your original code:

        // first, create the root MIME entity and add the payload entity to it  
        MimeEntity meMultipartSigned = new MimeEntity();
        meMultipartSigned.ContentType = new ContentType(MediaTypeNames.Multipart.Signed);
        meMultipartSigned.ContentType.Parameters.Add("protocol", "application/pkcs7-signature");
        meMultipartSigned.ContentType.Parameters.Add("micalg", "sha-1");

        MimeEntity mePayload = new MimeEntity();
        _payload.Seek(0, 0);
        mePayload.SetContent(_payload, "", "application/xml", TransferEncoding.Binary);
        meMultipartSigned.Parts.Add(mePayload);
        mePayload.ReadOnly = true;

        // now sign the data in the payload entity
        var ms = new MemoryStream();

        // use ToStream() method instead of GetContentStream() so that headers are copied as well
        mePayload.ToStream().CopyTo(ms);

        // discard the newline
        ms.SetLength(ms.Length - 2);

        ContentInfo contentInfo = new ContentInfo(ms.ToArray());
        SignedData signedData = new SignedData(contentInfo, true);
        signedData.IncludeOption = CertificateIncludeOption.EndCertificateOnly;

        SignerInfo signer = new SignerInfo(_certificate, SubjectIdentifierType.IssuerAndSerialNumber, SignatureHashAlgorithm.SHA1);
        signedData.SignerInfos.Add(signer);
        signedData.Sign(SignatureOptions.SkipCertificateUsageCheck);

        // store the signed data
        MimeEntity meSignedData = new MimeEntity();
        meSignedData.ContentType = new ContentType("application/pkcs7-signature");
        meSignedData.SetContent(new MemoryStream(signedData.Encode()), "smime.7ps", "application/pkcs7-signature", TransferEncoding.Base64);
        meSignedData.ContentType.Parameters.Add("smime-type", "signed-data");
        meMultipartSigned.Parts.Add(meSignedData);

        // save
        ms = new MemoryStream();
        meMultipartSigned.Save(ms);
        ms.Seek(0, 0);

        this.Content = new StreamContent(ms);

1 Answer

0 votes
answered Jan 6, 2014 by Tomas Knopp (58,890 points)
edited Jan 6, 2014

The easiest way to create a PKCS#7 SignedData MIME entity is to take advantage of the MimeEntity.SetSignedContent method together with the MimeEntity.Sign method like this:

        // first, create the root MIME entity and add the payload entity to it  
        MimeEntity meMultipartSigned = new MimeEntity();

        MimeEntity mePayload = new MimeEntity();
        _payload.Seek(0, 0);
        mePayload.SetContent(_payload, "", "application/xml", TransferEncoding.Binary);

        // set signed content
        meMultipartSigned.SetSignedContent(mePayload, _certificate);

        // specify options
        meMultipartSigned.SignedContentInfo.IncludeOption = CertificateIncludeOption.EndCertificateOnly;
        meMultipartSigned.Options |= MimeOptions.SkipCertificateUsageCheck;

        // sign the content
        meMultipartSigned.Sign();

        // save to stream
        MemoryStream ms = new MemoryStream();
        meMultipartSigned.Save(ms);
        ms.Seek(0, 0);

        // etc.
        this.Content = new StreamContent(ms);

The signature validation should work now. Please let us know if you need additional help.

...