23

I'm unit testing a .NET application; some of the unit tests involve programmatically generating X509Certificate2 objects.

I don't care about actual signing/private key/validation stuff, I'd just like to have an object that doesn't throw exceptions when its fields are examined. I tried using the parameterless constructor, but then a whole bunch of fields throw exceptions when accessed. As seen in the debugger:

SubjectName = '(new System.Collections.Generic.Mscorlib_CollectionDebugView(result.Certificates)).Items[0].SubjectName' threw an exception of type 'System.Security.Cryptography.CryptographicException'

I also tried passing a byte array with some random numbers in it, but that didn't even construct (does the array need to be a specific size?)

So, question: what is the simplest (fewest lines of code) way to programmatically generate an X509Certificate2 object which will not throw exceptions upon field/property accesses?

Elrond_EGLDer
  • 47,430
  • 25
  • 189
  • 180
Cephron
  • 1,547
  • 1
  • 18
  • 26
  • What is Moles? Can you provide your final solution as an answer? I'm trying to do the same thing now. – Langdon Jul 28 '11 at 18:47
  • I assume he means a framework for creating test stubs and detours from Microsoft research. You can look here: http://research.microsoft.com/en-us/projects/moles/. – Sascha Jul 28 '11 at 20:03

4 Answers4

10

I would suggest the following:

  1. Generate a certificate using makecert.
  2. Add the certificate to your project and change its Build Action to Embedded Resource.
  3. Load the certificate from the resource in your unit test setup, see below.

Code:

byte[] embeddedCert;
Assembly thisAssembly = Assembly.GetAssembly(typeof(MyType));
using (Stream certStream = thisAssembly.GetManifestResourceStream("YourProjectName.localhost.pfx"))
{
  embeddedCert = new byte[certStream.Length];
  certStream.Read(embeddedCert, 0, (int)certStream.Length);
}

_signingCert = new X509Certificate2(embeddedCert, "password");

At this point you should be good to go as far as interacting with the certificate. You can create different variants if your unit tests have different needs.

Jason Shantz
  • 853
  • 13
  • 13
  • 1
    I suppose I could actually just read that byte array *once*, printing it into into a file as characters, and then copy-paste that into the source code as a hard-coded byte[]? That wouldn't be too bad... – Cephron Jul 26 '11 at 21:07
  • Ended up using Moles to make it work, but thanks for a helpful answer! – Cephron Jul 27 '11 at 21:37
  • @Langdon Moles is an isolation framework from MS. It used to be a part of Pex testing framework. Some years ago it was extracted and now available as stand-alone framework. – Safor Oct 10 '14 at 12:53
  • 1
    @JasonShantz I don't understand why people who ask this question mark your answer as "answered" without explanation what "MyType" is??? – anhtv13 Jun 07 '16 at 08:22
  • 1
    @anhtv13 I believe MyType is a placeholder for a C# class/type in the project that contains the self-signed certificate as an embedded resource. This allows a reference to the assembly which in turn allows the resource to be read as a stream into the embeddedCert byte array. Once you have this byte array you can use the appropriate constructor for the X509Certificate2 https://msdn.microsoft.com/en-us/library/ms148413(v=vs.110).aspx – pwdst Nov 16 '17 at 11:04
  • 1
    In that respect MyType is any arbitrary object in the same project as the resource, which could be the UnitTest class itself if you keep the certificate in the same project. – pwdst Nov 16 '17 at 11:05
  • 1
    Note: "YourProjectName" should be the namespace (in case the project name and namespace are different). Use thisAssembly.GetManifestResourceNames() to check. – heringer Nov 22 '17 at 13:15
3

This may seem very hacky, and it depends on how pragmatic you want to be ... an approach I used was to just grab a random certificate from the machine.

This was good when: - I know that every machine that's running these tests has a valid certificate. - I was using GIT and didn't want to check in a binary file for the cert - I don't care about the cert content - I'm using code that's not mock friendly and explicitly requires a non-mockable X509Certificate object.

Definitely not bullet proof, but unblocked me and unblocked my testing scenario.

    static X509Certificate2 GetRandomCertificate()
    {
        X509Store st = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        st.Open(OpenFlags.ReadOnly);
        try
        {
            var certCollection = st.Certificates;

            if (certCollection.Count == 0)
            {
                return null;
            }
            return certCollection[0];
        }
        finally
        {
            st.Close();
        }
    }
Mike S
  • 2,719
  • 1
  • 19
  • 12
1

At Langdon's request, the solution I used, myself:

        //Moling test Certificate
        var cert = new MX509Certificate2();
        var subject = new MX500DistinguishedName();
        // hookup
        cert.SubjectNameGet = () => subject;
        cert.ThumbprintGet = () => "foo";
        subject.NameGet = () => "foobar";

This worked because the only fields I accessed in the Certificate were it's SubjectName and Thumbprint, and the only field of the SubjectName I accessed was the name. I "moled" the getter methods for these fields to return the dummy strings. If you were to access other fields, you'd probably need to mole them to.

And so, what is "Moles"?

"Moles is a lightweight framework for test stubs and detours in .NET that is based on delegates. Moles may be used to detour any .NET method, including non-virtual/static methods in sealed types. Moles is freely available on Visual Studio Gallery or bundled with Pex."

http://research.microsoft.com/en-us/projects/moles/

Cephron
  • 1,547
  • 1
  • 18
  • 26
0

There is one more simple way of doing it.

  1. Create a Certificate using MakeCert or export any existing certificate.
  2. Add the Certificate in Project (App_Data folder).
  3. Follow below steps to get the certificate.

Code:

        string certificate = "cert.pfx";
        string certPath = string.Format("{0}/App_Data/{1}", HttpRuntime.AppDomainAppPath, certificate);
        byte[] bytes = Util.FileToArray(certPath);
        X509Certificate2 publicKey = new X509Certificate2(bytes, "somepassoword");
Ashwin
  • 341
  • 1
  • 3
  • 10