2 // SN.cs: sn clone tool
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004-2006,2008 Novell, Inc (http://www.novell.com)
13 using System.Reflection;
14 using System.Security.Cryptography;
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 {
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);
41 string path = (string) config.Invoke (null, null);
43 bool exist = File.Exists (path);
45 Console.WriteLine ("Couldn't find machine.config");
47 StrongNameManager.LoadConfig (path);
51 Console.WriteLine ("Couldn't resolve machine.config location (corlib issue)");
58 static int SaveConfig ()
64 static byte[] ReadFromFile (string fileName)
67 FileStream fs = File.Open (fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
69 data = new byte [fs.Length];
70 fs.Read (data, 0, data.Length);
78 static void WriteToFile (string fileName, byte[] data)
80 FileStream fs = File.Open (fileName, FileMode.Create, FileAccess.Write);
82 fs.Write (data, 0, data.Length);
89 static void WriteCSVToFile (string fileName, byte[] data, string mask)
91 StreamWriter sw = File.CreateText (fileName);
93 for (int i=0; i < data.Length; i++) {
96 sw.Write (data [i].ToString (mask));
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"));
113 // ensure we can display up to 16384 bits keypair
114 sb.Append (" !!! TOO LONG !!!");
118 return sb.ToString ();
121 static RSA GetKeyFromFile (string filename)
123 byte[] data = ReadFromFile (filename);
125 // for SNK files (including the ECMA pseudo-key)
126 return new StrongName (data).RSA;
129 if (data.Length == 0 || data [0] != 0x30)
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)
137 RSA rsa = (pfx.Keys [0] as RSA);
144 // is assembly signed (or delayed signed) ?
145 static bool IsStrongNamed (Assembly assembly)
147 if (assembly == null)
150 object[] attrs = assembly.GetCustomAttributes (true);
151 foreach (object o in attrs) {
152 if (o is AssemblyKeyFileAttribute)
154 else if (o is AssemblyKeyNameAttribute)
160 static bool ReSign (string assemblyName, RSA key, bool quiet)
162 // this doesn't load the assembly (well it unloads it ;)
163 // http://weblogs.asp.net/nunitaddin/posts/9991.aspx
164 AssemblyName an = null;
166 an = AssemblyName.GetAssemblyName (assemblyName);
171 Console.WriteLine ("Unable to load assembly: {0}", assemblyName);
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));
181 // second, try to compare using the assembly public key
182 same = Compare (sign.PublicKey, an.GetPublicKey ());
184 // third (and last) chance, try to compare public key token
185 same = Compare (sign.PublicKeyToken, token);
190 bool signed = sign.Sign (assemblyName);
191 if (!quiet || !signed) {
192 Console.WriteLine (signed ? "Assembly {0} signed." : "Couldn't sign the assembly {0}.",
198 Console.WriteLine ("Couldn't sign the assembly {0} with this key pair.", assemblyName);
202 static int Verify (string assemblyName, bool forceVerification, bool quiet)
204 // this doesn't load the assembly (well it unloads it ;)
205 // http://weblogs.asp.net/nunitaddin/posts/9991.aspx
206 AssemblyName an = null;
208 an = AssemblyName.GetAssemblyName (assemblyName);
213 Console.WriteLine ("Unable to load assembly: {0}", assemblyName);
217 byte[] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ());
218 if ((publicKey == null) || (publicKey.Length < 12)) {
220 publicKey = an.GetPublicKey ();
221 if ((publicKey == null) || (publicKey.Length < 12)) {
222 Console.WriteLine ("{0} is not a strongly named assembly.", assemblyName);
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)) {
234 Console.WriteLine ("Assembly {0} is strongnamed.", assemblyName);
238 Console.WriteLine ("Assembly {0} is delay-signed but not strongnamed", assemblyName);
243 Console.WriteLine ("Assembly {0} is strongnamed (verification skipped).", assemblyName);
248 static bool Compare (byte[] value1, byte[] value2)
250 if ((value1 == null) || (value2 == null))
252 bool result = (value1.Length == value2.Length);
254 for (int i=0; i < value1.Length; i++) {
255 if (value1 [i] != value2 [i])
262 static void Help (string details)
264 Console.WriteLine ("Usage: sn [-q | -quiet] options [parameters]{0}", Environment.NewLine);
265 Console.WriteLine (" -q | -quiet \tQuiet mode (minimal display){0}", Environment.NewLine);
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);
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);
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);
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);
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");
314 static int Process (string[] args)
317 string param = args [i];
318 bool quiet = ((param == "-quiet") || (param == "-q"));
326 StrongName sn = null;
327 AssemblyName an = null;
328 RSACryptoServiceProvider rsa = null;
329 CspParameters csp = new CspParameters ();
330 csp.ProviderName = defaultCSP;
332 switch (args [i++]) {
334 // Change global CSP provider options
335 defaultCSP = args [i];
336 return SaveConfig ();
338 // Delete specified key container
339 csp.KeyContainerName = args [i];
340 rsa = new RSACryptoServiceProvider (csp);
341 rsa.PersistKeyInCsp = false;
343 Console.WriteLine ("Keypair in container {0} has been deleted", args [i]);
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
355 Console.WriteLine ("Assemblies are not identical (different digest for metadata)");
358 // Export public key from assembly
359 an = AssemblyName.GetAssemblyName (args [i++]);
360 WriteToFile (args[i], an.GetPublicKey ());
362 Console.WriteLine ("Public Key extracted to file {0}", args [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));
372 // Create a new strong name key pair
373 // (a new RSA keypair automagically if none is present)
375 if (i < args.Length + 2) {
377 size = Int32.Parse (args[i++]);
380 // oops, that wasn't a valid key size (assume 1024 bits)
384 sn = new StrongName (size);
385 WriteToFile (args[i], CryptoConvert.ToCapiKeyBlob (sn.RSA, true));
387 Console.WriteLine ("A new {0} bits strong name keypair has been generated in file '{1}'.", size, args [i]);
390 Console.WriteLine ("Unimplemented option");
393 byte[] infileD = ReadFromFile (args [i++]);
394 WriteCSVToFile (args [i], infileD, "D");
396 Console.WriteLine ("Output CSV file is {0} (decimal format)", args [i]);
399 byte[] infileX2 = ReadFromFile (args [i++]);
400 WriteCSVToFile (args [i], infileX2, "X2");
402 Console.WriteLine ("Output CVS file is {0} (hexadecimal format)", args [i]);
405 // Extract public key from SNK or PKCS#12/PFX file
406 sn = new StrongName (GetKeyFromFile (args [i++]));
407 WriteToFile (args[i], sn.PublicKey);
409 Console.WriteLine ("Public Key extracted to file {0}", args [i]);
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);
418 Console.WriteLine ("Public Key extracted to file {0}", args [i]);
421 string filename = args [i++];
422 if (! ReSign (filename, GetKeyFromFile (args [i]), quiet))
426 filename = args [i++];
427 csp.KeyContainerName = args [i];
428 rsa = new RSACryptoServiceProvider (csp);
429 if (! ReSign (filename, rsa, quiet))
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);
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);
446 // Show public key token from assembly
447 an = AssemblyName.GetAssemblyName (args [i++]);
448 // note: ignore quiet
449 byte [] pkt = an.GetPublicKeyToken ();
451 Console.WriteLine ("{0} does not represent a strongly named assembly.", args [i - 1]);
453 Console.WriteLine ("Public Key Token: " + ToString (pkt));
457 // Show public key and public key token from assembly
458 an = AssemblyName.GetAssemblyName (args [i++]);
459 byte [] token = an.GetPublicKeyToken ();
461 Console.WriteLine ("{0} does not represent a strongly named assembly.", args [i - 1]);
463 Console.WriteLine ("Public Key:" + ToString (an.GetPublicKey ()));
464 Console.WriteLine ("{0}Public Key Token: " + ToString (token), Environment.NewLine);
468 filename = args [i++];
469 return Verify (filename, false, quiet);
471 filename = args [i++];
472 return Verify (filename, true, quiet); // force verification
474 Console.WriteLine (new StrongNameManager ().ToString ());
477 Console.WriteLine ("Unimplemented option");
480 Console.WriteLine ("Unimplemented option");
483 // we must remove <verificationSettings> from each config files
484 Console.WriteLine ("Unimplemented option");
488 Help ((i < args.Length) ? args [i] : null);
492 Console.WriteLine ("Unknown option {0}", args [i-1]);
499 static int Main (string[] args)
502 if (args.Length < 1) {
506 return Process (args);
509 catch (IndexOutOfRangeException) {
510 Console.WriteLine ("ERROR: Invalid number of parameters.{0}", Environment.NewLine);
513 catch (CryptographicException ce) {
514 Console.WriteLine ("ERROR: {0}", ce.Message);
516 catch (Exception e) {
517 Console.WriteLine ("ERROR: Unknown error during processing: {0}", e);