Null constant cannot be used for ref/out variables
[mono.git] / mcs / tools / security / sn.cs
1 //
2 // SN.cs: sn clone tool
3 //
4 // Author:
5 //      Sebastien Pouliot  <sebastien@ximian.com>
6 //
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004-2006,2008 Novell, Inc (http://www.novell.com)
9 //
10
11 using System;
12 using System.IO;
13 using System.Reflection;
14 using System.Security.Cryptography;
15 using System.Text;
16
17 using Mono.Security;
18 using Mono.Security.Cryptography;
19 using Mono.Security.X509;
20
21 [assembly: AssemblyTitle("Mono StrongName")]
22 [assembly: AssemblyDescription("StrongName utility for signing assemblies")]
23
24 namespace Mono.Tools {
25
26         class SN {
27
28                 static private void Header () 
29                 {
30                         Console.WriteLine (new AssemblyInfo ().ToString ());
31                 }
32
33                 static string defaultCSP;
34
35                 static bool LoadConfig (bool quiet) 
36                 {
37                         MethodInfo config = typeof (System.Environment).GetMethod ("GetMachineConfigPath",
38                                 BindingFlags.Static|BindingFlags.NonPublic);
39
40                         if (config != null) {
41                                 string path = (string) config.Invoke (null, null);
42
43                                 bool exist = File.Exists (path);
44                                 if (!quiet && !exist)
45                                         Console.WriteLine ("Couldn't find machine.config");
46
47                                 StrongNameManager.LoadConfig (path);
48                                 return exist;
49                         }
50                         else if (!quiet)
51                                 Console.WriteLine ("Couldn't resolve machine.config location (corlib issue)");
52                         
53                         // default CSP
54                         return false;
55                 }
56
57                 // TODO
58                 static int SaveConfig () 
59                 {
60                         // default CSP
61                         return 1;
62                 }
63
64                 static byte[] ReadFromFile (string fileName) 
65                 {
66                         byte[] data = null;
67                         FileStream fs = File.Open (fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
68                         try {
69                                 data = new byte [fs.Length];
70                                 fs.Read (data, 0, data.Length);
71                         }
72                         finally {
73                                 fs.Close ();
74                         }
75                         return data;
76                 }
77
78                 static void WriteToFile (string fileName, byte[] data) 
79                 {
80                         FileStream fs = File.Open (fileName, FileMode.Create, FileAccess.Write);
81                         try {
82                                 fs.Write (data, 0, data.Length);
83                         }
84                         finally {
85                                 fs.Close ();
86                         }
87                 }
88
89                 static void WriteCSVToFile (string fileName, byte[] data, string mask) 
90                 {
91                         StreamWriter sw = File.CreateText (fileName);
92                         try {
93                                 for (int i=0; i < data.Length; i++) {
94                                         if (mask [0] == 'X')
95                                                 sw.Write ("0x");
96                                         sw.Write (data [i].ToString (mask));
97                                         sw.Write (", ");
98                                 }
99                         }
100                         finally {
101                                 sw.Close ();
102                         }
103                 }
104
105                 static string ToString (byte[] data) 
106                 {
107                         StringBuilder sb = new StringBuilder ();
108                         for (int i=0; i < data.Length; i++) {
109                                 if ((i % 39 == 0) && (data.Length > 39))
110                                         sb.Append (Environment.NewLine);
111                                 sb.Append (data [i].ToString ("x2"));
112                                 if (i > 2080) {
113                                         // ensure we can display up to 16384 bits keypair
114                                         sb.Append (" !!! TOO LONG !!!");
115                                         break;
116                                 }
117                         }
118                         return sb.ToString ();
119                 }
120
121                 static RSA GetKeyFromFile (string filename)
122                 {
123                         byte[] data = ReadFromFile (filename);
124                         try {
125                                 // for SNK files (including the ECMA pseudo-key)
126                                 return new StrongName (data).RSA;
127                         }
128                         catch {
129                                 if (data [0] != 0x30)
130                                         throw;
131                                 // this could be a PFX file
132                                 Console.Write ("Enter password for private key (will be visible when typed): ");
133                                 PKCS12 pfx = new PKCS12 (data, Console.ReadLine ());
134                                 // works only if a single key is present
135                                 if (pfx.Keys.Count != 1)
136                                         throw;
137                                 RSA rsa = (pfx.Keys [0] as RSA);
138                                 if (rsa == null)
139                                         throw;
140                                 return rsa;
141                         }
142                 }
143 #if false
144                 // is assembly signed (or delayed signed) ?
145                 static bool IsStrongNamed (Assembly assembly) 
146                 {
147                         if (assembly == null)
148                                 return false;
149
150                         object[] attrs = assembly.GetCustomAttributes (true);
151                         foreach (object o in attrs) {
152                                 if (o is AssemblyKeyFileAttribute)
153                                         return true;
154                                 else if (o is AssemblyKeyNameAttribute)
155                                         return true;
156                         }
157                         return false;
158                 }
159 #endif
160                 static bool ReSign (string assemblyName, RSA key) 
161                 {
162                         // this doesn't load the assembly (well it unloads it ;)
163                         // http://weblogs.asp.net/nunitaddin/posts/9991.aspx
164                         AssemblyName an = null;
165                         try {
166                                 an = AssemblyName.GetAssemblyName (assemblyName);
167                         }
168                         catch {
169                         }
170                         if (an == null) {
171                                 Console.WriteLine ("Unable to load assembly: {0}", assemblyName);
172                                 return false;
173                         }
174
175                         StrongName sign = new StrongName (key);
176                         byte[] token = an.GetPublicKeyToken ();
177
178                         // first, try to compare using a mapped public key (e.g. ECMA)
179                         bool same = Compare (sign.PublicKey, StrongNameManager.GetMappedPublicKey (token));
180                         if (!same) {
181                                 // second, try to compare using the assembly public key
182                                 same = Compare (sign.PublicKey, an.GetPublicKey ());
183                                 if (!same) {
184                                         // third (and last) chance, try to compare public key token
185                                         same = Compare (sign.PublicKeyToken, token);
186                                 }
187                         }
188
189                         if (same) {
190                                 bool signed = sign.Sign (assemblyName);
191                                 Console.WriteLine (signed ? "Assembly {0} signed." : "Couldn't sign the assembly {0}.", 
192                                                    assemblyName);
193                                 return signed;
194                         }
195                         
196                         Console.WriteLine ("Couldn't sign the assembly {0} with this key pair.", assemblyName);
197                         return false;
198                 }
199
200                 static int Verify (string assemblyName, bool forceVerification) 
201                 {
202                         // this doesn't load the assembly (well it unloads it ;)
203                         // http://weblogs.asp.net/nunitaddin/posts/9991.aspx
204                         AssemblyName an = null;
205                         try {
206                                 an = AssemblyName.GetAssemblyName (assemblyName);
207                         }
208                         catch {
209                         }
210                         if (an == null) {
211                                 Console.WriteLine ("Unable to load assembly: {0}", assemblyName);
212                                 return 2;
213                         }
214
215                         byte[] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ());
216                         if ((publicKey == null) || (publicKey.Length < 12)) {
217                                 // no mapping
218                                 publicKey = an.GetPublicKey ();
219                                 if ((publicKey == null) || (publicKey.Length < 12)) {
220                                         Console.WriteLine ("{0} is not a strongly named assembly.", assemblyName);
221                                         return 2;
222                                 }
223                         }
224
225                         // Note: MustVerify is based on the original token (by design). Public key
226                         // remapping won't affect if the assembly is verified or not.
227                         if (forceVerification || StrongNameManager.MustVerify (an)) {
228                                 RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey, 12);
229                                 StrongName sn = new StrongName (rsa);
230                                 if (sn.Verify (assemblyName)) {
231                                         Console.WriteLine ("Assembly {0} is strongnamed.", assemblyName);
232                                         return 0;
233                                 }
234                                 else {
235                                         Console.WriteLine ("Assembly {0} is delay-signed but not strongnamed", assemblyName);
236                                         return 1;
237                                 }
238                         }
239                         else {
240                                 Console.WriteLine ("Assembly {0} is strongnamed (verification skipped).", assemblyName);
241                                 return 0;
242                         }
243                 }
244
245                 static bool Compare (byte[] value1, byte[] value2) 
246                 {
247                         if ((value1 == null) || (value2 == null))
248                                 return false;
249                         bool result = (value1.Length == value2.Length);
250                         if (result) {
251                                 for (int i=0; i < value1.Length; i++) {
252                                         if (value1 [i] != value2 [i])
253                                                 return false;
254                                 }
255                         }
256                         return result;
257                 }
258
259                 static void Help (string details) 
260                 {
261                         Console.WriteLine ("Usage: sn [-q | -quiet] options [parameters]{0}", Environment.NewLine);
262                         Console.WriteLine (" -q | -quiet    \tQuiet mode (minimal display){0}", Environment.NewLine);
263                         switch (details) {
264                                 case "config":
265                                         Console.WriteLine ("Configuration options <1>");
266                                         Console.WriteLine (" -c provider{0}\tChange the default CSP provider", Environment.NewLine);
267                                         Console.WriteLine (" -m [y|n]{0}\tUse a machine [y] key container or user key container [n]", Environment.NewLine);
268                                         Console.WriteLine (" -Vl{0}\tList the verification options", Environment.NewLine);
269                                         Console.WriteLine (" -Vr assembly [userlist]{0}\tExempt the specified assembly from verification for the user list", Environment.NewLine);
270                                         Console.WriteLine (" -Vu assembly{0}\tRemove exemption entry for the specified assembly", Environment.NewLine);
271                                         Console.WriteLine (" -Vx{0}\tRemove all exemptions entries", Environment.NewLine);
272                                         Console.WriteLine ("{0}<1> Currently not implemented in the tool", Environment.NewLine);
273                                         break;
274                                 case "csp":
275                                         Console.WriteLine ("CSP related options");
276                                         Console.WriteLine (" -d container{0}\tDelete the specified key container", Environment.NewLine);
277                                         Console.WriteLine (" -i keypair.snk container{0}\tImport the keypair from a SNK file into a CSP container", Environment.NewLine);
278                                         Console.WriteLine (" -pc container public.key{0}\tExport the public key from a CSP container to the specified file", Environment.NewLine);
279                                         break;
280                                 case "convert":
281                                         Console.WriteLine ("Convertion options");
282                                         Console.WriteLine (" -e assembly output.pub{0}\tExport the assembly public key to the specified file", Environment.NewLine);
283                                         Console.WriteLine (" -p keypair.snk output.pub{0}\tExport the public key from a SNK file to the specified file", Environment.NewLine);
284                                         Console.WriteLine (" -o input output.txt{0}\tConvert the input file to a CSV file (using decimal).", Environment.NewLine);
285                                         Console.WriteLine (" -oh input output.txt{0}\tConvert the input file to a CSV file (using hexadecimal).", Environment.NewLine);
286                                         break;
287                                 case "sn":
288                                         Console.WriteLine ("StrongName signing options");
289                                         Console.WriteLine (" -D assembly1 assembly2{0}\tCompare assembly1 and assembly2 (without signatures)", Environment.NewLine);
290                                         Console.WriteLine (" -k keypair.snk{0}\tCreate a new keypair in the specified file", Environment.NewLine);
291                                         Console.WriteLine (" -R assembly keypair.snk{0}\tResign the assembly with the specified StrongName key file", Environment.NewLine);
292                                         Console.WriteLine (" -Rc assembly container{0}\tResign the assembly with the specified CSP container", Environment.NewLine);
293                                         Console.WriteLine (" -t file{0}\tShow the public key token from the specified file", Environment.NewLine);
294                                         Console.WriteLine (" -tp file{0}\tShow the public key and pk token from the specified file", Environment.NewLine);
295                                         Console.WriteLine (" -T assembly{0}\tShow the public key token from the specified assembly", Environment.NewLine);
296                                         Console.WriteLine (" -Tp assembly{0}\tShow the public key and pk token from the specified assembly", Environment.NewLine);
297                                         Console.WriteLine (" -v assembly{0}\tVerify the specified assembly signature", Environment.NewLine);
298                                         Console.WriteLine (" -vf assembly{0}\tVerify the specified assembly signature (even if disabled).", Environment.NewLine);
299                                         break;
300                                 default:
301                                         Console.WriteLine ("Help options");
302                                         Console.WriteLine (" -? | -h        \tShow this help screen about the tool");
303                                         Console.WriteLine (" -? | -h config \tConfiguration options");
304                                         Console.WriteLine (" -? | -h csp    \tCrypto Service Provider (CSP) related options");
305                                         Console.WriteLine (" -? | -h convert\tFormat convertion options");
306                                         Console.WriteLine (" -? | -h sn     \tStrongName signing options");
307                                         break;
308                         }
309                 }
310
311                 static int Process (string[] args)
312                 {
313                         int i = 0;
314                         string param = args [i];
315                         bool quiet = ((param == "-quiet") || (param == "-q"));
316                         if (quiet)
317                                 i++;
318                         else
319                                 Header();
320
321                         LoadConfig (quiet);
322
323                         StrongName sn = null;
324                         AssemblyName an = null;
325                         RSACryptoServiceProvider rsa = null;
326                         CspParameters csp = new CspParameters ();
327                         csp.ProviderName = defaultCSP;
328
329                         switch (args [i++]) {
330                                 case "-c":
331                                         // Change global CSP provider options
332                                         defaultCSP = args [i];
333                                         return SaveConfig ();
334                                 case "-d":
335                                         // Delete specified key container
336                                         csp.KeyContainerName = args [i];
337                                         rsa = new RSACryptoServiceProvider (csp);
338                                         rsa.PersistKeyInCsp = false;
339                                         if (!quiet)
340                                                 Console.WriteLine ("Keypair in container {0} has been deleted", args [i]);
341                                         break;
342                                 case "-D":
343                                         StrongName a1 = new StrongName ();
344                                         byte[] h1 = a1.Hash (args [i++]);
345                                         StrongName a2 = new StrongName ();
346                                         byte[] h2 = a2.Hash (args [i++]);
347                                         if (Compare (h1, h2)) {
348                                                 Console.WriteLine ("Both assembly are identical (same digest for metadata)");
349                                                 // TODO: if equals then compare signatures
350                                         }
351                                         else
352                                                 Console.WriteLine ("Assemblies are not identical (different digest for metadata)");
353                                         break;
354                                 case "-e":
355                                         // Export public key from assembly
356                                         an = AssemblyName.GetAssemblyName (args [i++]);
357                                         WriteToFile (args[i], an.GetPublicKey ());
358                                         if (!quiet)
359                                                 Console.WriteLine ("Public Key extracted to file {0}", args [i]);
360                                         break;
361                                 case "-i":
362                                         // import keypair from SNK to container
363                                         sn = new StrongName (ReadFromFile (args [i++]));
364                                         csp.KeyContainerName = args [i];
365                                         rsa = new RSACryptoServiceProvider (csp);
366                                         rsa.ImportParameters (sn.RSA.ExportParameters (true));
367                                         break;
368                                 case "-k":
369                                         // Create a new strong name key pair
370                                         // (a new RSA keypair automagically if none is present)
371                                         int size = 1024;
372                                         if (i < args.Length + 2) {
373                                                 try {
374                                                         size = Int32.Parse (args[i++]);
375                                                 }
376                                                 catch {
377                                                         // oops, that wasn't a valid key size (assume 1024 bits)
378                                                         i--;
379                                                 }
380                                         }
381                                         sn = new StrongName (size);
382                                         WriteToFile (args[i], CryptoConvert.ToCapiKeyBlob (sn.RSA, true));
383                                         if (!quiet)
384                                                 Console.WriteLine ("A new {0} bits strong name keypair has been generated in file '{1}'.", size, args [i]);
385                                         break;
386                                 case "-m":
387                                         Console.WriteLine ("Unimplemented option");
388                                         break;
389                                 case "-o":
390                                         byte[] infileD = ReadFromFile (args [i++]);
391                                         WriteCSVToFile (args [i], infileD, "D");
392                                         if (!quiet)
393                                                 Console.WriteLine ("Output CSV file is {0} (decimal format)", args [i]);
394                                         break;
395                                 case "-oh":
396                                         byte[] infileX2 = ReadFromFile (args [i++]);
397                                         WriteCSVToFile (args [i], infileX2, "X2");
398                                         if (!quiet)
399                                                 Console.WriteLine ("Output CVS file is {0} (hexadecimal format)", args [i]);
400                                         break;
401                                 case "-p":
402                                         // Extract public key from SNK or PKCS#12/PFX file
403                                         sn = new StrongName (GetKeyFromFile (args [i++]));
404                                         WriteToFile (args[i], sn.PublicKey);
405                                         if (!quiet)
406                                                 Console.WriteLine ("Public Key extracted to file {0}", args [i]);
407                                         break;
408                                 case "-pc":
409                                         // Extract public key from container
410                                         csp.KeyContainerName = args [i++];
411                                         rsa = new RSACryptoServiceProvider (csp);
412                                         sn = new StrongName (rsa);
413                                         WriteToFile (args[i], sn.PublicKey);
414                                         if (!quiet)
415                                                 Console.WriteLine ("Public Key extracted to file {0}", args [i]);
416                                         break;
417                                 case "-R":
418                                         string filename = args [i++];
419                                         if (! ReSign (filename, GetKeyFromFile (args [i])))
420                                                 return 1;
421                                         break;
422                                 case "-Rc":
423                                         filename = args [i++];
424                                         csp.KeyContainerName = args [i];
425                                         rsa = new RSACryptoServiceProvider (csp);
426                                         if (! ReSign (filename, rsa))
427                                                 return 1;
428                                         break;
429                                 case "-t":
430                                         // Show public key token from file
431                                         sn = new StrongName (ReadFromFile (args [i]));
432                                         // note: ignore quiet
433                                         Console.WriteLine ("Public Key Token: " + ToString (sn.PublicKeyToken), Environment.NewLine);
434                                         break;
435                                 case "-tp":
436                                         // Show public key and public key token from assembly
437                                         sn = new StrongName (ReadFromFile (args [i]));
438                                         // note: ignore quiet
439                                         Console.WriteLine ("Public Key:" + ToString (sn.PublicKey));
440                                         Console.WriteLine ("{0}Public Key Token: " + ToString (sn.PublicKeyToken), Environment.NewLine);
441                                         break;
442                                 case "-T":
443                                         // Show public key token from assembly
444                                         an = AssemblyName.GetAssemblyName (args [i++]);
445                                         // note: ignore quiet
446                                         byte [] pkt = an.GetPublicKeyToken ();
447                                         if (pkt == null) {
448                                                 Console.WriteLine ("{0} does not represent a strongly named assembly.", args [i - 1]);
449                                         } else {
450                                                 Console.WriteLine ("Public Key Token: " + ToString (pkt));
451                                         }
452                                         break;
453                                 case "-Tp":
454                                         // Show public key and public key token from assembly
455                                         an = AssemblyName.GetAssemblyName (args [i++]);
456                                         byte [] token = an.GetPublicKeyToken ();
457                                         if (token == null) {
458                                                 Console.WriteLine ("{0} does not represent a strongly named assembly.", args [i - 1]);
459                                         } else {
460                                                 Console.WriteLine ("Public Key:" + ToString (an.GetPublicKey ()));
461                                                 Console.WriteLine ("{0}Public Key Token: " + ToString (token), Environment.NewLine);
462                                         }
463                                         break;
464                                 case "-v":
465                                         filename = args [i++];
466                                         return Verify (filename, false);
467                                 case "-vf":
468                                         filename = args [i++];
469                                         return Verify (filename, true); // force verification
470                                 case "-Vl":
471                                         Console.WriteLine (new StrongNameManager ().ToString ());
472                                         break;
473                                 case "-Vr":
474                                         Console.WriteLine ("Unimplemented option");
475                                         break;
476                                 case "-Vu":
477                                         Console.WriteLine ("Unimplemented option");
478                                         break;
479                                 case "-Vx":
480                                         // we must remove <verificationSettings> from each config files
481                                         Console.WriteLine ("Unimplemented option");
482                                         break;
483                                 case "-?":
484                                 case "-h":
485                                         Help ((i < args.Length) ? args [i] : null);
486                                         break;
487                                 default:
488                                         if (!quiet)
489                                                 Console.WriteLine ("Unknown option {0}", args [i-1]);
490                                         return 1;
491                         }
492                         return 0;
493                 }
494
495                 [STAThread]
496                 static int Main (string[] args)
497                 {
498                         try {
499                                 if (args.Length < 1) {
500                                         Header ();
501                                         Help (null);
502                                 } else {
503                                         return Process (args);
504                                 }
505                         }
506                         catch (IndexOutOfRangeException) {
507                                 Console.WriteLine ("ERROR: Invalid number of parameters.{0}", Environment.NewLine);
508                                 Help (null);
509                         }
510                         catch (CryptographicException ce) {
511                                 Console.WriteLine ("ERROR: {0}", ce.Message);
512                         }
513                         catch (Exception e) {
514                                 Console.WriteLine ("ERROR: Unknown error during processing: {0}", e);
515                         }
516
517                         return 1;
518                 }
519         }
520 }