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