Merge pull request #4185 from BrzVlad/fix-arm64-finally-abort
[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.Length == 0 || 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, bool quiet) 
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                                 if (!quiet || !signed) {
192                                         Console.WriteLine (signed ? "Assembly {0} signed." : "Couldn't sign the assembly {0}.", 
193                                                            assemblyName);
194                                 }
195                                 return signed;
196                         }
197                         
198                         Console.WriteLine ("Couldn't sign the assembly {0} with this key pair. Public key of assembly did not match signing public key.", assemblyName);
199                         return false;
200                 }
201
202                 static int Verify (string assemblyName, bool forceVerification, bool quiet) 
203                 {
204                         // this doesn't load the assembly (well it unloads it ;)
205                         // http://weblogs.asp.net/nunitaddin/posts/9991.aspx
206                         AssemblyName an = null;
207                         try {
208                                 an = AssemblyName.GetAssemblyName (assemblyName);
209                         }
210                         catch {
211                         }
212                         if (an == null) {
213                                 Console.WriteLine ("Unable to load assembly: {0}", assemblyName);
214                                 return 2;
215                         }
216
217                         byte[] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ());
218                         if ((publicKey == null) || (publicKey.Length < 12)) {
219                                 // no mapping
220                                 publicKey = an.GetPublicKey ();
221                                 if ((publicKey == null) || (publicKey.Length < 12)) {
222                                         Console.WriteLine ("{0} is not a strongly named assembly.", assemblyName);
223                                         return 2;
224                                 }
225                         }
226
227                         // Note: MustVerify is based on the original token (by design). Public key
228                         // remapping won't affect if the assembly is verified or not.
229                         if (forceVerification || StrongNameManager.MustVerify (an)) {
230                                 RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey, 12);
231                                 StrongName sn = new StrongName (rsa);
232                                 if (sn.Verify (assemblyName)) {
233                                         if (!quiet)
234                                                 Console.WriteLine ("Assembly {0} is strongnamed.", assemblyName);
235                                         return 0;
236                                 }
237                                 else {
238                                         Console.WriteLine ("Assembly {0} is delay-signed but not strongnamed", assemblyName);
239                                         return 1;
240                                 }
241                         }
242                         else {
243                                 Console.WriteLine ("Assembly {0} is strongnamed (verification skipped).", assemblyName);
244                                 return 0;
245                         }
246                 }
247
248                 static bool Compare (byte[] value1, byte[] value2) 
249                 {
250                         if ((value1 == null) || (value2 == null))
251                                 return false;
252                         bool result = (value1.Length == value2.Length);
253                         if (result) {
254                                 for (int i=0; i < value1.Length; i++) {
255                                         if (value1 [i] != value2 [i])
256                                                 return false;
257                                 }
258                         }
259                         return result;
260                 }
261
262                 static void Help (string details) 
263                 {
264                         Console.WriteLine ("Usage: sn [-q | -quiet] options [parameters]{0}", Environment.NewLine);
265                         Console.WriteLine (" -q | -quiet    \tQuiet mode (minimal display){0}", Environment.NewLine);
266                         switch (details) {
267                                 case "config":
268                                         Console.WriteLine ("Configuration options <1>");
269                                         Console.WriteLine (" -c provider{0}\tChange the default CSP provider", Environment.NewLine);
270                                         Console.WriteLine (" -m [y|n]{0}\tUse a machine [y] key container or user key container [n]", Environment.NewLine);
271                                         Console.WriteLine (" -Vl{0}\tList the verification options", Environment.NewLine);
272                                         Console.WriteLine (" -Vr assembly [userlist]{0}\tExempt the specified assembly from verification for the user list", Environment.NewLine);
273                                         Console.WriteLine (" -Vu assembly{0}\tRemove exemption entry for the specified assembly", Environment.NewLine);
274                                         Console.WriteLine (" -Vx{0}\tRemove all exemptions entries", Environment.NewLine);
275                                         Console.WriteLine ("{0}<1> Currently not implemented in the tool", Environment.NewLine);
276                                         break;
277                                 case "csp":
278                                         Console.WriteLine ("CSP related options");
279                                         Console.WriteLine (" -d container{0}\tDelete the specified key container", Environment.NewLine);
280                                         Console.WriteLine (" -i keypair.snk container{0}\tImport the keypair from a SNK file into a CSP container", Environment.NewLine);
281                                         Console.WriteLine (" -pc container public.key{0}\tExport the public key from a CSP container to the specified file", Environment.NewLine);
282                                         break;
283                                 case "convert":
284                                         Console.WriteLine ("Convertion options");
285                                         Console.WriteLine (" -e assembly output.pub{0}\tExport the assembly public key to the specified file", Environment.NewLine);
286                                         Console.WriteLine (" -p keypair.snk output.pub{0}\tExport the public key from a SNK file to the specified file", Environment.NewLine);
287                                         Console.WriteLine (" -o input output.txt{0}\tConvert the input file to a CSV file (using decimal).", Environment.NewLine);
288                                         Console.WriteLine (" -oh input output.txt{0}\tConvert the input file to a CSV file (using hexadecimal).", Environment.NewLine);
289                                         break;
290                                 case "sn":
291                                         Console.WriteLine ("StrongName signing options");
292                                         Console.WriteLine (" -D assembly1 assembly2{0}\tCompare assembly1 and assembly2 (without signatures)", Environment.NewLine);
293                                         Console.WriteLine (" -k keypair.snk{0}\tCreate a new keypair in the specified file", Environment.NewLine);
294                                         Console.WriteLine (" -R assembly keypair.snk{0}\tResign the assembly with the specified StrongName key file", Environment.NewLine);
295                                         Console.WriteLine (" -Rc assembly container{0}\tResign the assembly with the specified CSP container", Environment.NewLine);
296                                         Console.WriteLine (" -t file{0}\tShow the public key token from the specified file", Environment.NewLine);
297                                         Console.WriteLine (" -tp file{0}\tShow the public key and pk token from the specified file", Environment.NewLine);
298                                         Console.WriteLine (" -T assembly{0}\tShow the public key token from the specified assembly", Environment.NewLine);
299                                         Console.WriteLine (" -Tp assembly{0}\tShow the public key and pk token from the specified assembly", Environment.NewLine);
300                                         Console.WriteLine (" -v assembly{0}\tVerify the specified assembly signature", Environment.NewLine);
301                                         Console.WriteLine (" -vf assembly{0}\tVerify the specified assembly signature (even if disabled).", Environment.NewLine);
302                                         break;
303                                 default:
304                                         Console.WriteLine ("Help options");
305                                         Console.WriteLine (" -? | -h        \tShow this help screen about the tool");
306                                         Console.WriteLine (" -? | -h config \tConfiguration options");
307                                         Console.WriteLine (" -? | -h csp    \tCrypto Service Provider (CSP) related options");
308                                         Console.WriteLine (" -? | -h convert\tFormat convertion options");
309                                         Console.WriteLine (" -? | -h sn     \tStrongName signing options");
310                                         break;
311                         }
312                 }
313
314                 static int Process (string[] args)
315                 {
316                         int i = 0;
317                         string param = args [i];
318                         bool quiet = ((param == "-quiet") || (param == "-q"));
319                         if (quiet)
320                                 i++;
321                         else
322                                 Header();
323
324                         LoadConfig (quiet);
325
326                         StrongName sn = null;
327                         AssemblyName an = null;
328                         RSACryptoServiceProvider rsa = null;
329                         CspParameters csp = new CspParameters ();
330                         csp.ProviderName = defaultCSP;
331
332                         switch (args [i++]) {
333                                 case "-c":
334                                         // Change global CSP provider options
335                                         defaultCSP = args [i];
336                                         return SaveConfig ();
337                                 case "-d":
338                                         // Delete specified key container
339                                         csp.KeyContainerName = args [i];
340                                         rsa = new RSACryptoServiceProvider (csp);
341                                         rsa.PersistKeyInCsp = false;
342                                         if (!quiet)
343                                                 Console.WriteLine ("Keypair in container {0} has been deleted", args [i]);
344                                         break;
345                                 case "-D":
346                                         StrongName a1 = new StrongName ();
347                                         byte[] h1 = a1.Hash (args [i++]);
348                                         StrongName a2 = new StrongName ();
349                                         byte[] h2 = a2.Hash (args [i++]);
350                                         if (Compare (h1, h2)) {
351                                                 Console.WriteLine ("Both assembly are identical (same digest for metadata)");
352                                                 // TODO: if equals then compare signatures
353                                         }
354                                         else
355                                                 Console.WriteLine ("Assemblies are not identical (different digest for metadata)");
356                                         break;
357                                 case "-e":
358                                         // Export public key from assembly
359                                         an = AssemblyName.GetAssemblyName (args [i++]);
360                                         WriteToFile (args[i], an.GetPublicKey ());
361                                         if (!quiet)
362                                                 Console.WriteLine ("Public Key extracted to file {0}", args [i]);
363                                         break;
364                                 case "-i":
365                                         // import keypair from SNK to container
366                                         sn = new StrongName (ReadFromFile (args [i++]));
367                                         csp.KeyContainerName = args [i];
368                                         rsa = new RSACryptoServiceProvider (csp);
369                                         rsa.ImportParameters (sn.RSA.ExportParameters (true));
370                                         break;
371                                 case "-k":
372                                         // Create a new strong name key pair
373                                         // (a new RSA keypair automagically if none is present)
374                                         int size = 1024;
375                                         if (i < args.Length + 2) {
376                                                 try {
377                                                         size = Int32.Parse (args[i++]);
378                                                 }
379                                                 catch {
380                                                         // oops, that wasn't a valid key size (assume 1024 bits)
381                                                         i--;
382                                                 }
383                                         }
384                                         sn = new StrongName (size);
385                                         WriteToFile (args[i], CryptoConvert.ToCapiKeyBlob (sn.RSA, true));
386                                         if (!quiet)
387                                                 Console.WriteLine ("A new {0} bits strong name keypair has been generated in file '{1}'.", size, args [i]);
388                                         break;
389                                 case "-m":
390                                         Console.WriteLine ("Unimplemented option");
391                                         break;
392                                 case "-o":
393                                         byte[] infileD = ReadFromFile (args [i++]);
394                                         WriteCSVToFile (args [i], infileD, "D");
395                                         if (!quiet)
396                                                 Console.WriteLine ("Output CSV file is {0} (decimal format)", args [i]);
397                                         break;
398                                 case "-oh":
399                                         byte[] infileX2 = ReadFromFile (args [i++]);
400                                         WriteCSVToFile (args [i], infileX2, "X2");
401                                         if (!quiet)
402                                                 Console.WriteLine ("Output CVS file is {0} (hexadecimal format)", args [i]);
403                                         break;
404                                 case "-p":
405                                         // Extract public key from SNK or PKCS#12/PFX file
406                                         sn = new StrongName (GetKeyFromFile (args [i++]));
407                                         WriteToFile (args[i], sn.PublicKey);
408                                         if (!quiet)
409                                                 Console.WriteLine ("Public Key extracted to file {0}", args [i]);
410                                         break;
411                                 case "-pc":
412                                         // Extract public key from container
413                                         csp.KeyContainerName = args [i++];
414                                         rsa = new RSACryptoServiceProvider (csp);
415                                         sn = new StrongName (rsa);
416                                         WriteToFile (args[i], sn.PublicKey);
417                                         if (!quiet)
418                                                 Console.WriteLine ("Public Key extracted to file {0}", args [i]);
419                                         break;
420                                 case "-R":
421                                         string filename = args [i++];
422                                         if (! ReSign (filename, GetKeyFromFile (args [i]), quiet))
423                                                 return 1;
424                                         break;
425                                 case "-Rc":
426                                         filename = args [i++];
427                                         csp.KeyContainerName = args [i];
428                                         rsa = new RSACryptoServiceProvider (csp);
429                                         if (! ReSign (filename, rsa, quiet))
430                                                 return 1;
431                                         break;
432                                 case "-t":
433                                         // Show public key token from file
434                                         sn = new StrongName (ReadFromFile (args [i]));
435                                         // note: ignore quiet
436                                         Console.WriteLine ("Public Key Token: " + ToString (sn.PublicKeyToken), Environment.NewLine);
437                                         break;
438                                 case "-tp":
439                                         // Show public key and public key token from assembly
440                                         sn = new StrongName (ReadFromFile (args [i]));
441                                         // note: ignore quiet
442                                         Console.WriteLine ("Public Key:" + ToString (sn.PublicKey));
443                                         Console.WriteLine ("{0}Public Key Token: " + ToString (sn.PublicKeyToken), Environment.NewLine);
444                                         break;
445                                 case "-T":
446                                         // Show public key token from assembly
447                                         an = AssemblyName.GetAssemblyName (args [i++]);
448                                         // note: ignore quiet
449                                         byte [] pkt = an.GetPublicKeyToken ();
450                                         if (pkt == null) {
451                                                 Console.WriteLine ("{0} does not represent a strongly named assembly.", args [i - 1]);
452                                         } else {
453                                                 Console.WriteLine ("Public Key Token: " + ToString (pkt));
454                                         }
455                                         break;
456                                 case "-Tp":
457                                         // Show public key and public key token from assembly
458                                         an = AssemblyName.GetAssemblyName (args [i++]);
459                                         byte [] token = an.GetPublicKeyToken ();
460                                         if (token == null) {
461                                                 Console.WriteLine ("{0} does not represent a strongly named assembly.", args [i - 1]);
462                                         } else {
463                                                 Console.WriteLine ("Public Key:" + ToString (an.GetPublicKey ()));
464                                                 Console.WriteLine ("{0}Public Key Token: " + ToString (token), Environment.NewLine);
465                                         }
466                                         break;
467                                 case "-v":
468                                         filename = args [i++];
469                                         return Verify (filename, false, quiet);
470                                 case "-vf":
471                                         filename = args [i++];
472                                         return Verify (filename, true, quiet);  // force verification
473                                 case "-Vl":
474                                         Console.WriteLine (new StrongNameManager ().ToString ());
475                                         break;
476                                 case "-Vr":
477                                         Console.WriteLine ("Unimplemented option");
478                                         break;
479                                 case "-Vu":
480                                         Console.WriteLine ("Unimplemented option");
481                                         break;
482                                 case "-Vx":
483                                         // we must remove <verificationSettings> from each config files
484                                         Console.WriteLine ("Unimplemented option");
485                                         break;
486                                 case "-?":
487                                 case "-h":
488                                         Help ((i < args.Length) ? args [i] : null);
489                                         break;
490                                 default:
491                                         if (!quiet)
492                                                 Console.WriteLine ("Unknown option {0}", args [i-1]);
493                                         return 1;
494                         }
495                         return 0;
496                 }
497
498                 [STAThread]
499                 static int Main (string[] args)
500                 {
501                         try {
502                                 if (args.Length < 1) {
503                                         Header ();
504                                         Help (null);
505                                 } else {
506                                         return Process (args);
507                                 }
508                         }
509                         catch (IndexOutOfRangeException) {
510                                 Console.WriteLine ("ERROR: Invalid number of parameters.{0}", Environment.NewLine);
511                                 Help (null);
512                         }
513                         catch (CryptographicException ce) {
514                                 Console.WriteLine ("ERROR: {0}", ce.Message);
515                         }
516                         catch (Exception e) {
517                                 Console.WriteLine ("ERROR: Unknown error during processing: {0}", e);
518                         }
519
520                         return 1;
521                 }
522         }
523 }