2 // CertMgr.cs: Certificate Manager clone tool (CLI version)
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
11 using System.Collections;
14 using System.Net.Sockets;
15 using System.Reflection;
16 using System.Security.Cryptography;
17 using SSCX = System.Security.Cryptography.X509Certificates;
20 using Mono.Security.Authenticode;
21 using Mono.Security.Cryptography;
22 using Mono.Security.X509;
23 using Mono.Security.Protocol.Tls;
25 [assembly: AssemblyTitle ("Mono Certificate Manager")]
26 [assembly: AssemblyDescription ("Manage X.509 certificates and CRL from stores.")]
28 namespace Mono.Tools {
30 class CertificateManager {
32 static private void Header ()
34 Console.WriteLine (new AssemblyInfo ().ToString ());
37 static private void Help ()
39 Console.WriteLine ("Usage: certmgr [action] [object-type] [options] store [filename]");
40 Console.WriteLine (" or: certmgr -list object-type [options] store");
41 Console.WriteLine (" or: certmgr -del object-type [options] store certhash");
42 Console.WriteLine (" or: certmgr -ssl [options] url");
44 Console.WriteLine ("actions");
45 Console.WriteLine ("\t-add\tAdd a certificate, CRL or CTL to specified store");
46 Console.WriteLine ("\t-del\tRemove a certificate, CRL or CTL to specified store");
47 Console.WriteLine ("\t-put\tCopy a certificate, CRL or CTL from a store to a file");
48 Console.WriteLine ("\t-list\tList certificates, CRL ot CTL in the specified store.");
49 Console.WriteLine ("\t-ssl\tDownload and add certificates from an SSL session");
50 Console.WriteLine ("object types");
51 Console.WriteLine ("\t-c\tadd/del/put certificates");
52 Console.WriteLine ("\t-crl\tadd/del/put certificate revocation lists");
53 Console.WriteLine ("\t-ctl\tadd/del/put certificate trust lists [unsupported]");
54 Console.WriteLine ("other options");
55 Console.WriteLine ("\t-m\tuse the machine certificate store (default to user)");
56 Console.WriteLine ("\t-v\tverbose mode (display status for every steps)");
57 Console.WriteLine ("\t-?\th[elp]\tDisplay this help message");
61 static string GetCommand (string arg)
63 if ((arg == null) || (arg.Length < 1))
68 return arg.Substring (1).ToUpper ();
72 int n = ((arg [1] == '-') ? 2 : 1);
73 return arg.Substring (n).ToUpper ();
88 static Action GetAction (string arg)
90 Action action = Action.None;
91 switch (GetCommand (arg)) {
97 action = Action.Delete;
104 action = Action.List;
121 static ObjectType GetObjectType (string arg)
123 ObjectType type = ObjectType.None;
124 switch (GetCommand (arg)) {
128 type = ObjectType.Certificate;
131 type = ObjectType.CRL;
134 type = ObjectType.CTL;
140 static X509Store GetStoreFromName (string storeName, bool machine)
142 X509Stores stores = ((machine) ? X509StoreManager.LocalMachine : X509StoreManager.CurrentUser);
143 X509Store store = null;
145 case X509Stores.Names.Personal:
146 return stores.Personal;
147 case X509Stores.Names.OtherPeople:
148 return stores.OtherPeople;
149 case X509Stores.Names.IntermediateCA:
150 return stores.IntermediateCA;
151 case "Root": // special case (same as trusted root)
152 case X509Stores.Names.TrustedRoot:
153 return stores.TrustedRoot;
154 case X509Stores.Names.Untrusted:
155 return stores.Untrusted;
160 static byte[] PEM (string type, byte[] data)
162 string pem = Encoding.ASCII.GetString (data);
163 string header = String.Format ("-----BEGIN {0}-----", type);
164 string footer = String.Format ("-----END {0}-----", type);
165 int start = pem.IndexOf (header) + header.Length;
166 int end = pem.IndexOf (footer, start);
167 string base64 = pem.Substring (start, (end - start));
168 return Convert.FromBase64String (base64);
171 static X509CertificateCollection LoadCertificates (string filename)
173 X509Certificate x509 = null;
174 X509CertificateCollection coll = new X509CertificateCollection ();
175 switch (Path.GetExtension (filename).ToUpper ()) {
178 SoftwarePublisherCertificate spc = SoftwarePublisherCertificate.CreateFromFile (filename);
179 coll.AddRange (spc.Certificates);
184 using (FileStream fs = File.OpenRead (filename)) {
185 byte[] data = new byte [fs.Length];
186 fs.Read (data, 0, data.Length);
187 if (data [0] != 0x30) {
188 // maybe it's ASCII PEM base64 encoded ?
189 data = PEM ("CERTIFICATE", data);
192 x509 = new X509Certificate (data);
199 // TODO - support PKCS12 with passwords
200 PKCS12 p12 = PKCS12.LoadFromFile (filename);
201 coll.AddRange (p12.Certificates);
205 Console.WriteLine ("Unknown file extension: {0}",
206 Path.GetExtension (filename));
212 static ArrayList LoadCRLs (string filename)
215 ArrayList list = new ArrayList ();
216 switch (Path.GetExtension (filename).ToUpper ()) {
219 SoftwarePublisherCertificate spc = SoftwarePublisherCertificate.CreateFromFile (filename);
220 list.AddRange (spc.Crls);
224 using (FileStream fs = File.OpenRead (filename)) {
225 byte[] data = new byte [fs.Length];
226 fs.Read (data, 0, data.Length);
227 crl = new X509Crl (data);
232 Console.WriteLine ("Unknown file extension: {0}",
233 Path.GetExtension (filename));
239 static void Add (ObjectType type, X509Store store, string file, bool verbose)
242 case ObjectType.Certificate:
243 X509CertificateCollection coll = LoadCertificates (file);
244 foreach (X509Certificate x509 in coll) {
247 Console.WriteLine ("{0} certificate(s) added to store {1}.",
248 coll.Count, store.Name);
251 ArrayList list = LoadCRLs (file);
252 foreach (X509Crl crl in list) {
255 Console.WriteLine ("{0} CRL(s) added to store {1}.",
256 list.Count, store.Name);
259 throw new NotSupportedException (type.ToString ());
263 static void Delete (ObjectType type, X509Store store, string hash, bool verbose)
266 case ObjectType.Certificate:
267 foreach (X509Certificate x509 in store.Certificates) {
268 if (hash == CryptoConvert.ToHex (x509.Hash)) {
270 Console.WriteLine ("Certificate removed from store.");
276 foreach (X509Crl crl in store.Crls) {
277 if (hash == CryptoConvert.ToHex (crl.Hash)) {
279 Console.WriteLine ("CRL removed from store.");
285 throw new NotSupportedException (type.ToString ());
289 static void Put (ObjectType type, X509Store store, string file, bool verbose)
291 throw new NotImplementedException ("Put not yet supported");
293 case ObjectType.Certificate:
299 throw new NotSupportedException (type.ToString ());
303 static void DisplayCertificate (X509Certificate x509, bool verbose)
305 Console.WriteLine ("{0}X.509 v{1} Certificate", (x509.IsSelfSigned ? "Self-signed " : String.Empty), x509.Version);
306 Console.WriteLine (" Serial Number: {0}", CryptoConvert.ToHex (x509.SerialNumber));
307 Console.WriteLine (" Issuer Name: {0}", x509.IssuerName);
308 Console.WriteLine (" Subject Name: {0}", x509.SubjectName);
309 Console.WriteLine (" Valid From: {0}", x509.ValidFrom);
310 Console.WriteLine (" Valid Until: {0}", x509.ValidUntil);
311 Console.WriteLine (" Unique Hash: {0}", CryptoConvert.ToHex (x509.Hash));
313 Console.WriteLine (" Key Algorithm: {0}", x509.KeyAlgorithm);
314 Console.WriteLine (" Algorithm Parameters: {0}", (x509.KeyAlgorithmParameters == null) ? "None" :
315 CryptoConvert.ToHex (x509.KeyAlgorithmParameters));
316 Console.WriteLine (" Public Key: {0}", CryptoConvert.ToHex (x509.PublicKey));
317 Console.WriteLine (" Signature Algorithm: {0}", x509.SignatureAlgorithm);
318 Console.WriteLine (" Algorithm Parameters: {0}", (x509.SignatureAlgorithmParameters == null) ? "None" :
319 CryptoConvert.ToHex (x509.SignatureAlgorithmParameters));
320 Console.WriteLine (" Signature: {0}", CryptoConvert.ToHex (x509.Signature));
322 Console.WriteLine ();
325 static void List (ObjectType type, X509Store store, string file, bool verbose)
328 case ObjectType.Certificate:
329 foreach (X509Certificate x509 in store.Certificates) {
330 DisplayCertificate (x509, verbose);
337 throw new NotSupportedException (type.ToString ());
341 static X509CertificateCollection GetCertificatesFromSslSession (string url)
343 Uri uri = new Uri (url);
344 IPHostEntry host = Dns.Resolve (uri.Host);
345 IPAddress ip = host.AddressList [0];
346 Socket socket = new Socket (ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
347 socket.Connect (new IPEndPoint (ip, uri.Port));
348 NetworkStream ns = new NetworkStream (socket, false);
349 SslClientStream ssl = new SslClientStream (ns, uri.Host, false, Mono.Security.Protocol.Tls.SecurityProtocolType.Default, null);
350 ssl.ServerCertValidationDelegate += new CertificateValidationCallback (CertificateValidation);
353 // we don't really want to write to the server (as we don't know
354 // the protocol it using) but we must send something to be sure the
355 // SSL handshake is done (so we receive the X.509 certificates).
356 StreamWriter sw = new StreamWriter (ssl);
357 sw.WriteLine (Environment.NewLine);
359 socket.Poll (30000, SelectMode.SelectRead);
365 // we need a little reflection magic to get this information
366 PropertyInfo pi = typeof (SslStreamBase).GetProperty ("ServerCertificates", BindingFlags.Instance | BindingFlags.NonPublic);
368 Console.WriteLine ("Sorry but you need a newer version of Mono.Security.dll to use this feature.");
371 return (X509CertificateCollection) pi.GetValue (ssl, null);
374 static bool CertificateValidation (SSCX.X509Certificate certificate, int[] certificateErrors)
376 // the main reason to download it is that it's not trusted
378 // OTOH we ask user confirmation before adding certificates into the stores
381 static void Ssl (string host, bool machine, bool verbose)
384 Console.WriteLine ("Importing certificates from '{0}' into the {1} stores.",
385 host, machine ? "machine" : "user");
389 X509CertificateCollection coll = GetCertificatesFromSslSession (host);
391 X509Store store = null;
392 // start by the end (root) so we can stop adding them anytime afterward
393 for (int i = coll.Count - 1; i >= 0; i--) {
394 X509Certificate x509 = coll [i];
395 bool selfsign = false;
398 selfsign = x509.IsSelfSigned;
401 // sadly it's hard to interpret old certificates with MD2
402 // without manually changing the machine.config file
408 store = GetStoreFromName (X509Stores.Names.TrustedRoot, machine);
410 // server certificate isn't (generally) an intermediate CA
411 store = GetStoreFromName (X509Stores.Names.OtherPeople, machine);
413 // all other certificates should be intermediate CA
414 store = GetStoreFromName (X509Stores.Names.IntermediateCA, machine);
417 Console.WriteLine ("{0}{1}X.509 Certificate v{2}",
419 selfsign ? "Self-signed " : String.Empty,
421 Console.WriteLine (" Issued from: {0}", x509.IssuerName);
422 Console.WriteLine (" Issued to: {0}", x509.SubjectName);
423 Console.WriteLine (" Valid from: {0}", x509.ValidFrom);
424 Console.WriteLine (" Valid until: {0}", x509.ValidUntil);
427 Console.WriteLine (" *** WARNING: Certificate isn't current ***");
428 if ((i > 0) && !selfsign) {
429 X509Certificate signer = coll [i-1];
432 if (signer.RSA != null) {
433 signed = x509.VerifySignature (signer.RSA);
434 } else if (signer.DSA != null) {
435 signed = x509.VerifySignature (signer.DSA);
437 Console.WriteLine (" *** WARNING: Couldn't not find who signed this certificate ***");
438 signed = true; // skip next warning
442 Console.WriteLine (" *** WARNING: Certificate signature is INVALID ***");
449 Console.WriteLine (" *** ERROR: Couldn't decode certificate properly ***");
450 Console.WriteLine (" *** try 'man certmgr' for additional help or report to bugzilla.novell.com ***");
454 if (store.Certificates.Contains (x509)) {
455 Console.WriteLine ("This certificate is already in the {0} store.", store.Name);
457 Console.Write ("Import this certificate into the {0} store ?", store.Name);
458 string answer = Console.ReadLine ().ToUpper ();
459 if ((answer == "YES") || (answer == "Y")) {
464 Console.WriteLine ("Certificate not imported into store {0}.",
473 Console.WriteLine ();
475 Console.WriteLine ("No certificate were added to the stores.");
477 Console.WriteLine ("{0} certificate{1} added to the stores.",
478 n, (n == 1) ? String.Empty : "s");
483 static void Main (string[] args)
486 if (args.Length < 2) {
491 Action action = GetAction (args [0]);
492 ObjectType type = ObjectType.None;
495 if (action != Action.Ssl) {
496 type = GetObjectType (args [n]);
497 if (type != ObjectType.None)
501 bool verbose = (GetCommand (args [n]) == "V");
504 bool machine = (GetCommand (args [n]) == "M");
508 X509Store store = null;
509 string storeName = null;
510 if (action != Action.Ssl) {
511 if ((action == Action.None) || (type == ObjectType.None)) {
515 if (type == ObjectType.CTL) {
516 Console.WriteLine ("CTL are not supported");
520 storeName = args [n++];
521 store = GetStoreFromName (storeName, machine);
523 Console.WriteLine ("Invalid Store: {0}", storeName);
524 Console.WriteLine ("Valid stores are: {0}, {1}, {2}, {3} and {4}",
525 X509Stores.Names.Personal,
526 X509Stores.Names.OtherPeople,
527 X509Stores.Names.IntermediateCA,
528 X509Stores.Names.TrustedRoot,
529 X509Stores.Names.Untrusted);
534 string file = (n < args.Length) ? args [n] : null;
540 Add (type, store, file, verbose);
543 Delete (type, store, file, verbose);
546 Put (type, store, file, verbose);
549 List (type, store, file, verbose);
552 Ssl (file, machine, verbose);
555 throw new NotSupportedException (action.ToString ());
558 catch (UnauthorizedAccessException uae) {
559 Console.WriteLine ("Access to the {0} '{1}' certificate store has been denied.",
560 (machine ? "machine" : "user"), storeName);
562 Console.WriteLine (uae);