Generating StrongName Keys

(updated 12/03/04 to point to refactored code)

Generating Keys

It's been just under a month since I've updated the Managed StrongName API, so here's the next set of APIs.  This time, I've setup the APIs needed to create a new key suitable for signing.  Namely, these are the StrongNameKeyGen and (for Whidbey) StrongNameKeyGenEx APIs.  Both APIs work the same way, so I'll describe the newer StrongNameKeyGenEx API, which only adds an extra parameter from the old StrongNameKeyGen version.  To start with, lets look at the P/Invoke declaration, from MS.StrongName\Native\NativeMethods.cs:

The parameters for StrongNameKeyGenEx work out as follows:

Parameter Use
wszKeyContainer Name of the key container to store the key in, can be null if passing no flags to dwFlags
dwFlags A member of the StrongNameKeyGenFlags enumeration. The only interesting member is LeaveKey which will not remove the generated key from its key container upon return from the API
dwKeySize This is available in StrongNameKeyGenEx only, and specifies the size in bits of the key to generate. StrongNameKeyGen defaults this to 1024.
ppbKeyBlob Generated key blob
pcbKeyBlob Size in bytes of the generated blob

StrongNameKeyGenerationEx introduces the dwKeySize parameter, which allows you to generate keys of various sizes.  However, .NET versions 1.0 and 1.1 will only sign with 1024 bit keys.  Whidbey adds support for signing with 2048 bit keys.  Both APIs return true if the key was successfully generated, and false if there was an error.

Since there is only a true / false return code, getting more information on error conditions is accomplished through the use of the StrongNameErrorInfo API, whose declaration can be found in MS.StrongName\Native\NativeMethods.cs.  The return value from this function is an HRESULT, which can be translated into an Exception through the use of the Marshal.ThrowExceptionForHR, or on Whidbey Marshal.GetExceptionForHR APIs.  This is shown in MS.StrongName\Utility.cs

Once you've gotten the key back from StrongNameKeyGenerationEx, you need to copy it into a managed byte array.  This can be done with the Marshal.Copy method.  However, this still leaves unmanaged memory allocated to your process.  In order to release this memory, another P/Invoke declaration from MS.StrongName\Native\NativeMethods.cs is used.  StrongNameFreeBuffer simply takes a pointer to the memory that StrongNameKeyGen(Ex) returned to you, and releases it.

Making a key file out of the resulting byte array is very easy.  Since snk files are simply raw dumps of the key information needed to sign an assembly, simply writing the byte array out to a file will result in a usable .snk file.

This entire process can be found in the MS.StrongName.Keys::GenerateKeyPair method in MS.StrongName\Keys.cs.  Boiled down to the essentials, with error checking removed, the process looks similar to the following:

/// <summary>
/// Generate a key and write it to a file
/// </summary>
/// <param name="keysize">size, in bits, of the key to generate</param>
/// <param name="filename">name of the file to write to</param>
/// <returns>true if the operation succeeded, false otherwise</returns>
private static bool GenerateKey(uint keysize, string filename)
{
    Debug.Assert(!String.IsNullOrEmpty(filename));

    // variables that hold the unmanaged key
    IntPtr keyBlob = IntPtr.Zero;
    long generatedSize = 0;

    // create the key
    bool createdKey = StrongName.Native.Generation.StrongNameKeyGenEx(null, 
        StrongName.Native.StrongNameKeyGenFlags.None, (int)keysize,
        out keyBlob, out generatedSize);

    // if there was a problem, translate it and report it
    if(!createdKey || keyBlob == IntPtr.Zero)
    {
        Exception error = Marshal.GetExceptionForHR(Utility.StrongNameErrorInfo());
        Console.WriteLine("Error generating key: {0}", error.Message);
        return false;
    }
                        
    try
    {
        Debug.Assert(keyBlob != IntPtr.Zero);

        // make sure the key size makes sense
        Debug.Assert(generatedSize > 0 && generatedSize <= Int32.MaxValue);
        if(generatedSize <= 0 || generatedSize > Int32.MaxValue)
        {
            Console.WriteLine("Error while generating key");
            return false;
        }
    
        // get the key into managed memory
        byte[] key = new byte[generatedSize];
        Marshal.Copy(keyBlob, key, 0, (int)generatedSize);

        // write the key to the specified file
        using(FileStream snkStream = new FileStream(filename, FileMode.Create, FileAccess.Write))
        using(BinaryWriter snkWriter = new BinaryWriter(snkStream))
            snkWriter.Write(key);
    }
    finally
    {
        // release the unmanaged memory the key resides in
        if(keyBlob != IntPtr.Zero)
            Utility.StrongNameFreeBuffer(keyBlob);
    }

    // everything is written ok
    return true;
}

Changes to the Managed StrongName project

I've made quite a few changes to the managed strong name files this time around.  The biggest change was my decision to drop support for building the tool under v1.1 of the framework as well as Whidbey.  Most of the P/Invoke declarations will still work, but there were going to be enough difference in the msn.exe tool itself, that I didn't feel like it justified creating all the differences.  The biggest fallout from this is that classes that used to be sealed with a private constructor are now all static classes.  In addition, the expanded capabilities of the Console and String classes are used.  The complete change list is:

Modified

  • StrongName.Native\Verification.cs
    • removed the private constructor and made into static class
  • msn\msn.cs
    • added the -k mode
    • removed the private constructor and made into a static class
    • added an extra blank line of output in the output of msn
    • converted String.Length == 0 checks into String.IsNullOrEmpty calls
    • always use Console.WindowWidth to figure out the size of the output
  • msn\msn.resx
    • added extra resources to support key generation
  • msn\Verification.cs
    • removed the private constructor and made into a static class

Added

  • StrongName.Native\Generation.cs - P/Invoke declarations for StrongNameKeyGen and StrongNameKeyGenEx
  • StrongName.Native\Utility.cs - P/Invoke declarations for StrongNameFreeBuffer and StrongNameErrorInfo
  • msn\Generation.cs - Implementation of the -k option to msn, to allow for keys to be generated