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
[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
)
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 Console
.WriteLine (signed
? "Assembly {0} signed." : "Couldn't sign the assembly {0}.",
196 Console
.WriteLine ("Couldn't sign the assembly {0} with this key pair.", assemblyName
);
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;
206 an
= AssemblyName
.GetAssemblyName (assemblyName
);
211 Console
.WriteLine ("Unable to load assembly: {0}", assemblyName
);
215 byte[] publicKey
= StrongNameManager
.GetMappedPublicKey (an
.GetPublicKeyToken ());
216 if ((publicKey
== null) || (publicKey
.Length
< 12)) {
218 publicKey
= an
.GetPublicKey ();
219 if ((publicKey
== null) || (publicKey
.Length
< 12)) {
220 Console
.WriteLine ("{0} is not a strongly named assembly.", assemblyName
);
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
);
235 Console
.WriteLine ("Assembly {0} is delay-signed but not strongnamed", assemblyName
);
240 Console
.WriteLine ("Assembly {0} is strongnamed (verification skipped).", assemblyName
);
245 static bool Compare (byte[] value1
, byte[] value2
)
247 if ((value1
== null) || (value2
== null))
249 bool result
= (value1
.Length
== value2
.Length
);
251 for (int i
=0; i
< value1
.Length
; i
++) {
252 if (value1
[i
] != value2
[i
])
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
);
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
);
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
);
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
);
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
);
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");
311 static int Process (string[] args
)
314 string param
= args
[i
];
315 bool quiet
= ((param
== "-quiet") || (param
== "-q"));
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
++]) {
331 // Change global CSP provider options
332 defaultCSP
= args
[i
];
333 return SaveConfig ();
335 // Delete specified key container
336 csp
.KeyContainerName
= args
[i
];
337 rsa
= new RSACryptoServiceProvider (csp
);
338 rsa
.PersistKeyInCsp
= false;
340 Console
.WriteLine ("Keypair in container {0} has been deleted", args
[i
]);
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
352 Console
.WriteLine ("Assemblies are not identical (different digest for metadata)");
355 // Export public key from assembly
356 an
= AssemblyName
.GetAssemblyName (args
[i
++]);
357 WriteToFile (args
[i
], an
.GetPublicKey ());
359 Console
.WriteLine ("Public Key extracted to file {0}", args
[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));
369 // Create a new strong name key pair
370 // (a new RSA keypair automagically if none is present)
372 if (i
< args
.Length
+ 2) {
374 size
= Int32
.Parse (args
[i
++]);
377 // oops, that wasn't a valid key size (assume 1024 bits)
381 sn
= new StrongName (size
);
382 WriteToFile (args
[i
], CryptoConvert
.ToCapiKeyBlob (sn
.RSA
, true));
384 Console
.WriteLine ("A new {0} bits strong name keypair has been generated in file '{1}'.", size
, args
[i
]);
387 Console
.WriteLine ("Unimplemented option");
390 byte[] infileD
= ReadFromFile (args
[i
++]);
391 WriteCSVToFile (args
[i
], infileD
, "D");
393 Console
.WriteLine ("Output CSV file is {0} (decimal format)", args
[i
]);
396 byte[] infileX2
= ReadFromFile (args
[i
++]);
397 WriteCSVToFile (args
[i
], infileX2
, "X2");
399 Console
.WriteLine ("Output CVS file is {0} (hexadecimal format)", args
[i
]);
402 // Extract public key from SNK or PKCS#12/PFX file
403 sn
= new StrongName (GetKeyFromFile (args
[i
++]));
404 WriteToFile (args
[i
], sn
.PublicKey
);
406 Console
.WriteLine ("Public Key extracted to file {0}", args
[i
]);
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
);
415 Console
.WriteLine ("Public Key extracted to file {0}", args
[i
]);
418 string filename
= args
[i
++];
419 if (! ReSign (filename
, GetKeyFromFile (args
[i
])))
423 filename
= args
[i
++];
424 csp
.KeyContainerName
= args
[i
];
425 rsa
= new RSACryptoServiceProvider (csp
);
426 if (! ReSign (filename
, rsa
))
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
);
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
);
443 // Show public key token from assembly
444 an
= AssemblyName
.GetAssemblyName (args
[i
++]);
445 // note: ignore quiet
446 byte [] pkt
= an
.GetPublicKeyToken ();
448 Console
.WriteLine ("{0} does not represent a strongly named assembly.", args
[i
- 1]);
450 Console
.WriteLine ("Public Key Token: " + ToString (pkt
));
454 // Show public key and public key token from assembly
455 an
= AssemblyName
.GetAssemblyName (args
[i
++]);
456 byte [] token
= an
.GetPublicKeyToken ();
458 Console
.WriteLine ("{0} does not represent a strongly named assembly.", args
[i
- 1]);
460 Console
.WriteLine ("Public Key:" + ToString (an
.GetPublicKey ()));
461 Console
.WriteLine ("{0}Public Key Token: " + ToString (token
), Environment
.NewLine
);
465 filename
= args
[i
++];
466 return Verify (filename
, false);
468 filename
= args
[i
++];
469 return Verify (filename
, true); // force verification
471 Console
.WriteLine (new StrongNameManager ().ToString ());
474 Console
.WriteLine ("Unimplemented option");
477 Console
.WriteLine ("Unimplemented option");
480 // we must remove <verificationSettings> from each config files
481 Console
.WriteLine ("Unimplemented option");
485 Help ((i
< args
.Length
) ? args
[i
] : null);
489 Console
.WriteLine ("Unknown option {0}", args
[i
-1]);
496 static int Main (string[] args
)
499 if (args
.Length
< 1) {
503 return Process (args
);
506 catch (IndexOutOfRangeException
) {
507 Console
.WriteLine ("ERROR: Invalid number of parameters.{0}", Environment
.NewLine
);
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
);