To avoid this error you could either:
- Load the assembly dynamically, or
- Sign the third-party assembly.
You will find instructions on signing third-party assemblies in .NET-fu: Signing an Unsigned Assembly (Without Delay Signing).
Signing Third-Party Assemblies
The basic principle to sign a thirp-party is to
Disassemble the assembly using ildasm.exe
and save the intermediate language (IL):
ildasm /all /out=thirdPartyLib.il thirdPartyLib.dll
Rebuild and sign the assembly:
ilasm /dll /key=myKey.snk thirdPartyLib.il
Fixing Additional References
The above steps work fine unless your third-party assembly (A.dll) references another library (B.dll) which also has to be signed. You can disassemble, rebuild and sign both A.dll and B.dll using the commands above, but at runtime, loading of B.dll will fail because A.dll was originally built with a reference to the unsigned version of B.dll.
The fix to this issue is to patch the IL file generated in step 1 above. You will need to add the public key token of B.dll to the reference. You get this token by calling
sn -Tp B.dll
which will give you the following output:
Microsoft (R) .NET Framework Strong Name Utility Version 4.0.30319.33440
Copyright (c) Microsoft Corporation. All rights reserved.
Public key (hash algorithm: sha1):
002400000480000094000000060200000024000052534131000400000100010093d86f6656eed3
b62780466e6ba30fd15d69a3918e4bbd75d3e9ca8baa5641955c86251ce1e5a83857c7f49288eb
4a0093b20aa9c7faae5184770108d9515905ddd82222514921fa81fff2ea565ae0e98cf66d3758
cb8b22c8efd729821518a76427b7ca1c979caa2d78404da3d44592badc194d05bfdd29b9b8120c
78effe92
Public key token is a8a7ed7203d87bc9
The last line contains the public key token. You then have to search the IL of A.dll for the reference to B.dll and add the token as follows:
.assembly extern /*23000003*/ MyAssemblyName
{
.publickeytoken = (A8 A7 ED 72 03 D8 7B C9 )
.ver 10:0:0:0
}
When you sign an assembly with a strong name based on a private key that you create, this has the following benefits:
- A strong name guarantees the uniqueness of an assembly's identity by adding a public key token and a digital signature to the assembly.
- A strong name can be matched to a public key to prove that the assembly comes from the publisher with that public key, and only that publisher.
- A strong name provides a strong integrity check. Passing the .NET Framework security checks guarantees that the contents of the assembly haven't been changed since it was last built.
Is it possible to use strong-naming to verify an assembly author?
Yes, as discussed above strong-naming can verify the assembly's latest author. But it doesn't verify the original author. If an attacker replaces your assembly's strong name, then all that can be verified is that you weren't the latest author of the assembly. If he removes the strong name, then no author verification can be done at all.
To which extent can a strong-named assembly be verified to avoid tampering?
The following C# code verifies that an attacker hasn't tampered with the public key token that was written to your assembly when you applied the strong name. It doesn't avoid tampering, but it can detect some types of tampering. The method below accepts a byte array containing your public key token, and compares it with the actual token of the assembly. Note that for this technique to be effective, your obfuscator of choice should encrypt the string containing your public key token, and only decrypt it on the fly as it's used. And also be aware that you need to have FullTrust permission for this code to work because it uses reflection underneath the hood.
// Check that public key token matches what's expected.
private static bool IsPublicTokenOkay_Check(byte [] tokenExpected)
{
// Retrieve token from current assembly
byte [] tokenCurrent = Assembly.GetExecutingAssembly().GetName().GetPublicKeyToken();
// Check that lengths match
if (tokenExpected.Length == tokenCurrent.Length)
{
// Check that token contents match
for (int i = 0; i < tokenCurrent.Length; i++)
if (tokenExpected[i] != tokenCurrent[i])
return false;
}
else
{
return false;
}
return true;
}
As long as you're running under a version of the .NET Framework before .NET 3.5 SP1, you can also force verification of the strong name signature in case the strong name was removed by an attacker or the strong name check was disabled in the registry. The following code demonstrates a call into a static method of another class called NativeMethods. This is where the verification will be enforced.
// Check that this assembly has a strong name.
private bool IsStrongNameValid_Check()
{
byte wasVerified = Convert.ToByte(false);
byte forceVerification = Convert.ToByte(true);
string assemblyName = AppDomain.CurrentDomain.BaseDirectory +
AppDomain.CurrentDomain.FriendlyName;
return NativeMethods.CheckSignature(assemblyName,
forceVerification,
ref wasVerified);
}
The actual signature verification is done using P/Invoke as shown below. The usage of the StrongNameSignatureVerificationEx API is quite convoluted - for a decent explanation, see this blog entry.
// P/Invoke to check various security settings
// Using byte for arguments rather than bool,
// because bool won't work on 64-bit Windows!
[DllImport("mscoree.dll", CharSet=CharSet.Unicode)]
private static extern bool StrongNameSignatureVerificationEx(string wszFilePath,
byte fForceVerification,
ref byte pfWasVerified);
// Private constructor because this type has no non-static members
private NativeMethods()
{
}
public static bool CheckSignature(string assemblyName,
byte forceVerification,
ref byte wasVerified)
{
return StrongNameSignatureVerificationEx(assemblyName,
forceVerification,
ref wasVerified );
}
Note that this won't work by default for applications using .NET 3.5 SP1 or higher, which has the strong name bypass feature. It's possible to disable this feature for your application by adding a setting to its config file. But of course any attacker with read/write access to that config file can reverse your decision.
Best Answer
Yes, it is a best practice and you really ought to do it especially considering the fact that you are shipping this code to customers (I consider strong-naming to be less critical in an internal or web-based application).
For an explanation of this rationale please see Strong-Named Assemblies: