2010-05-25 Jb Evain <jbevain@novell.com>
[mcs.git] / tools / security / sn.cs
blob28ee366d4e46d0efc9a5cfdf056cf2fd6d3af38a
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 //
11 using System;
12 using System.IO;
13 using System.Reflection;
14 using System.Security.Cryptography;
15 using System.Text;
17 using Mono.Security;
18 using Mono.Security.Cryptography;
19 using Mono.Security.X509;
21 [assembly: AssemblyTitle("Mono StrongName")]
22 [assembly: AssemblyDescription("StrongName utility for signing assemblies")]
24 namespace Mono.Tools {
26 class SN {
28 static private void Header ()
30 Console.WriteLine (new AssemblyInfo ().ToString ());
33 static string defaultCSP;
35 static bool LoadConfig (bool quiet)
37 MethodInfo config = typeof (System.Environment).GetMethod ("GetMachineConfigPath",
38 BindingFlags.Static|BindingFlags.NonPublic);
40 if (config != null) {
41 string path = (string) config.Invoke (null, null);
43 bool exist = File.Exists (path);
44 if (!quiet && !exist)
45 Console.WriteLine ("Couldn't find machine.config");
47 StrongNameManager.LoadConfig (path);
48 return exist;
50 else if (!quiet)
51 Console.WriteLine ("Couldn't resolve machine.config location (corlib issue)");
53 // default CSP
54 return false;
57 // TODO
58 static int SaveConfig ()
60 // default CSP
61 return 1;
64 static byte[] ReadFromFile (string fileName)
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);
72 finally {
73 fs.Close ();
75 return data;
78 static void WriteToFile (string fileName, byte[] data)
80 FileStream fs = File.Open (fileName, FileMode.Create, FileAccess.Write);
81 try {
82 fs.Write (data, 0, data.Length);
84 finally {
85 fs.Close ();
89 static void WriteCSVToFile (string fileName, byte[] data, string mask)
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 (", ");
100 finally {
101 sw.Close ();
105 static string ToString (byte[] data)
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;
118 return sb.ToString ();
121 static RSA GetKeyFromFile (string filename)
123 byte[] data = ReadFromFile (filename);
124 try {
125 // for SNK files (including the ECMA pseudo-key)
126 return new StrongName (data).RSA;
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;
143 #if false
144 // is assembly signed (or delayed signed) ?
145 static bool IsStrongNamed (Assembly assembly)
147 if (assembly == null)
148 return false;
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;
157 return false;
159 #endif
160 static bool ReSign (string assemblyName, RSA key)
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);
168 catch {
170 if (an == null) {
171 Console.WriteLine ("Unable to load assembly: {0}", assemblyName);
172 return false;
175 StrongName sign = new StrongName (key);
176 byte[] token = an.GetPublicKeyToken ();
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);
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;
196 Console.WriteLine ("Couldn't sign the assembly {0} with this key pair.", assemblyName);
197 return false;
200 static int Verify (string assemblyName, bool forceVerification)
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);
208 catch {
210 if (an == null) {
211 Console.WriteLine ("Unable to load assembly: {0}", assemblyName);
212 return 2;
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;
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;
234 else {
235 Console.WriteLine ("Assembly {0} is delay-signed but not strongnamed", assemblyName);
236 return 1;
239 else {
240 Console.WriteLine ("Assembly {0} is strongnamed (verification skipped).", assemblyName);
241 return 0;
245 static bool Compare (byte[] value1, byte[] value2)
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;
256 return result;
259 static void Help (string details)
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;
311 static int Process (string[] args)
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();
321 LoadConfig (quiet);
323 StrongName sn = null;
324 AssemblyName an = null;
325 RSACryptoServiceProvider rsa = null;
326 CspParameters csp = new CspParameters ();
327 csp.ProviderName = defaultCSP;
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
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++]);
376 catch {
377 // oops, that wasn't a valid key size (assume 1024 bits)
378 i--;
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));
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);
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;
492 return 0;
495 [STAThread]
496 static int Main (string[] args)
498 try {
499 if (args.Length < 1) {
500 Header ();
501 Help (null);
502 } else {
503 return Process (args);
506 catch (IndexOutOfRangeException) {
507 Console.WriteLine ("ERROR: Invalid number of parameters.{0}", Environment.NewLine);
508 Help (null);
510 catch (CryptographicException ce) {
511 Console.WriteLine ("ERROR: {0}", ce.Message);
513 catch (Exception e) {
514 Console.WriteLine ("ERROR: Unknown error during processing: {0}", e);
517 return 1;