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