Null constant cannot be used for ref/out variables
[mono.git] / mcs / tools / security / sn.cs
index 92a36b95cc05f81032d0d20d2498f0141fe0a726..28ee366d4e46d0efc9a5cfdf056cf2fd6d3af38a 100644 (file)
@@ -2,9 +2,10 @@
 // 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;
@@ -15,6 +16,7 @@ using System.Text;
 
 using Mono.Security;
 using Mono.Security.Cryptography;
+using Mono.Security.X509;
 
 [assembly: AssemblyTitle("Mono StrongName")]
 [assembly: AssemblyDescription("StrongName utility for signing assemblies")]
@@ -25,27 +27,34 @@ namespace Mono.Tools {
 
                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
@@ -100,7 +109,8 @@ namespace Mono.Tools {
                                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;
                                }
@@ -108,6 +118,144 @@ namespace Mono.Tools {
                        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);
@@ -121,9 +269,10 @@ namespace Mono.Tools {
                                        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);
@@ -132,44 +281,35 @@ namespace Mono.Tools {
                                        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"));
@@ -178,7 +318,7 @@ namespace Mono.Tools {
                        else
                                Header();
 
-                       bool config = LoadConfig ();
+                       LoadConfig (quiet);
 
                        StrongName sn = null;
                        AssemblyName an = null;
@@ -200,7 +340,16 @@ namespace Mono.Tools {
                                                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
@@ -219,16 +368,29 @@ namespace Mono.Tools {
                                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++]);
@@ -237,9 +399,9 @@ namespace Mono.Tools {
                                                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;
@@ -247,15 +409,22 @@ namespace Mono.Tools {
                                        // 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
@@ -274,23 +443,32 @@ namespace Mono.Tools {
                                        // 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");
@@ -299,6 +477,7 @@ namespace Mono.Tools {
                                        Console.WriteLine ("Unimplemented option");
                                        break;
                                case "-Vx":
+                                       // we must remove <verificationSettings> from each config files
                                        Console.WriteLine ("Unimplemented option");
                                        break;
                                case "-?":
@@ -312,5 +491,30 @@ namespace Mono.Tools {
                        }
                        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;
+               }
        }
 }