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 X509Stores.Names.TrustedRoot:
152 return stores.TrustedRoot;
153 case X509Stores.Names.Untrusted:
154 return stores.Untrusted;
159 static byte[] PEM (string type, byte[] data)
161 string pem = Encoding.ASCII.GetString (data);
162 string header = String.Format ("-----BEGIN {0}-----", type);
163 string footer = String.Format ("-----END {0}-----", type);
164 int start = pem.IndexOf (header) + header.Length;
165 int end = pem.IndexOf (footer, start);
166 string base64 = pem.Substring (start, (end - start));
167 return Convert.FromBase64String (base64);
170 static X509CertificateCollection LoadCertificates (string filename)
172 X509Certificate x509 = null;
173 X509CertificateCollection coll = new X509CertificateCollection ();
174 switch (Path.GetExtension (filename).ToUpper ()) {
177 SoftwarePublisherCertificate spc = SoftwarePublisherCertificate.CreateFromFile (filename);
178 coll.AddRange (spc.Certificates);
183 using (FileStream fs = File.OpenRead (filename)) {
184 byte[] data = new byte [fs.Length];
185 fs.Read (data, 0, data.Length);
186 if (data [0] != 0x30) {
187 // maybe it's ASCII PEM base64 encoded ?
188 data = PEM ("CERTIFICATE", data);
191 x509 = new X509Certificate (data);
198 // TODO - support PKCS12 with passwords
199 PKCS12 p12 = PKCS12.LoadFromFile (filename);
200 coll.AddRange (p12.Certificates);
204 Console.WriteLine ("Unknown file extension: {0}",
205 Path.GetExtension (filename));
211 static ArrayList LoadCRLs (string filename)
214 ArrayList list = new ArrayList ();
215 switch (Path.GetExtension (filename).ToUpper ()) {
218 SoftwarePublisherCertificate spc = SoftwarePublisherCertificate.CreateFromFile (filename);
219 list.AddRange (spc.Crls);
223 using (FileStream fs = File.OpenRead (filename)) {
224 byte[] data = new byte [fs.Length];
225 fs.Read (data, 0, data.Length);
226 crl = new X509Crl (data);
231 Console.WriteLine ("Unknown file extension: {0}",
232 Path.GetExtension (filename));
238 static void Add (ObjectType type, X509Store store, string file, bool verbose)
241 case ObjectType.Certificate:
242 X509CertificateCollection coll = LoadCertificates (file);
243 foreach (X509Certificate x509 in coll) {
246 Console.WriteLine ("{0} certificate(s) added to store {1}.",
247 coll.Count, store.Name);
250 // TODO ArrayList list = LoadCRLs (file);
251 throw new NotImplementedException ("Adding CRL not yet supported");
253 throw new NotSupportedException (type.ToString ());
257 static void Delete (ObjectType type, X509Store store, string hash, bool verbose)
260 case ObjectType.Certificate:
261 foreach (X509Certificate x509 in store.Certificates) {
262 if (hash == CryptoConvert.ToHex (x509.Hash)) {
264 Console.WriteLine ("Certificate removed from store.");
270 throw new NotImplementedException ("Delete not yet supported");
272 throw new NotSupportedException (type.ToString ());
276 static void Put (ObjectType type, X509Store store, string file, bool verbose)
278 throw new NotImplementedException ("Put not yet supported");
280 case ObjectType.Certificate:
286 throw new NotSupportedException (type.ToString ());
290 static void DisplayCertificate (X509Certificate x509, bool verbose)
292 Console.WriteLine ("{0}X.509 v{1} Certificate", (x509.IsSelfSigned ? "Self-signed " : String.Empty), x509.Version);
293 Console.WriteLine (" Serial Number: {0}", CryptoConvert.ToHex (x509.SerialNumber));
294 Console.WriteLine (" Issuer Name: {0}", x509.IssuerName);
295 Console.WriteLine (" Subject Name: {0}", x509.SubjectName);
296 Console.WriteLine (" Valid From: {0}", x509.ValidFrom);
297 Console.WriteLine (" Valid Until: {0}", x509.ValidUntil);
298 Console.WriteLine (" Unique Hash: {0}", CryptoConvert.ToHex (x509.Hash));
300 Console.WriteLine (" Key Algorithm: {0}", x509.KeyAlgorithm);
301 Console.WriteLine (" Algorithm Parameters: {0}", (x509.KeyAlgorithmParameters == null) ? "None" :
302 CryptoConvert.ToHex (x509.KeyAlgorithmParameters));
303 Console.WriteLine (" Public Key: {0}", CryptoConvert.ToHex (x509.PublicKey));
304 Console.WriteLine (" Signature Algorithm: {0}", x509.SignatureAlgorithm);
305 Console.WriteLine (" Algorithm Parameters: {0}", (x509.SignatureAlgorithmParameters == null) ? "None" :
306 CryptoConvert.ToHex (x509.SignatureAlgorithmParameters));
307 Console.WriteLine (" Signature: {0}", CryptoConvert.ToHex (x509.Signature));
309 Console.WriteLine ();
312 static void List (ObjectType type, X509Store store, string file, bool verbose)
315 case ObjectType.Certificate:
316 foreach (X509Certificate x509 in store.Certificates) {
317 DisplayCertificate (x509, verbose);
324 throw new NotSupportedException (type.ToString ());
328 static X509CertificateCollection GetCertificatesFromSslSession (string url)
330 Uri uri = new Uri (url);
331 IPHostEntry host = Dns.Resolve (uri.Host);
332 IPAddress ip = host.AddressList [0];
333 Socket socket = new Socket (ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
334 socket.Connect (new IPEndPoint (ip, uri.Port));
335 NetworkStream ns = new NetworkStream (socket, false);
336 SslClientStream ssl = new SslClientStream (ns, uri.Host, false, Mono.Security.Protocol.Tls.SecurityProtocolType.Default, null);
337 ssl.ServerCertValidationDelegate += new CertificateValidationCallback (CertificateValidation);
340 // we don't really want to write to the server (as we don't know
341 // the protocol it using) but we must send something to be sure the
342 // SSL handshake is done (so we receive the X.509 certificates).
343 StreamWriter sw = new StreamWriter (ssl);
344 sw.WriteLine (Environment.NewLine);
346 socket.Poll (30000, SelectMode.SelectRead);
352 // we need a little reflection magic to get this information
353 PropertyInfo pi = typeof (SslClientStream).GetProperty ("ServerCertificates", BindingFlags.Instance | BindingFlags.NonPublic);
355 Console.WriteLine ("Sorry but you need a newer version of Mono.Security.dll to use this feature.");
358 return (X509CertificateCollection) pi.GetValue (ssl, null);
361 static bool CertificateValidation (SSCX.X509Certificate certificate, int[] certificateErrors)
363 // the main reason to download it is that it's not trusted
365 // OTOH we ask user confirmation before adding certificates into the stores
368 static void Ssl (string host, bool machine, bool verbose)
371 Console.WriteLine ("Importing certificates from '{0}' into the {1} stores.",
372 host, machine ? "machine" : "user");
376 X509CertificateCollection coll = GetCertificatesFromSslSession (host);
378 X509Store store = null;
379 // start by the end (root) so we can stop adding them anytime afterward
380 for (int i = coll.Count - 1; i >= 0; i--) {
381 X509Certificate x509 = coll [i];
382 bool selfsign = false;
385 selfsign = x509.IsSelfSigned;
388 // sadly it's hard to interpret old certificates with MD2
389 // without manually changing the machine.config file
395 store = GetStoreFromName (X509Stores.Names.TrustedRoot, machine);
397 // server certificate isn't (generally) an intermediate CA
398 store = GetStoreFromName (X509Stores.Names.OtherPeople, machine);
400 // all other certificates should be intermediate CA
401 store = GetStoreFromName (X509Stores.Names.IntermediateCA, machine);
404 Console.WriteLine ("{0}{1} X.509 Certificate v{2}",
406 selfsign ? "Self-signed " : String.Empty,
408 Console.WriteLine (" Issued from: {0}", x509.IssuerName);
409 Console.WriteLine (" Issued to: {0}", x509.SubjectName);
410 Console.WriteLine (" Valid from: {0}", x509.ValidFrom);
411 Console.WriteLine (" Valid until: {0}", x509.ValidUntil);
414 Console.WriteLine (" *** WARNING: Certificate isn't current ***");
415 if ((i > 0) && !selfsign) {
416 X509Certificate signer = coll [i-1];
419 if (signer.RSA != null) {
420 signed = x509.VerifySignature (signer.RSA);
421 } else if (signer.DSA != null) {
422 signed = x509.VerifySignature (signer.RSA);
424 Console.WriteLine (" *** WARNING: Couldn't not find who signed this certificate ***");
425 signed = true; // skip next warning
429 Console.WriteLine (" *** WARNING: Certificate signature is INVALID ***");
436 Console.WriteLine (" *** ERROR: Couldn't decode certificate properly ***");
437 Console.WriteLine (" *** try 'man certmgr' for additional help or report to bugzilla.ximian.com ***");
441 if (store.Certificates.Contains (x509)) {
442 Console.WriteLine ("This certificate is already in the {0} store.", store.Name);
444 Console.Write ("Import this certificate into the {0} store ?", store.Name);
445 string answer = Console.ReadLine ().ToUpper ();
446 if ((answer == "YES") || (answer == "Y")) {
451 Console.WriteLine ("Certificate not imported into store {0}.",
460 Console.WriteLine ();
462 Console.WriteLine ("No certificate were added to the stores.");
464 Console.WriteLine ("{0} certificate{1} added to the stores.",
465 n, (n == 1) ? String.Empty : "s");
470 static void Main (string[] args)
473 if (args.Length < 2) {
478 Action action = GetAction (args [0]);
479 ObjectType type = ObjectType.None;
482 if (action != Action.Ssl)
483 type = GetObjectType (args [n++]);
485 bool verbose = (GetCommand (args [n]) == "V");
488 bool machine = (GetCommand (args [n]) == "M");
492 X509Store store = null;
493 string storeName = null;
494 if (action != Action.Ssl) {
495 if ((action == Action.None) || (type == ObjectType.None)) {
499 if (type == ObjectType.CTL) {
500 Console.WriteLine ("CTL are not supported");
504 storeName = args [n++];
505 store = GetStoreFromName (storeName, machine);
507 Console.WriteLine ("Invalid Store: {0}", storeName);
508 Console.WriteLine ("Valid stores are: {0}, {1}, {2}, {3} and {4}",
509 X509Stores.Names.Personal,
510 X509Stores.Names.OtherPeople,
511 X509Stores.Names.IntermediateCA,
512 X509Stores.Names.TrustedRoot,
513 X509Stores.Names.Untrusted);
518 string file = (n < args.Length) ? args [n] : null;
524 Add (type, store, file, verbose);
527 Delete (type, store, file, verbose);
530 Put (type, store, file, verbose);
533 List (type, store, file, verbose);
536 Ssl (file, machine, verbose);
539 throw new NotSupportedException (action.ToString ());
542 catch (UnauthorizedAccessException uae) {
543 Console.WriteLine ("Access to the {0} '{1}' certificate store has been denied.",
544 (machine ? "machine" : "user"), storeName);
546 Console.WriteLine (uae);