Xamarin Public Jenkins (auto-signing) 7d7f676260 Imported Upstream version 5.16.0.100
Former-commit-id: 38faa55fb9669e35e7d8448b15c25dc447f25767
2018-08-07 15:19:03 +00:00

706 lines
22 KiB
C#

//
// CertMgr.cs: Certificate Manager clone tool (CLI version)
//
// Author:
// Sebastien Pouliot <sebastien@ximian.com>
//
// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
//
using System;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Reflection;
using System.Security.Cryptography;
using SSCX = System.Security.Cryptography.X509Certificates;
using System.Text;
using Mono.Security.Authenticode;
using Mono.Security.Cryptography;
using Mono.Security.X509;
[assembly: AssemblyTitle ("Mono Certificate Manager")]
[assembly: AssemblyDescription ("Manage X.509 certificates and CRL from stores.")]
namespace Mono.Tools {
class CertificateManager {
static private void Header ()
{
Console.WriteLine (new AssemblyInfo ().ToString ());
}
static private void Help ()
{
Console.WriteLine ("Usage: certmgr [action] [object-type] [options] store [filename]");
Console.WriteLine (" or: certmgr -list object-type [options] store");
Console.WriteLine (" or: certmgr -del object-type [options] store certhash");
Console.WriteLine (" or: certmgr -ssl [options] url");
Console.WriteLine (" or: certmgr -put object-type [options] store certfile");
Console.WriteLine (" or: certmgr -importKey [options] store pkcs12file");
Console.WriteLine ();
Console.WriteLine ("actions");
Console.WriteLine ("\t-add\t\tAdd a certificate, CRL or CTL to specified store");
Console.WriteLine ("\t-del\t\tRemove a certificate, CRL or CTL to specified store");
Console.WriteLine ("\t-put\t\tCopy a certificate, CRL or CTL from a store to a file");
Console.WriteLine ("\t-list\t\tList certificates, CRL or CTL in the specified store.");
Console.WriteLine ("\t-ssl\t\tDownload and add certificates from an SSL session");
Console.WriteLine ("\t-importKey\tImport PKCS12 privateKey to keypair store.");
Console.WriteLine ("object types");
Console.WriteLine ("\t-c\t\tadd/del/put certificates");
Console.WriteLine ("\t-crl\t\tadd/del/put certificate revocation lists");
Console.WriteLine ("\t-ctl\t\tadd/del/put certificate trust lists [unsupported]");
Console.WriteLine ("other options");
Console.WriteLine ("\t-m\t\tuse the machine certificate store (default to user)");
Console.WriteLine ("\t-v\t\tverbose mode (display status for every steps)");
Console.WriteLine ("\t-p [password]\tPassword used to decrypt PKCS12");
Console.WriteLine ("\t-pem\t\tPut certificate in Base-64 encoded format (default DER encoded)");
Console.WriteLine ("\t-?\t\th[elp]\tDisplay this help message");
Console.WriteLine ();
}
static string GetCommand (string arg)
{
if ((arg == null) || (arg.Length < 1))
return null;
switch (arg [0]) {
case '/':
return arg.Substring (1).ToUpper ();
case '-':
if (arg.Length < 2)
return null;
int n = ((arg [1] == '-') ? 2 : 1);
return arg.Substring (n).ToUpper ();
default:
return arg;
}
}
enum Action {
None,
Add,
Delete,
Put,
List,
Ssl,
ImportKey
}
static Action GetAction (string arg)
{
Action action = Action.None;
switch (GetCommand (arg)) {
case "ADD":
action = Action.Add;
break;
case "DEL":
case "DELETE":
action = Action.Delete;
break;
case "PUT":
action = Action.Put;
break;
case "LST":
case "LIST":
action = Action.List;
break;
case "SSL":
case "TLS":
action = Action.Ssl;
break;
case "IMPORTKEY":
action = Action.ImportKey;
break;
}
return action;
}
enum ObjectType {
None,
Certificate,
CRL,
CTL
}
static ObjectType GetObjectType (string arg)
{
ObjectType type = ObjectType.None;
switch (GetCommand (arg)) {
case "C":
case "CERT":
case "CERTIFICATE":
type = ObjectType.Certificate;
break;
case "CRL":
type = ObjectType.CRL;
break;
case "CTL":
type = ObjectType.CTL;
break;
}
return type;
}
static X509Store GetStoreFromName (string storeName, bool machine)
{
X509Stores stores = ((machine) ? X509StoreManager.LocalMachine : X509StoreManager.CurrentUser);
X509Store store = null;
switch (storeName) {
case X509Stores.Names.Personal:
return stores.Personal;
case X509Stores.Names.OtherPeople:
return stores.OtherPeople;
case X509Stores.Names.IntermediateCA:
return stores.IntermediateCA;
case "Root": // special case (same as trusted root)
case X509Stores.Names.TrustedRoot:
return stores.TrustedRoot;
case X509Stores.Names.Untrusted:
return stores.Untrusted;
}
return store;
}
static byte[] PEM (string type, byte[] data)
{
string pem = Encoding.ASCII.GetString (data);
string header = String.Format ("-----BEGIN {0}-----", type);
string footer = String.Format ("-----END {0}-----", type);
int start = pem.IndexOf (header) + header.Length;
int end = pem.IndexOf (footer, start);
string base64 = pem.Substring (start, (end - start));
return Convert.FromBase64String (base64);
}
static byte[] ToPEM (string type, byte[] data)
{
string header = String.Format ("-----BEGIN {0}-----", type);
string footer = String.Format ("-----END {0}-----", type);
string encodedString = Convert.ToBase64String (data);
StringBuilder sb = new StringBuilder ();
int remaining = encodedString.Length;
sb.AppendLine (header);
for (int i = 0; i <= encodedString.Length; i += 64) {
if (remaining >= 64) {
sb.AppendLine (encodedString.Substring (i, 64));
} else {
sb.AppendLine (encodedString.Substring (i, remaining));
}
remaining -= 64;
}
sb.AppendLine (footer);
return Encoding.ASCII.GetBytes (sb.ToString ());
}
static X509CertificateCollection LoadCertificates (string filename, string password, bool verbose)
{
X509Certificate x509 = null;
X509CertificateCollection coll = new X509CertificateCollection ();
switch (Path.GetExtension (filename).ToUpper ()) {
case ".P7B":
case ".SPC":
SoftwarePublisherCertificate spc = SoftwarePublisherCertificate.CreateFromFile (filename);
coll.AddRange (spc.Certificates);
spc = null;
break;
case ".CER":
case ".CRT":
using (FileStream fs = File.OpenRead (filename)) {
byte[] data = new byte [fs.Length];
fs.Read (data, 0, data.Length);
if (data [0] != 0x30) {
// maybe it's ASCII PEM base64 encoded ?
data = PEM ("CERTIFICATE", data);
}
if (data != null)
x509 = new X509Certificate (data);
}
if (x509 != null)
coll.Add (x509);
break;
case ".P12":
case ".PFX":
PKCS12 p12 = password == null ? PKCS12.LoadFromFile (filename)
: PKCS12.LoadFromFile (filename, password);
X509CertificateCollection tmp = new X509CertificateCollection (p12.Certificates);
for (int i = 0; i != p12.Keys.Count; i++) {
X509Certificate cert = p12.Certificates[i];
RSACryptoServiceProvider pk = p12.Keys[i] as RSACryptoServiceProvider;
if (pk == null || pk.PublicOnly)
continue;
if (verbose)
Console.WriteLine ("Found key for certificate: {0}", cert.SubjectName);
tmp[0].RSA = pk;
}
coll.AddRange(tmp);
p12 = null;
break;
default:
Console.WriteLine ("Unknown file extension: {0}",
Path.GetExtension (filename));
break;
}
return coll;
}
static ArrayList LoadCRLs (string filename)
{
X509Crl crl = null;
ArrayList list = new ArrayList ();
switch (Path.GetExtension (filename).ToUpper ()) {
case ".P7B":
case ".SPC":
SoftwarePublisherCertificate spc = SoftwarePublisherCertificate.CreateFromFile (filename);
list.AddRange (spc.Crls);
spc = null;
break;
case ".CRL":
using (FileStream fs = File.OpenRead (filename)) {
byte[] data = new byte [fs.Length];
fs.Read (data, 0, data.Length);
crl = new X509Crl (data);
}
list.Add (crl);
break;
default:
Console.WriteLine ("Unknown file extension: {0}",
Path.GetExtension (filename));
break;
}
return list;
}
static void Add (ObjectType type, X509Store store, string file, string password, bool verbose)
{
switch (type) {
case ObjectType.Certificate:
X509CertificateCollection coll = LoadCertificates (file, password, verbose);
foreach (X509Certificate x509 in coll) {
store.Import (x509);
}
Console.WriteLine ("{0} certificate(s) added to store {1}.",
coll.Count, store.Name);
break;
case ObjectType.CRL:
ArrayList list = LoadCRLs (file);
foreach (X509Crl crl in list) {
store.Import (crl);
}
Console.WriteLine ("{0} CRL(s) added to store {1}.",
list.Count, store.Name);
break;
default:
throw new NotSupportedException (type.ToString ());
}
}
static void Delete (ObjectType type, X509Store store, string hash, bool verbose)
{
switch (type) {
case ObjectType.Certificate:
foreach (X509Certificate x509 in store.Certificates) {
if (hash == CryptoConvert.ToHex (x509.Hash)) {
store.Remove (x509);
Console.WriteLine ("Certificate removed from store.");
return;
}
}
break;
case ObjectType.CRL:
foreach (X509Crl crl in store.Crls) {
if (hash == CryptoConvert.ToHex (crl.Hash)) {
store.Remove (crl);
Console.WriteLine ("CRL removed from store.");
return;
}
}
break;
default:
throw new NotSupportedException (type.ToString ());
}
}
static void Put (ObjectType type, X509Store store, string file, bool machine, bool pem, bool verbose)
{
if (String.IsNullOrEmpty (file)) {
Console.Error.WriteLine("error: no filename provided to put the certificate.");
Help();
return;
}
switch (type) {
case ObjectType.Certificate:
for(int i = 0; i < store.Certificates.Count; i++) {
Console.WriteLine ("==============Certificate # {0} ==========", i + 1);
DisplayCertificate (store.Certificates[i], machine, verbose);
}
int selection;
Console.Write("Enter cert # from the above list to put-->");
if (!int.TryParse(Console.ReadLine(), out selection) || selection > store.Certificates.Count) {
Console.Error.WriteLine ("error: invalid selection.");
return;
}
SSCX.X509Certificate2 cert = new SSCX.X509Certificate2 (store.Certificates[selection-1].RawData);
byte[] data = null;
if(pem) {
data = ToPEM ("CERTIFICATE", cert.Export (SSCX.X509ContentType.Cert));
} else {
data = cert.Export (SSCX.X509ContentType.Cert);
}
using (FileStream fs = File.Create (file)) {
fs.Write(data, 0, data.Length);
}
Console.WriteLine ("Certificate put to {0}.", file);
break;
default:
throw new NotSupportedException ("Put " + type + " not supported yet");
}
}
static void DisplayCertificate (X509Certificate x509, bool machine, bool verbose)
{
Console.WriteLine ("{0}X.509 v{1} Certificate", (x509.IsSelfSigned ? "Self-signed " : String.Empty), x509.Version);
Console.WriteLine (" Serial Number: {0}", CryptoConvert.ToHex (x509.SerialNumber));
Console.WriteLine (" Issuer Name: {0}", x509.IssuerName);
Console.WriteLine (" Subject Name: {0}", x509.SubjectName);
Console.WriteLine (" Valid From: {0}", x509.ValidFrom);
Console.WriteLine (" Valid Until: {0}", x509.ValidUntil);
Console.WriteLine (" Unique Hash: {0}", CryptoConvert.ToHex (x509.Hash));
if (verbose) {
Console.WriteLine (" Key Algorithm: {0}", x509.KeyAlgorithm);
Console.WriteLine (" Algorithm Parameters: {0}", (x509.KeyAlgorithmParameters == null) ? "None" :
CryptoConvert.ToHex (x509.KeyAlgorithmParameters));
Console.WriteLine (" Public Key: {0}", CryptoConvert.ToHex (x509.PublicKey));
Console.WriteLine (" Signature Algorithm: {0}", x509.SignatureAlgorithm);
Console.WriteLine (" Algorithm Parameters: {0}", (x509.SignatureAlgorithmParameters == null) ? "None" :
CryptoConvert.ToHex (x509.SignatureAlgorithmParameters));
Console.WriteLine (" Signature: {0}", CryptoConvert.ToHex (x509.Signature));
RSACryptoServiceProvider rsaCsp = x509.RSA as RSACryptoServiceProvider;
RSAManaged rsaManaged = x509.RSA as RSAManaged;
Console.WriteLine (" Private Key: {0}", ((rsaCsp != null && !rsaCsp.PublicOnly)
|| (rsaManaged != null && !rsaManaged.PublicOnly)));
CspParameters cspParams = new CspParameters ();
cspParams.KeyContainerName = CryptoConvert.ToHex (x509.Hash);
cspParams.Flags = machine ? CspProviderFlags.UseMachineKeyStore : 0;
KeyPairPersistence kpp = new KeyPairPersistence (cspParams);
Console.WriteLine (" KeyPair Key: {0}", kpp.Load ());
}
Console.WriteLine ();
}
static void DisplayCrl (X509Crl crl, bool machine, bool verbose)
{
Console.WriteLine ("X.509 v{0} CRL", crl.Version);
Console.WriteLine (" Issuer Name: {0}", crl.IssuerName);
Console.WriteLine (" This Update: {0}", crl.ThisUpdate);
Console.WriteLine (" Next Update: {0} {1}", crl.NextUpdate, crl.IsCurrent ? String.Empty : "update overdue!");
Console.WriteLine (" Unique Hash: {0}", CryptoConvert.ToHex (crl.Hash));
if (verbose) {
Console.WriteLine (" Signature Algorithm: {0}", crl.SignatureAlgorithm);
Console.WriteLine (" Signature: {0}", CryptoConvert.ToHex (crl.Signature));
int n = 0;
foreach (X509Crl.X509CrlEntry entry in crl.Entries) {
Console.WriteLine (" #{0}: Serial: {1} revoked on {2}",
++n, CryptoConvert.ToHex (entry.SerialNumber), entry.RevocationDate);
}
}
}
static void List (ObjectType type, X509Store store, bool machine, string file, bool verbose)
{
switch (type) {
case ObjectType.Certificate:
foreach (X509Certificate x509 in store.Certificates) {
DisplayCertificate (x509, machine, verbose);
}
break;
case ObjectType.CRL:
foreach (X509Crl crl in store.Crls) {
DisplayCrl (crl, machine, verbose);
}
break;
default:
throw new NotSupportedException (type.ToString ());
}
}
static X509CertificateCollection GetCertificatesFromSslSession (string url)
{
Uri uri = new Uri (url);
IPHostEntry host = Dns.Resolve (uri.Host);
IPAddress ip = host.AddressList [0];
Socket socket = new Socket (ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Connect (new IPEndPoint (ip, uri.Port));
NetworkStream ns = new NetworkStream (socket, false);
var certs = new X509CertificateCollection ();
var ssl = new SslStream (ns, false, (s, cert, chain, p) => {
var elements = chain?.ChainPolicy?.ExtraStore;
if (elements != null && elements.Count > 0) {
foreach (var element in elements) {
certs.Add (new X509Certificate (element.RawData));
}
} else {
certs.Add (new X509Certificate (cert.GetRawCertData ()));
}
return true;
});
ssl.AuthenticateAsClient (uri.Host);
return certs;
}
static void Ssl (string host, bool machine, bool verbose)
{
if (verbose) {
Console.WriteLine ("Importing certificates from '{0}' into the {1} stores.",
host, machine ? "machine" : "user");
}
int n=0;
X509CertificateCollection coll = GetCertificatesFromSslSession (host);
if (coll != null) {
X509Store store = null;
// start by the end (root) so we can stop adding them anytime afterward
for (int i = coll.Count - 1; i >= 0; i--) {
X509Certificate x509 = coll [i];
bool selfsign = false;
bool failed = false;
try {
selfsign = x509.IsSelfSigned;
}
catch {
// sadly it's hard to interpret old certificates with MD2
// without manually changing the machine.config file
failed = true;
}
if (selfsign) {
// this is a root
store = GetStoreFromName (X509Stores.Names.TrustedRoot, machine);
} else if (i == 0) {
// server certificate isn't (generally) an intermediate CA
store = GetStoreFromName (X509Stores.Names.OtherPeople, machine);
} else {
// all other certificates should be intermediate CA
store = GetStoreFromName (X509Stores.Names.IntermediateCA, machine);
}
Console.WriteLine ("{0}{1}X.509 Certificate v{2}",
Environment.NewLine,
selfsign ? "Self-signed " : String.Empty,
x509.Version);
Console.WriteLine (" Issued from: {0}", x509.IssuerName);
Console.WriteLine (" Issued to: {0}", x509.SubjectName);
Console.WriteLine (" Valid from: {0}", x509.ValidFrom);
Console.WriteLine (" Valid until: {0}", x509.ValidUntil);
if (!x509.IsCurrent)
Console.WriteLine (" *** WARNING: Certificate isn't current ***");
if ((i > 0) && !selfsign) {
X509Certificate signer = coll [i-1];
bool signed = false;
try {
if (signer.RSA != null) {
signed = x509.VerifySignature (signer.RSA);
} else if (signer.DSA != null) {
signed = x509.VerifySignature (signer.DSA);
} else {
Console.WriteLine (" *** WARNING: Couldn't not find who signed this certificate ***");
signed = true; // skip next warning
}
if (!signed)
Console.WriteLine (" *** WARNING: Certificate signature is INVALID ***");
}
catch {
failed = true;
}
}
if (failed) {
Console.WriteLine (" *** ERROR: Couldn't decode certificate properly ***");
Console.WriteLine (" *** try 'man certmgr' for additional help or report to bugzilla.novell.com ***");
break;
}
if (store.Certificates.Contains (x509)) {
Console.WriteLine ("This certificate is already in the {0} store.", store.Name);
} else {
Console.Write ("Import this certificate into the {0} store ?", store.Name);
string answer = Console.ReadLine ().ToUpper ();
if ((answer == "YES") || (answer == "Y")) {
store.Import (x509);
n++;
} else {
if (verbose) {
Console.WriteLine ("Certificate not imported into store {0}.",
store.Name);
}
break;
}
}
}
}
Console.WriteLine ();
if (n == 0) {
Console.WriteLine ("No certificate were added to the stores.");
} else {
Console.WriteLine ("{0} certificate{1} added to the stores.",
n, (n == 1) ? String.Empty : "s");
}
}
static void ImportKey (ObjectType type, bool machine, string file, string password, bool verbose)
{
switch (type) {
case ObjectType.Certificate:
X509CertificateCollection coll = LoadCertificates (file, password, verbose);
int count = 0;
foreach (X509Certificate x509 in coll) {
RSACryptoServiceProvider pk = x509.RSA as RSACryptoServiceProvider;
if (pk == null || pk.PublicOnly)
continue;
CspParameters csp = new CspParameters ();
csp.KeyContainerName = CryptoConvert.ToHex (x509.Hash);
csp.Flags = machine ? CspProviderFlags.UseMachineKeyStore : 0;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider (csp);
rsa.ImportParameters (pk.ExportParameters (true));
rsa.PersistKeyInCsp = true;
count++;
}
Console.WriteLine ("{0} keys(s) imported to KeyPair {1} persister.",
count, machine ? "LocalMachine" : "CurrentUser");
break;
default:
throw new NotSupportedException (type.ToString ());
}
}
[STAThread]
static void Main (string[] args)
{
string password = null;
bool verbose = false;
bool pem = false;
bool machine = false;
Header ();
if (args.Length < 2) {
Help ();
return;
}
Action action = GetAction (args [0]);
ObjectType type = ObjectType.None;
int n = 1;
if (action != Action.Ssl) {
type = GetObjectType (args [n]);
if (type != ObjectType.None)
n++;
}
for (int i = n; i < args.Length; i++) {
switch (GetCommand (args[i])) {
case "V":
verbose = true;
n++;
break;
case "M":
machine = true;
n++;
break;
case "P":
password = args[++n];
n++;
break;
case "PEM":
pem = true;
n++;
break;
}
}
X509Store store = null;
string storeName = null;
if (action != Action.Ssl) {
if ((action == Action.None) || (type == ObjectType.None)) {
Help ();
return;
}
if (type == ObjectType.CTL) {
Console.WriteLine ("CTL are not supported");
return;
}
storeName = args [n++];
store = GetStoreFromName (storeName, machine);
if (store == null) {
Console.WriteLine ("Invalid Store: {0}", storeName);
Console.WriteLine ("Valid stores are: {0}, {1}, {2}, {3} and {4}",
X509Stores.Names.Personal,
X509Stores.Names.OtherPeople,
X509Stores.Names.IntermediateCA,
X509Stores.Names.TrustedRoot,
X509Stores.Names.Untrusted);
return;
}
}
string file = (n < args.Length) ? args [n] : null;
// now action!
try {
switch (action) {
case Action.Add:
Add (type, store, file, password, verbose);
break;
case Action.Delete:
Delete (type, store, file, verbose);
break;
case Action.Put:
Put (type, store, file, machine, pem, verbose);
break;
case Action.List:
List (type, store, machine, file, verbose);
break;
case Action.Ssl:
Ssl (file, machine, verbose);
break;
case Action.ImportKey:
ImportKey (type, machine, file, password, verbose);
break;
default:
throw new NotSupportedException (action.ToString ());
}
}
catch (UnauthorizedAccessException uae) {
Console.WriteLine ("Access to the {0} '{1}' certificate store has been denied.",
(machine ? "machine" : "user"), storeName);
if (verbose) {
Console.WriteLine (uae);
}
}
}
}
}