// SN.cs: sn clone tool
//
// Author:
-// Sebastien Pouliot (spouliot@motus.com)
+// Sebastien Pouliot <sebastien@ximian.com>
//
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006,2008 Novell, Inc (http://www.novell.com)
//
using System;
using Mono.Security;
using Mono.Security.Cryptography;
+using Mono.Security.X509;
[assembly: AssemblyTitle("Mono StrongName")]
[assembly: AssemblyDescription("StrongName utility for signing assemblies")]
static private void Header ()
{
- Assembly a = Assembly.GetExecutingAssembly ();
- AssemblyName an = a.GetName ();
-
- object [] att = a.GetCustomAttributes (typeof (AssemblyTitleAttribute), false);
- string title = ((att.Length > 0) ? ((AssemblyTitleAttribute) att [0]).Title : "Mono StrongName");
-
- att = a.GetCustomAttributes (typeof (AssemblyCopyrightAttribute), false);
- string copyright = ((att.Length > 0) ? ((AssemblyCopyrightAttribute) att [0]).Copyright : "");
-
- Console.WriteLine ("{0} {1}", title, an.Version.ToString ());
- Console.WriteLine ("{0}{1}", copyright, Environment.NewLine);
+ Console.WriteLine (new AssemblyInfo ().ToString ());
}
- static string defaultCSP = null;
+ static string defaultCSP;
- static bool LoadConfig ()
+ static bool LoadConfig (bool quiet)
{
+ MethodInfo config = typeof (System.Environment).GetMethod ("GetMachineConfigPath",
+ BindingFlags.Static|BindingFlags.NonPublic);
+
+ if (config != null) {
+ string path = (string) config.Invoke (null, null);
+
+ bool exist = File.Exists (path);
+ if (!quiet && !exist)
+ Console.WriteLine ("Couldn't find machine.config");
+
+ StrongNameManager.LoadConfig (path);
+ return exist;
+ }
+ else if (!quiet)
+ Console.WriteLine ("Couldn't resolve machine.config location (corlib issue)");
+
// default CSP
return false;
}
+ // TODO
static int SaveConfig ()
{
// default CSP
if ((i % 39 == 0) && (data.Length > 39))
sb.Append (Environment.NewLine);
sb.Append (data [i].ToString ("x2"));
- if (i > 1000) {
+ if (i > 2080) {
+ // ensure we can display up to 16384 bits keypair
sb.Append (" !!! TOO LONG !!!");
break;
}
return sb.ToString ();
}
+ static RSA GetKeyFromFile (string filename)
+ {
+ byte[] data = ReadFromFile (filename);
+ try {
+ // for SNK files (including the ECMA pseudo-key)
+ return new StrongName (data).RSA;
+ }
+ catch {
+ if (data [0] != 0x30)
+ throw;
+ // this could be a PFX file
+ Console.Write ("Enter password for private key (will be visible when typed): ");
+ PKCS12 pfx = new PKCS12 (data, Console.ReadLine ());
+ // works only if a single key is present
+ if (pfx.Keys.Count != 1)
+ throw;
+ RSA rsa = (pfx.Keys [0] as RSA);
+ if (rsa == null)
+ throw;
+ return rsa;
+ }
+ }
+#if false
+ // is assembly signed (or delayed signed) ?
+ static bool IsStrongNamed (Assembly assembly)
+ {
+ if (assembly == null)
+ return false;
+
+ object[] attrs = assembly.GetCustomAttributes (true);
+ foreach (object o in attrs) {
+ if (o is AssemblyKeyFileAttribute)
+ return true;
+ else if (o is AssemblyKeyNameAttribute)
+ return true;
+ }
+ return false;
+ }
+#endif
+ static bool ReSign (string assemblyName, RSA key)
+ {
+ // this doesn't load the assembly (well it unloads it ;)
+ // http://weblogs.asp.net/nunitaddin/posts/9991.aspx
+ AssemblyName an = null;
+ try {
+ an = AssemblyName.GetAssemblyName (assemblyName);
+ }
+ catch {
+ }
+ if (an == null) {
+ Console.WriteLine ("Unable to load assembly: {0}", assemblyName);
+ return false;
+ }
+
+ StrongName sign = new StrongName (key);
+ byte[] token = an.GetPublicKeyToken ();
+
+ // first, try to compare using a mapped public key (e.g. ECMA)
+ bool same = Compare (sign.PublicKey, StrongNameManager.GetMappedPublicKey (token));
+ if (!same) {
+ // second, try to compare using the assembly public key
+ same = Compare (sign.PublicKey, an.GetPublicKey ());
+ if (!same) {
+ // third (and last) chance, try to compare public key token
+ same = Compare (sign.PublicKeyToken, token);
+ }
+ }
+
+ if (same) {
+ bool signed = sign.Sign (assemblyName);
+ Console.WriteLine (signed ? "Assembly {0} signed." : "Couldn't sign the assembly {0}.",
+ assemblyName);
+ return signed;
+ }
+
+ Console.WriteLine ("Couldn't sign the assembly {0} with this key pair.", assemblyName);
+ return false;
+ }
+
+ static int Verify (string assemblyName, bool forceVerification)
+ {
+ // this doesn't load the assembly (well it unloads it ;)
+ // http://weblogs.asp.net/nunitaddin/posts/9991.aspx
+ AssemblyName an = null;
+ try {
+ an = AssemblyName.GetAssemblyName (assemblyName);
+ }
+ catch {
+ }
+ if (an == null) {
+ Console.WriteLine ("Unable to load assembly: {0}", assemblyName);
+ return 2;
+ }
+
+ byte[] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ());
+ if ((publicKey == null) || (publicKey.Length < 12)) {
+ // no mapping
+ publicKey = an.GetPublicKey ();
+ if ((publicKey == null) || (publicKey.Length < 12)) {
+ Console.WriteLine ("{0} is not a strongly named assembly.", assemblyName);
+ return 2;
+ }
+ }
+
+ // Note: MustVerify is based on the original token (by design). Public key
+ // remapping won't affect if the assembly is verified or not.
+ if (forceVerification || StrongNameManager.MustVerify (an)) {
+ RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey, 12);
+ StrongName sn = new StrongName (rsa);
+ if (sn.Verify (assemblyName)) {
+ Console.WriteLine ("Assembly {0} is strongnamed.", assemblyName);
+ return 0;
+ }
+ else {
+ Console.WriteLine ("Assembly {0} is delay-signed but not strongnamed", assemblyName);
+ return 1;
+ }
+ }
+ else {
+ Console.WriteLine ("Assembly {0} is strongnamed (verification skipped).", assemblyName);
+ return 0;
+ }
+ }
+
+ static bool Compare (byte[] value1, byte[] value2)
+ {
+ if ((value1 == null) || (value2 == null))
+ return false;
+ bool result = (value1.Length == value2.Length);
+ if (result) {
+ for (int i=0; i < value1.Length; i++) {
+ if (value1 [i] != value2 [i])
+ return false;
+ }
+ }
+ return result;
+ }
+
static void Help (string details)
{
Console.WriteLine ("Usage: sn [-q | -quiet] options [parameters]{0}", Environment.NewLine);
Console.WriteLine (" -Vr assembly [userlist]{0}\tExempt the specified assembly from verification for the user list", Environment.NewLine);
Console.WriteLine (" -Vu assembly{0}\tRemove exemption entry for the specified assembly", Environment.NewLine);
Console.WriteLine (" -Vx{0}\tRemove all exemptions entries", Environment.NewLine);
+ Console.WriteLine ("{0}<1> Currently not implemented in the tool", Environment.NewLine);
break;
case "csp":
- Console.WriteLine ("CSP related options <2>");
+ Console.WriteLine ("CSP related options");
Console.WriteLine (" -d container{0}\tDelete the specified key container", Environment.NewLine);
Console.WriteLine (" -i keypair.snk container{0}\tImport the keypair from a SNK file into a CSP container", Environment.NewLine);
Console.WriteLine (" -pc container public.key{0}\tExport the public key from a CSP container to the specified file", Environment.NewLine);
Console.WriteLine ("Convertion options");
Console.WriteLine (" -e assembly output.pub{0}\tExport the assembly public key to the specified file", Environment.NewLine);
Console.WriteLine (" -p keypair.snk output.pub{0}\tExport the public key from a SNK file to the specified file", Environment.NewLine);
- Console.WriteLine (" -o input output.txt{0}\tConvert the input file to a CVS file (using decimal).", Environment.NewLine);
- Console.WriteLine (" -oh input output.txt{0}\tConvert the input file to a CVS file (using hexadecimal).", Environment.NewLine);
+ Console.WriteLine (" -o input output.txt{0}\tConvert the input file to a CSV file (using decimal).", Environment.NewLine);
+ Console.WriteLine (" -oh input output.txt{0}\tConvert the input file to a CSV file (using hexadecimal).", Environment.NewLine);
break;
case "sn":
Console.WriteLine ("StrongName signing options");
- Console.WriteLine (" -D assembly1 assembly2{0}\tCompare assembly1 and assembly2 (without signatures) <1>", Environment.NewLine);
+ Console.WriteLine (" -D assembly1 assembly2{0}\tCompare assembly1 and assembly2 (without signatures)", Environment.NewLine);
Console.WriteLine (" -k keypair.snk{0}\tCreate a new keypair in the specified file", Environment.NewLine);
Console.WriteLine (" -R assembly keypair.snk{0}\tResign the assembly with the specified StrongName key file", Environment.NewLine);
Console.WriteLine (" -Rc assembly container{0}\tResign the assembly with the specified CSP container", Environment.NewLine);
- Console.WriteLine (" -t file{0}\tShow the public key from the specified file [1]", Environment.NewLine);
- Console.WriteLine (" -tp file{0}\tShow the public key and pk token from the specified file <1>", Environment.NewLine);
- Console.WriteLine (" -T assembly{0}\tShow the public key from the specified assembly", Environment.NewLine);
+ Console.WriteLine (" -t file{0}\tShow the public key token from the specified file", Environment.NewLine);
+ Console.WriteLine (" -tp file{0}\tShow the public key and pk token from the specified file", Environment.NewLine);
+ Console.WriteLine (" -T assembly{0}\tShow the public key token from the specified assembly", Environment.NewLine);
Console.WriteLine (" -Tp assembly{0}\tShow the public key and pk token from the specified assembly", Environment.NewLine);
- Console.WriteLine (" -V assembly{0}\tVerify the specified assembly signature <1>", Environment.NewLine);
- Console.WriteLine (" -Vf assembly{0}\tVerify the specified assembly signature (even if disabled) <1>.", Environment.NewLine);
+ Console.WriteLine (" -v assembly{0}\tVerify the specified assembly signature", Environment.NewLine);
+ Console.WriteLine (" -vf assembly{0}\tVerify the specified assembly signature (even if disabled).", Environment.NewLine);
break;
default:
Console.WriteLine ("Help options");
Console.WriteLine (" -? | -h \tShow this help screen about the tool");
- Console.WriteLine (" -? | -h config \tConfiguration options (see strongname.xml)");
+ Console.WriteLine (" -? | -h config \tConfiguration options");
Console.WriteLine (" -? | -h csp \tCrypto Service Provider (CSP) related options");
Console.WriteLine (" -? | -h convert\tFormat convertion options");
Console.WriteLine (" -? | -h sn \tStrongName signing options");
break;
}
- Console.WriteLine ("{0}<1> Currently not implemented in the tool", Environment.NewLine);
- Console.WriteLine ("<2> Implemented in the tool but not in Mono{0}", Environment.NewLine);
}
- [STAThread]
- static int Main (string[] args)
+ static int Process (string[] args)
{
- if (args.Length < 1) {
- Header ();
- Help (null);
- return 1;
- }
-
int i = 0;
string param = args [i];
bool quiet = ((param == "-quiet") || (param == "-q"));
else
Header();
- bool config = LoadConfig ();
+ LoadConfig (quiet);
StrongName sn = null;
AssemblyName an = null;
Console.WriteLine ("Keypair in container {0} has been deleted", args [i]);
break;
case "-D":
- Console.WriteLine ("Unimplemented option");
+ StrongName a1 = new StrongName ();
+ byte[] h1 = a1.Hash (args [i++]);
+ StrongName a2 = new StrongName ();
+ byte[] h2 = a2.Hash (args [i++]);
+ if (Compare (h1, h2)) {
+ Console.WriteLine ("Both assembly are identical (same digest for metadata)");
+ // TODO: if equals then compare signatures
+ }
+ else
+ Console.WriteLine ("Assemblies are not identical (different digest for metadata)");
break;
case "-e":
// Export public key from assembly
case "-k":
// Create a new strong name key pair
// (a new RSA keypair automagically if none is present)
- sn = new StrongName ();
+ int size = 1024;
+ if (i < args.Length + 2) {
+ try {
+ size = Int32.Parse (args[i++]);
+ }
+ catch {
+ // oops, that wasn't a valid key size (assume 1024 bits)
+ i--;
+ }
+ }
+ sn = new StrongName (size);
WriteToFile (args[i], CryptoConvert.ToCapiKeyBlob (sn.RSA, true));
if (!quiet)
- Console.WriteLine ("A new strong name keypair has been generated in {0}", args [i]);
+ Console.WriteLine ("A new {0} bits strong name keypair has been generated in file '{1}'.", size, args [i]);
+ break;
+ case "-m":
+ Console.WriteLine ("Unimplemented option");
break;
case "-o":
byte[] infileD = ReadFromFile (args [i++]);
WriteCSVToFile (args [i], infileD, "D");
if (!quiet)
- Console.WriteLine ("Output CVS file is {0} (decimal format)", args [i]);
+ Console.WriteLine ("Output CSV file is {0} (decimal format)", args [i]);
break;
case "-oh":
byte[] infileX2 = ReadFromFile (args [i++]);
Console.WriteLine ("Output CVS file is {0} (hexadecimal format)", args [i]);
break;
case "-p":
- // Extract public key from SNK file
- sn = new StrongName (ReadFromFile (args [i++]));
- WriteToFile (args[i], CryptoConvert.ToCapiKeyBlob (sn.RSA, false));
+ // Extract public key from SNK or PKCS#12/PFX file
+ sn = new StrongName (GetKeyFromFile (args [i++]));
+ WriteToFile (args[i], sn.PublicKey);
if (!quiet)
Console.WriteLine ("Public Key extracted to file {0}", args [i]);
break;
// Extract public key from container
csp.KeyContainerName = args [i++];
rsa = new RSACryptoServiceProvider (csp);
- WriteToFile (args[i], CryptoConvert.ToCapiKeyBlob (rsa, false));
+ sn = new StrongName (rsa);
+ WriteToFile (args[i], sn.PublicKey);
if (!quiet)
Console.WriteLine ("Public Key extracted to file {0}", args [i]);
break;
case "-R":
- Console.WriteLine ("Unimplemented option");
+ string filename = args [i++];
+ if (! ReSign (filename, GetKeyFromFile (args [i])))
+ return 1;
break;
case "-Rc":
- Console.WriteLine ("Unimplemented option");
+ filename = args [i++];
+ csp.KeyContainerName = args [i];
+ rsa = new RSACryptoServiceProvider (csp);
+ if (! ReSign (filename, rsa))
+ return 1;
break;
case "-t":
// Show public key token from file
// Show public key token from assembly
an = AssemblyName.GetAssemblyName (args [i++]);
// note: ignore quiet
- Console.WriteLine ("Public Key Token: " + ToString (an.GetPublicKeyToken ()));
+ byte [] pkt = an.GetPublicKeyToken ();
+ if (pkt == null) {
+ Console.WriteLine ("{0} does not represent a strongly named assembly.", args [i - 1]);
+ } else {
+ Console.WriteLine ("Public Key Token: " + ToString (pkt));
+ }
break;
case "-Tp":
// Show public key and public key token from assembly
an = AssemblyName.GetAssemblyName (args [i++]);
- // note: ignore quiet
- Console.WriteLine ("Public Key:" + ToString (an.GetPublicKey ()));
- Console.WriteLine ("{0}Public Key Token: " + ToString (an.GetPublicKeyToken ()), Environment.NewLine);
- break;
- case "-V":
- Console.WriteLine ("Unimplemented option");
- break;
- case "-Vf":
- Console.WriteLine ("Unimplemented option");
+ byte [] token = an.GetPublicKeyToken ();
+ if (token == null) {
+ Console.WriteLine ("{0} does not represent a strongly named assembly.", args [i - 1]);
+ } else {
+ Console.WriteLine ("Public Key:" + ToString (an.GetPublicKey ()));
+ Console.WriteLine ("{0}Public Key Token: " + ToString (token), Environment.NewLine);
+ }
break;
+ case "-v":
+ filename = args [i++];
+ return Verify (filename, false);
+ case "-vf":
+ filename = args [i++];
+ return Verify (filename, true); // force verification
case "-Vl":
- Console.WriteLine ("Unimplemented option");
+ Console.WriteLine (new StrongNameManager ().ToString ());
break;
case "-Vr":
Console.WriteLine ("Unimplemented option");
Console.WriteLine ("Unimplemented option");
break;
case "-Vx":
+ // we must remove <verificationSettings> from each config files
Console.WriteLine ("Unimplemented option");
break;
case "-?":
}
return 0;
}
+
+ [STAThread]
+ static int Main (string[] args)
+ {
+ try {
+ if (args.Length < 1) {
+ Header ();
+ Help (null);
+ } else {
+ return Process (args);
+ }
+ }
+ catch (IndexOutOfRangeException) {
+ Console.WriteLine ("ERROR: Invalid number of parameters.{0}", Environment.NewLine);
+ Help (null);
+ }
+ catch (CryptographicException ce) {
+ Console.WriteLine ("ERROR: {0}", ce.Message);
+ }
+ catch (Exception e) {
+ Console.WriteLine ("ERROR: Unknown error during processing: {0}", e);
+ }
+
+ return 1;
+ }
}
}