6bdd276d05
Former-commit-id: fd56571888259555122d8a0f58c68838229cea2b
524 lines
17 KiB
C#
524 lines
17 KiB
C#
//
|
|
// SN.cs: sn clone tool
|
|
//
|
|
// Author:
|
|
// Sebastien Pouliot <sebastien@ximian.com>
|
|
//
|
|
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
|
|
// Copyright (C) 2004-2006,2008 Novell, Inc (http://www.novell.com)
|
|
//
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
using Mono.Security;
|
|
using Mono.Security.Cryptography;
|
|
using Mono.Security.X509;
|
|
|
|
[assembly: AssemblyTitle("Mono StrongName")]
|
|
[assembly: AssemblyDescription("StrongName utility for signing assemblies")]
|
|
|
|
namespace Mono.Tools {
|
|
|
|
class SN {
|
|
|
|
static private void Header ()
|
|
{
|
|
Console.WriteLine (new AssemblyInfo ().ToString ());
|
|
}
|
|
|
|
static string defaultCSP;
|
|
|
|
static bool LoadConfig (bool quiet)
|
|
{
|
|
MethodInfo config = typeof (System.Environment).GetMethod ("GetMachineConfigPath",
|
|
BindingFlags.Static|BindingFlags.NonPublic);
|
|
|
|
if (config != null) {
|
|
string path = (string) config.Invoke (null, null);
|
|
|
|
bool exist = File.Exists (path);
|
|
if (!quiet && !exist)
|
|
Console.WriteLine ("Couldn't find machine.config");
|
|
|
|
StrongNameManager.LoadConfig (path);
|
|
return exist;
|
|
}
|
|
else if (!quiet)
|
|
Console.WriteLine ("Couldn't resolve machine.config location (corlib issue)");
|
|
|
|
// default CSP
|
|
return false;
|
|
}
|
|
|
|
// TODO
|
|
static int SaveConfig ()
|
|
{
|
|
// default CSP
|
|
return 1;
|
|
}
|
|
|
|
static byte[] ReadFromFile (string fileName)
|
|
{
|
|
byte[] data = null;
|
|
FileStream fs = File.Open (fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
try {
|
|
data = new byte [fs.Length];
|
|
fs.Read (data, 0, data.Length);
|
|
}
|
|
finally {
|
|
fs.Close ();
|
|
}
|
|
return data;
|
|
}
|
|
|
|
static void WriteToFile (string fileName, byte[] data)
|
|
{
|
|
FileStream fs = File.Open (fileName, FileMode.Create, FileAccess.Write);
|
|
try {
|
|
fs.Write (data, 0, data.Length);
|
|
}
|
|
finally {
|
|
fs.Close ();
|
|
}
|
|
}
|
|
|
|
static void WriteCSVToFile (string fileName, byte[] data, string mask)
|
|
{
|
|
StreamWriter sw = File.CreateText (fileName);
|
|
try {
|
|
for (int i=0; i < data.Length; i++) {
|
|
if (mask [0] == 'X')
|
|
sw.Write ("0x");
|
|
sw.Write (data [i].ToString (mask));
|
|
sw.Write (", ");
|
|
}
|
|
}
|
|
finally {
|
|
sw.Close ();
|
|
}
|
|
}
|
|
|
|
static string ToString (byte[] data)
|
|
{
|
|
StringBuilder sb = new StringBuilder ();
|
|
for (int i=0; i < data.Length; i++) {
|
|
if ((i % 39 == 0) && (data.Length > 39))
|
|
sb.Append (Environment.NewLine);
|
|
sb.Append (data [i].ToString ("x2"));
|
|
if (i > 2080) {
|
|
// ensure we can display up to 16384 bits keypair
|
|
sb.Append (" !!! TOO LONG !!!");
|
|
break;
|
|
}
|
|
}
|
|
return sb.ToString ();
|
|
}
|
|
|
|
static RSA GetKeyFromFile (string filename)
|
|
{
|
|
byte[] data = ReadFromFile (filename);
|
|
try {
|
|
// for SNK files (including the ECMA pseudo-key)
|
|
return new StrongName (data).RSA;
|
|
}
|
|
catch {
|
|
if (data.Length == 0 || data [0] != 0x30)
|
|
throw;
|
|
// this could be a PFX file
|
|
Console.Write ("Enter password for private key (will be visible when typed): ");
|
|
PKCS12 pfx = new PKCS12 (data, Console.ReadLine ());
|
|
// works only if a single key is present
|
|
if (pfx.Keys.Count != 1)
|
|
throw;
|
|
RSA rsa = (pfx.Keys [0] as RSA);
|
|
if (rsa == null)
|
|
throw;
|
|
return rsa;
|
|
}
|
|
}
|
|
#if false
|
|
// is assembly signed (or delayed signed) ?
|
|
static bool IsStrongNamed (Assembly assembly)
|
|
{
|
|
if (assembly == null)
|
|
return false;
|
|
|
|
object[] attrs = assembly.GetCustomAttributes (true);
|
|
foreach (object o in attrs) {
|
|
if (o is AssemblyKeyFileAttribute)
|
|
return true;
|
|
else if (o is AssemblyKeyNameAttribute)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
static bool ReSign (string assemblyName, RSA key, bool quiet)
|
|
{
|
|
// this doesn't load the assembly (well it unloads it ;)
|
|
// http://weblogs.asp.net/nunitaddin/posts/9991.aspx
|
|
AssemblyName an = null;
|
|
try {
|
|
an = AssemblyName.GetAssemblyName (assemblyName);
|
|
}
|
|
catch {
|
|
}
|
|
if (an == null) {
|
|
Console.WriteLine ("Unable to load assembly: {0}", assemblyName);
|
|
return false;
|
|
}
|
|
|
|
StrongName sign = new StrongName (key);
|
|
byte[] token = an.GetPublicKeyToken ();
|
|
|
|
// first, try to compare using a mapped public key (e.g. ECMA)
|
|
bool same = Compare (sign.PublicKey, StrongNameManager.GetMappedPublicKey (token));
|
|
if (!same) {
|
|
// second, try to compare using the assembly public key
|
|
same = Compare (sign.PublicKey, an.GetPublicKey ());
|
|
if (!same) {
|
|
// third (and last) chance, try to compare public key token
|
|
same = Compare (sign.PublicKeyToken, token);
|
|
}
|
|
}
|
|
|
|
if (same) {
|
|
bool signed = sign.Sign (assemblyName);
|
|
if (!quiet || !signed) {
|
|
Console.WriteLine (signed ? "Assembly {0} signed." : "Couldn't sign the assembly {0}.",
|
|
assemblyName);
|
|
}
|
|
return signed;
|
|
}
|
|
|
|
Console.WriteLine ("Couldn't sign the assembly {0} with this key pair. Public key of assembly did not match signing public key.", assemblyName);
|
|
return false;
|
|
}
|
|
|
|
static int Verify (string assemblyName, bool forceVerification, bool quiet)
|
|
{
|
|
// this doesn't load the assembly (well it unloads it ;)
|
|
// http://weblogs.asp.net/nunitaddin/posts/9991.aspx
|
|
AssemblyName an = null;
|
|
try {
|
|
an = AssemblyName.GetAssemblyName (assemblyName);
|
|
}
|
|
catch {
|
|
}
|
|
if (an == null) {
|
|
Console.WriteLine ("Unable to load assembly: {0}", assemblyName);
|
|
return 2;
|
|
}
|
|
|
|
byte[] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ());
|
|
if ((publicKey == null) || (publicKey.Length < 12)) {
|
|
// no mapping
|
|
publicKey = an.GetPublicKey ();
|
|
if ((publicKey == null) || (publicKey.Length < 12)) {
|
|
Console.WriteLine ("{0} is not a strongly named assembly.", assemblyName);
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
// Note: MustVerify is based on the original token (by design). Public key
|
|
// remapping won't affect if the assembly is verified or not.
|
|
if (forceVerification || StrongNameManager.MustVerify (an)) {
|
|
RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey, 12);
|
|
StrongName sn = new StrongName (rsa);
|
|
if (sn.Verify (assemblyName)) {
|
|
if (!quiet)
|
|
Console.WriteLine ("Assembly {0} is strongnamed.", assemblyName);
|
|
return 0;
|
|
}
|
|
else {
|
|
Console.WriteLine ("Assembly {0} is delay-signed but not strongnamed", assemblyName);
|
|
return 1;
|
|
}
|
|
}
|
|
else {
|
|
Console.WriteLine ("Assembly {0} is strongnamed (verification skipped).", assemblyName);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static bool Compare (byte[] value1, byte[] value2)
|
|
{
|
|
if ((value1 == null) || (value2 == null))
|
|
return false;
|
|
bool result = (value1.Length == value2.Length);
|
|
if (result) {
|
|
for (int i=0; i < value1.Length; i++) {
|
|
if (value1 [i] != value2 [i])
|
|
return false;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void Help (string details)
|
|
{
|
|
Console.WriteLine ("Usage: sn [-q | -quiet] options [parameters]{0}", Environment.NewLine);
|
|
Console.WriteLine (" -q | -quiet \tQuiet mode (minimal display){0}", Environment.NewLine);
|
|
switch (details) {
|
|
case "config":
|
|
Console.WriteLine ("Configuration options <1>");
|
|
Console.WriteLine (" -c provider{0}\tChange the default CSP provider", Environment.NewLine);
|
|
Console.WriteLine (" -m [y|n]{0}\tUse a machine [y] key container or user key container [n]", Environment.NewLine);
|
|
Console.WriteLine (" -Vl{0}\tList the verification options", Environment.NewLine);
|
|
Console.WriteLine (" -Vr assembly [userlist]{0}\tExempt the specified assembly from verification for the user list", Environment.NewLine);
|
|
Console.WriteLine (" -Vu assembly{0}\tRemove exemption entry for the specified assembly", Environment.NewLine);
|
|
Console.WriteLine (" -Vx{0}\tRemove all exemptions entries", Environment.NewLine);
|
|
Console.WriteLine ("{0}<1> Currently not implemented in the tool", Environment.NewLine);
|
|
break;
|
|
case "csp":
|
|
Console.WriteLine ("CSP related options");
|
|
Console.WriteLine (" -d container{0}\tDelete the specified key container", Environment.NewLine);
|
|
Console.WriteLine (" -i keypair.snk container{0}\tImport the keypair from a SNK file into a CSP container", Environment.NewLine);
|
|
Console.WriteLine (" -pc container public.key{0}\tExport the public key from a CSP container to the specified file", Environment.NewLine);
|
|
break;
|
|
case "convert":
|
|
Console.WriteLine ("Convertion options");
|
|
Console.WriteLine (" -e assembly output.pub{0}\tExport the assembly public key to the specified file", Environment.NewLine);
|
|
Console.WriteLine (" -p keypair.snk output.pub{0}\tExport the public key from a SNK file to the specified file", Environment.NewLine);
|
|
Console.WriteLine (" -o input output.txt{0}\tConvert the input file to a CSV file (using decimal).", Environment.NewLine);
|
|
Console.WriteLine (" -oh input output.txt{0}\tConvert the input file to a CSV file (using hexadecimal).", Environment.NewLine);
|
|
break;
|
|
case "sn":
|
|
Console.WriteLine ("StrongName signing options");
|
|
Console.WriteLine (" -D assembly1 assembly2{0}\tCompare assembly1 and assembly2 (without signatures)", Environment.NewLine);
|
|
Console.WriteLine (" -k keypair.snk{0}\tCreate a new keypair in the specified file", Environment.NewLine);
|
|
Console.WriteLine (" -R assembly keypair.snk{0}\tResign the assembly with the specified StrongName key file", Environment.NewLine);
|
|
Console.WriteLine (" -Rc assembly container{0}\tResign the assembly with the specified CSP container", Environment.NewLine);
|
|
Console.WriteLine (" -t file{0}\tShow the public key token from the specified file", Environment.NewLine);
|
|
Console.WriteLine (" -tp file{0}\tShow the public key and pk token from the specified file", Environment.NewLine);
|
|
Console.WriteLine (" -T assembly{0}\tShow the public key token from the specified assembly", Environment.NewLine);
|
|
Console.WriteLine (" -Tp assembly{0}\tShow the public key and pk token from the specified assembly", Environment.NewLine);
|
|
Console.WriteLine (" -v assembly{0}\tVerify the specified assembly signature", Environment.NewLine);
|
|
Console.WriteLine (" -vf assembly{0}\tVerify the specified assembly signature (even if disabled).", Environment.NewLine);
|
|
break;
|
|
default:
|
|
Console.WriteLine ("Help options");
|
|
Console.WriteLine (" -? | -h \tShow this help screen about the tool");
|
|
Console.WriteLine (" -? | -h config \tConfiguration options");
|
|
Console.WriteLine (" -? | -h csp \tCrypto Service Provider (CSP) related options");
|
|
Console.WriteLine (" -? | -h convert\tFormat convertion options");
|
|
Console.WriteLine (" -? | -h sn \tStrongName signing options");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int Process (string[] args)
|
|
{
|
|
int i = 0;
|
|
string param = args [i];
|
|
bool quiet = ((param == "-quiet") || (param == "-q"));
|
|
if (quiet)
|
|
i++;
|
|
else
|
|
Header();
|
|
|
|
LoadConfig (quiet);
|
|
|
|
StrongName sn = null;
|
|
AssemblyName an = null;
|
|
RSACryptoServiceProvider rsa = null;
|
|
CspParameters csp = new CspParameters ();
|
|
csp.ProviderName = defaultCSP;
|
|
|
|
switch (args [i++]) {
|
|
case "-c":
|
|
// Change global CSP provider options
|
|
defaultCSP = args [i];
|
|
return SaveConfig ();
|
|
case "-d":
|
|
// Delete specified key container
|
|
csp.KeyContainerName = args [i];
|
|
rsa = new RSACryptoServiceProvider (csp);
|
|
rsa.PersistKeyInCsp = false;
|
|
if (!quiet)
|
|
Console.WriteLine ("Keypair in container {0} has been deleted", args [i]);
|
|
break;
|
|
case "-D":
|
|
StrongName a1 = new StrongName ();
|
|
byte[] h1 = a1.Hash (args [i++]);
|
|
StrongName a2 = new StrongName ();
|
|
byte[] h2 = a2.Hash (args [i++]);
|
|
if (Compare (h1, h2)) {
|
|
Console.WriteLine ("Both assembly are identical (same digest for metadata)");
|
|
// TODO: if equals then compare signatures
|
|
}
|
|
else
|
|
Console.WriteLine ("Assemblies are not identical (different digest for metadata)");
|
|
break;
|
|
case "-e":
|
|
// Export public key from assembly
|
|
an = AssemblyName.GetAssemblyName (args [i++]);
|
|
WriteToFile (args[i], an.GetPublicKey ());
|
|
if (!quiet)
|
|
Console.WriteLine ("Public Key extracted to file {0}", args [i]);
|
|
break;
|
|
case "-i":
|
|
// import keypair from SNK to container
|
|
sn = new StrongName (ReadFromFile (args [i++]));
|
|
csp.KeyContainerName = args [i];
|
|
rsa = new RSACryptoServiceProvider (csp);
|
|
rsa.ImportParameters (sn.RSA.ExportParameters (true));
|
|
break;
|
|
case "-k":
|
|
// Create a new strong name key pair
|
|
// (a new RSA keypair automagically if none is present)
|
|
int size = 1024;
|
|
if (i < args.Length + 2) {
|
|
try {
|
|
size = Int32.Parse (args[i++]);
|
|
}
|
|
catch {
|
|
// oops, that wasn't a valid key size (assume 1024 bits)
|
|
i--;
|
|
}
|
|
}
|
|
sn = new StrongName (size);
|
|
WriteToFile (args[i], CryptoConvert.ToCapiKeyBlob (sn.RSA, true));
|
|
if (!quiet)
|
|
Console.WriteLine ("A new {0} bits strong name keypair has been generated in file '{1}'.", size, args [i]);
|
|
break;
|
|
case "-m":
|
|
Console.WriteLine ("Unimplemented option");
|
|
break;
|
|
case "-o":
|
|
byte[] infileD = ReadFromFile (args [i++]);
|
|
WriteCSVToFile (args [i], infileD, "D");
|
|
if (!quiet)
|
|
Console.WriteLine ("Output CSV file is {0} (decimal format)", args [i]);
|
|
break;
|
|
case "-oh":
|
|
byte[] infileX2 = ReadFromFile (args [i++]);
|
|
WriteCSVToFile (args [i], infileX2, "X2");
|
|
if (!quiet)
|
|
Console.WriteLine ("Output CVS file is {0} (hexadecimal format)", args [i]);
|
|
break;
|
|
case "-p":
|
|
// Extract public key from SNK or PKCS#12/PFX file
|
|
sn = new StrongName (GetKeyFromFile (args [i++]));
|
|
WriteToFile (args[i], sn.PublicKey);
|
|
if (!quiet)
|
|
Console.WriteLine ("Public Key extracted to file {0}", args [i]);
|
|
break;
|
|
case "-pc":
|
|
// Extract public key from container
|
|
csp.KeyContainerName = args [i++];
|
|
rsa = new RSACryptoServiceProvider (csp);
|
|
sn = new StrongName (rsa);
|
|
WriteToFile (args[i], sn.PublicKey);
|
|
if (!quiet)
|
|
Console.WriteLine ("Public Key extracted to file {0}", args [i]);
|
|
break;
|
|
case "-R":
|
|
string filename = args [i++];
|
|
if (! ReSign (filename, GetKeyFromFile (args [i]), quiet))
|
|
return 1;
|
|
break;
|
|
case "-Rc":
|
|
filename = args [i++];
|
|
csp.KeyContainerName = args [i];
|
|
rsa = new RSACryptoServiceProvider (csp);
|
|
if (! ReSign (filename, rsa, quiet))
|
|
return 1;
|
|
break;
|
|
case "-t":
|
|
// Show public key token from file
|
|
sn = new StrongName (ReadFromFile (args [i]));
|
|
// note: ignore quiet
|
|
Console.WriteLine ("Public Key Token: " + ToString (sn.PublicKeyToken), Environment.NewLine);
|
|
break;
|
|
case "-tp":
|
|
// Show public key and public key token from assembly
|
|
sn = new StrongName (ReadFromFile (args [i]));
|
|
// note: ignore quiet
|
|
Console.WriteLine ("Public Key:" + ToString (sn.PublicKey));
|
|
Console.WriteLine ("{0}Public Key Token: " + ToString (sn.PublicKeyToken), Environment.NewLine);
|
|
break;
|
|
case "-T":
|
|
// Show public key token from assembly
|
|
an = AssemblyName.GetAssemblyName (args [i++]);
|
|
// note: ignore quiet
|
|
byte [] pkt = an.GetPublicKeyToken ();
|
|
if (pkt == null) {
|
|
Console.WriteLine ("{0} does not represent a strongly named assembly.", args [i - 1]);
|
|
} else {
|
|
Console.WriteLine ("Public Key Token: " + ToString (pkt));
|
|
}
|
|
break;
|
|
case "-Tp":
|
|
// Show public key and public key token from assembly
|
|
an = AssemblyName.GetAssemblyName (args [i++]);
|
|
byte [] token = an.GetPublicKeyToken ();
|
|
if (token == null) {
|
|
Console.WriteLine ("{0} does not represent a strongly named assembly.", args [i - 1]);
|
|
} else {
|
|
Console.WriteLine ("Public Key:" + ToString (an.GetPublicKey ()));
|
|
Console.WriteLine ("{0}Public Key Token: " + ToString (token), Environment.NewLine);
|
|
}
|
|
break;
|
|
case "-v":
|
|
filename = args [i++];
|
|
return Verify (filename, false, quiet);
|
|
case "-vf":
|
|
filename = args [i++];
|
|
return Verify (filename, true, quiet); // force verification
|
|
case "-Vl":
|
|
Console.WriteLine (new StrongNameManager ().ToString ());
|
|
break;
|
|
case "-Vr":
|
|
Console.WriteLine ("Unimplemented option");
|
|
break;
|
|
case "-Vu":
|
|
Console.WriteLine ("Unimplemented option");
|
|
break;
|
|
case "-Vx":
|
|
// we must remove <verificationSettings> from each config files
|
|
Console.WriteLine ("Unimplemented option");
|
|
break;
|
|
case "-?":
|
|
case "-h":
|
|
Help ((i < args.Length) ? args [i] : null);
|
|
break;
|
|
default:
|
|
if (!quiet)
|
|
Console.WriteLine ("Unknown option {0}", args [i-1]);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
[STAThread]
|
|
static int Main (string[] args)
|
|
{
|
|
try {
|
|
if (args.Length < 1) {
|
|
Header ();
|
|
Help (null);
|
|
} else {
|
|
return Process (args);
|
|
}
|
|
}
|
|
catch (IndexOutOfRangeException) {
|
|
Console.WriteLine ("ERROR: Invalid number of parameters.{0}", Environment.NewLine);
|
|
Help (null);
|
|
}
|
|
catch (CryptographicException ce) {
|
|
Console.WriteLine ("ERROR: {0}", ce.Message);
|
|
}
|
|
catch (Exception e) {
|
|
Console.WriteLine ("ERROR: Unknown error during processing: {0}", e);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
}
|