2010-05-25 Jb Evain <jbevain@novell.com>
[mcs.git] / tools / security / certmgr.cs
blob8c79e2dcc5723398d231521680838f8209c655fb
1 //
2 // CertMgr.cs: Certificate Manager clone tool (CLI version)
3 //
4 // Author:
5 // Sebastien Pouliot <sebastien@ximian.com>
6 //
7 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
8 //
10 using System;
11 using System.Collections;
12 using System.IO;
13 using System.Net;
14 using System.Net.Sockets;
15 using System.Reflection;
16 using System.Security.Cryptography;
17 using SSCX = System.Security.Cryptography.X509Certificates;
18 using System.Text;
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");
43 Console.WriteLine ();
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");
58 Console.WriteLine ();
61 static string GetCommand (string arg)
63 if ((arg == null) || (arg.Length < 1))
64 return null;
66 switch (arg [0]) {
67 case '/':
68 return arg.Substring (1).ToUpper ();
69 case '-':
70 if (arg.Length < 2)
71 return null;
72 int n = ((arg [1] == '-') ? 2 : 1);
73 return arg.Substring (n).ToUpper ();
74 default:
75 return arg;
79 enum Action {
80 None,
81 Add,
82 Delete,
83 Put,
84 List,
85 Ssl
88 static Action GetAction (string arg)
90 Action action = Action.None;
91 switch (GetCommand (arg)) {
92 case "ADD":
93 action = Action.Add;
94 break;
95 case "DEL":
96 case "DELETE":
97 action = Action.Delete;
98 break;
99 case "PUT":
100 action = Action.Put;
101 break;
102 case "LST":
103 case "LIST":
104 action = Action.List;
105 break;
106 case "SSL":
107 case "TLS":
108 action = Action.Ssl;
109 break;
111 return action;
114 enum ObjectType {
115 None,
116 Certificate,
117 CRL,
121 static ObjectType GetObjectType (string arg)
123 ObjectType type = ObjectType.None;
124 switch (GetCommand (arg)) {
125 case "C":
126 case "CERT":
127 case "CERTIFICATE":
128 type = ObjectType.Certificate;
129 break;
130 case "CRL":
131 type = ObjectType.CRL;
132 break;
133 case "CTL":
134 type = ObjectType.CTL;
135 break;
137 return type;
140 static X509Store GetStoreFromName (string storeName, bool machine)
142 X509Stores stores = ((machine) ? X509StoreManager.LocalMachine : X509StoreManager.CurrentUser);
143 X509Store store = null;
144 switch (storeName) {
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;
157 return store;
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 ()) {
176 case ".P7B":
177 case ".SPC":
178 SoftwarePublisherCertificate spc = SoftwarePublisherCertificate.CreateFromFile (filename);
179 coll.AddRange (spc.Certificates);
180 spc = null;
181 break;
182 case ".CER":
183 case ".CRT":
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);
191 if (data != null)
192 x509 = new X509Certificate (data);
194 if (x509 != null)
195 coll.Add (x509);
196 break;
197 case ".P12":
198 case ".PFX":
199 // TODO - support PKCS12 with passwords
200 PKCS12 p12 = PKCS12.LoadFromFile (filename);
201 coll.AddRange (p12.Certificates);
202 p12 = null;
203 break;
204 default:
205 Console.WriteLine ("Unknown file extension: {0}",
206 Path.GetExtension (filename));
207 break;
209 return coll;
212 static ArrayList LoadCRLs (string filename)
214 X509Crl crl = null;
215 ArrayList list = new ArrayList ();
216 switch (Path.GetExtension (filename).ToUpper ()) {
217 case ".P7B":
218 case ".SPC":
219 SoftwarePublisherCertificate spc = SoftwarePublisherCertificate.CreateFromFile (filename);
220 list.AddRange (spc.Crls);
221 spc = null;
222 break;
223 case ".CRL":
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);
229 list.Add (crl);
230 break;
231 default:
232 Console.WriteLine ("Unknown file extension: {0}",
233 Path.GetExtension (filename));
234 break;
236 return list;
239 static void Add (ObjectType type, X509Store store, string file, bool verbose)
241 switch (type) {
242 case ObjectType.Certificate:
243 X509CertificateCollection coll = LoadCertificates (file);
244 foreach (X509Certificate x509 in coll) {
245 store.Import (x509);
247 Console.WriteLine ("{0} certificate(s) added to store {1}.",
248 coll.Count, store.Name);
249 break;
250 case ObjectType.CRL:
251 ArrayList list = LoadCRLs (file);
252 foreach (X509Crl crl in list) {
253 store.Import (crl);
255 Console.WriteLine ("{0} CRL(s) added to store {1}.",
256 list.Count, store.Name);
257 break;
258 default:
259 throw new NotSupportedException (type.ToString ());
263 static void Delete (ObjectType type, X509Store store, string hash, bool verbose)
265 switch (type) {
266 case ObjectType.Certificate:
267 foreach (X509Certificate x509 in store.Certificates) {
268 if (hash == CryptoConvert.ToHex (x509.Hash)) {
269 store.Remove (x509);
270 Console.WriteLine ("Certificate removed from store.");
271 return;
274 break;
275 case ObjectType.CRL:
276 foreach (X509Crl crl in store.Crls) {
277 if (hash == CryptoConvert.ToHex (crl.Hash)) {
278 store.Remove (crl);
279 Console.WriteLine ("CRL removed from store.");
280 return;
283 break;
284 default:
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");
292 /* switch (type) {
293 case ObjectType.Certificate:
294 break;
295 case ObjectType.CRL:
296 // TODO
297 break;
298 default:
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));
312 if (verbose) {
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)
327 switch (type) {
328 case ObjectType.Certificate:
329 foreach (X509Certificate x509 in store.Certificates) {
330 DisplayCertificate (x509, verbose);
332 break;
333 case ObjectType.CRL:
334 // TODO
335 break;
336 default:
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);
352 try {
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);
358 sw.Flush ();
359 socket.Poll (30000, SelectMode.SelectRead);
361 finally {
362 socket.Close ();
365 // we need a little reflection magic to get this information
366 PropertyInfo pi = typeof (SslStreamBase).GetProperty ("ServerCertificates", BindingFlags.Instance | BindingFlags.NonPublic);
367 if (pi == null) {
368 Console.WriteLine ("Sorry but you need a newer version of Mono.Security.dll to use this feature.");
369 return null;
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
377 return true;
378 // OTOH we ask user confirmation before adding certificates into the stores
381 static void Ssl (string host, bool machine, bool verbose)
383 if (verbose) {
384 Console.WriteLine ("Importing certificates from '{0}' into the {1} stores.",
385 host, machine ? "machine" : "user");
387 int n=0;
389 X509CertificateCollection coll = GetCertificatesFromSslSession (host);
390 if (coll != null) {
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;
396 bool failed = false;
397 try {
398 selfsign = x509.IsSelfSigned;
400 catch {
401 // sadly it's hard to interpret old certificates with MD2
402 // without manually changing the machine.config file
403 failed = true;
406 if (selfsign) {
407 // this is a root
408 store = GetStoreFromName (X509Stores.Names.TrustedRoot, machine);
409 } else if (i == 0) {
410 // server certificate isn't (generally) an intermediate CA
411 store = GetStoreFromName (X509Stores.Names.OtherPeople, machine);
412 } else {
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}",
418 Environment.NewLine,
419 selfsign ? "Self-signed " : String.Empty,
420 x509.Version);
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);
426 if (!x509.IsCurrent)
427 Console.WriteLine (" *** WARNING: Certificate isn't current ***");
428 if ((i > 0) && !selfsign) {
429 X509Certificate signer = coll [i-1];
430 bool signed = false;
431 try {
432 if (signer.RSA != null) {
433 signed = x509.VerifySignature (signer.RSA);
434 } else if (signer.DSA != null) {
435 signed = x509.VerifySignature (signer.RSA);
436 } else {
437 Console.WriteLine (" *** WARNING: Couldn't not find who signed this certificate ***");
438 signed = true; // skip next warning
441 if (!signed)
442 Console.WriteLine (" *** WARNING: Certificate signature is INVALID ***");
444 catch {
445 failed = true;
448 if (failed) {
449 Console.WriteLine (" *** ERROR: Couldn't decode certificate properly ***");
450 Console.WriteLine (" *** try 'man certmgr' for additional help or report to bugzilla.ximian.com ***");
451 break;
454 if (store.Certificates.Contains (x509)) {
455 Console.WriteLine ("This certificate is already in the {0} store.", store.Name);
456 } else {
457 Console.Write ("Import this certificate into the {0} store ?", store.Name);
458 string answer = Console.ReadLine ().ToUpper ();
459 if ((answer == "YES") || (answer == "Y")) {
460 store.Import (x509);
461 n++;
462 } else {
463 if (verbose) {
464 Console.WriteLine ("Certificate not imported into store {0}.",
465 store.Name);
467 break;
473 Console.WriteLine ();
474 if (n == 0) {
475 Console.WriteLine ("No certificate were added to the stores.");
476 } else {
477 Console.WriteLine ("{0} certificate{1} added to the stores.",
478 n, (n == 1) ? String.Empty : "s");
482 [STAThread]
483 static void Main (string[] args)
485 Header ();
486 if (args.Length < 2) {
487 Help ();
488 return;
491 Action action = GetAction (args [0]);
492 ObjectType type = ObjectType.None;
494 int n = 1;
495 if (action != Action.Ssl) {
496 type = GetObjectType (args [n]);
497 if (type != ObjectType.None)
498 n++;
501 bool verbose = (GetCommand (args [n]) == "V");
502 if (verbose)
503 n++;
504 bool machine = (GetCommand (args [n]) == "M");
505 if (machine)
506 n++;
508 X509Store store = null;
509 string storeName = null;
510 if (action != Action.Ssl) {
511 if ((action == Action.None) || (type == ObjectType.None)) {
512 Help ();
513 return;
515 if (type == ObjectType.CTL) {
516 Console.WriteLine ("CTL are not supported");
517 return;
520 storeName = args [n++];
521 store = GetStoreFromName (storeName, machine);
522 if (store == null) {
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);
530 return;
534 string file = (n < args.Length) ? args [n] : null;
536 // now action!
537 try {
538 switch (action) {
539 case Action.Add:
540 Add (type, store, file, verbose);
541 break;
542 case Action.Delete:
543 Delete (type, store, file, verbose);
544 break;
545 case Action.Put:
546 Put (type, store, file, verbose);
547 break;
548 case Action.List:
549 List (type, store, file, verbose);
550 break;
551 case Action.Ssl:
552 Ssl (file, machine, verbose);
553 break;
554 default:
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);
561 if (verbose) {
562 Console.WriteLine (uae);