Null constant cannot be used for ref/out variables
[mono.git] / mcs / tools / security / certmgr.cs
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 //
9
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;
19
20 using Mono.Security.Authenticode;
21 using Mono.Security.Cryptography;
22 using Mono.Security.X509;
23 using Mono.Security.Protocol.Tls;
24
25 [assembly: AssemblyTitle ("Mono Certificate Manager")]
26 [assembly: AssemblyDescription ("Manage X.509 certificates and CRL from stores.")]
27
28 namespace Mono.Tools {
29
30         class CertificateManager {
31
32                 static private void Header () 
33                 {
34                         Console.WriteLine (new AssemblyInfo ().ToString ());
35                 }
36
37                 static private void Help () 
38                 {
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 ();
59                 }
60
61                 static string GetCommand (string arg) 
62                 {
63                         if ((arg == null) || (arg.Length < 1))
64                                 return null;
65
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;
76                         }
77                 }
78
79                 enum Action {
80                         None,
81                         Add,
82                         Delete,
83                         Put,
84                         List,
85                         Ssl
86                 }
87
88                 static Action GetAction (string arg) 
89                 {
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;
110                         }
111                         return action;
112                 }
113
114                 enum ObjectType {
115                         None,
116                         Certificate,
117                         CRL,
118                         CTL
119                 }
120
121                 static ObjectType GetObjectType (string arg) 
122                 {
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;
136                         }
137                         return type;
138                 }
139
140                 static X509Store GetStoreFromName (string storeName, bool machine) 
141                 {
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;
156                         }
157                         return store;
158                 }
159
160                 static byte[] PEM (string type, byte[] data) 
161                 {
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);
169                 }
170
171                 static X509CertificateCollection LoadCertificates (string filename) 
172                 {
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);
190                                                 }
191                                                 if (data != null)
192                                                         x509 = new X509Certificate (data);
193                                         }
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;
208                         }
209                         return coll;
210                 }
211
212                 static ArrayList LoadCRLs (string filename) 
213                 {
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);
228                                         }
229                                         list.Add (crl);
230                                         break;
231                                 default:
232                                         Console.WriteLine ("Unknown file extension: {0}", 
233                                                 Path.GetExtension (filename));
234                                         break;
235                         }
236                         return list;
237                 }
238
239                 static void Add (ObjectType type, X509Store store, string file, bool verbose) 
240                 {
241                         switch (type) {
242                                 case ObjectType.Certificate:
243                                         X509CertificateCollection coll = LoadCertificates (file);
244                                         foreach (X509Certificate x509 in coll) {
245                                                 store.Import (x509);
246                                         }
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);
254                                         }
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 ());
260                         }
261                 }
262
263                 static void Delete (ObjectType type, X509Store store, string hash, bool verbose) 
264                 {
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;
272                                                 }
273                                         }
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;
281                                                 }
282                                         }
283                                         break;
284                                 default:
285                                         throw new NotSupportedException (type.ToString ());
286                         }
287                 }
288
289                 static void Put (ObjectType type, X509Store store, string file, bool verbose) 
290                 {
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 ());
300                         }*/
301                 }
302
303                 static void DisplayCertificate (X509Certificate x509, bool verbose)
304                 {
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));
321                         }
322                         Console.WriteLine ();
323                 }
324
325                 static void List (ObjectType type, X509Store store, string file, bool verbose) 
326                 {
327                         switch (type) {
328                                 case ObjectType.Certificate:
329                                         foreach (X509Certificate x509 in store.Certificates) {
330                                                 DisplayCertificate (x509, verbose);
331                                         }
332                                         break;
333                                 case ObjectType.CRL:
334                                         // TODO
335                                         break;
336                                 default:
337                                         throw new NotSupportedException (type.ToString ());
338                         }
339                 }
340
341                 static X509CertificateCollection GetCertificatesFromSslSession (string url) 
342                 {
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);
351
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);
360                         }
361                         finally {
362                                 socket.Close ();
363                         }
364
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;
370                         }
371                         return (X509CertificateCollection) pi.GetValue (ssl, null);
372                 }
373
374                 static bool CertificateValidation (SSCX.X509Certificate certificate, int[] certificateErrors)
375                 {
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
379                 }
380
381                 static void Ssl (string host, bool machine, bool verbose) 
382                 {
383                         if (verbose) {
384                                 Console.WriteLine ("Importing certificates from '{0}' into the {1} stores.",
385                                         host, machine ? "machine" : "user");
386                         }
387                         int n=0;
388
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;
399                                         }
400                                         catch {
401                                                 // sadly it's hard to interpret old certificates with MD2
402                                                 // without manually changing the machine.config file
403                                                 failed = true;
404                                         }
405
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);
415                                         }
416
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);
425
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.DSA);
436                                                         } else {
437                                                                 Console.WriteLine ("   *** WARNING: Couldn't not find who signed this certificate ***");
438                                                                 signed = true; // skip next warning
439                                                         }
440
441                                                         if (!signed)
442                                                                 Console.WriteLine ("   *** WARNING: Certificate signature is INVALID ***");
443                                                 }
444                                                 catch {
445                                                         failed = true;
446                                                 }
447                                         }
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.novell.com ***");
451                                                 break;
452                                         }
453
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);
466                                                         }
467                                                         break;
468                                                 }
469                                         }
470                                 }
471                         }
472
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");
479                         }
480                 }
481
482                 [STAThread]
483                 static void Main (string[] args)
484                 {
485                         Header ();
486                         if (args.Length < 2) {
487                                 Help ();
488                                 return;
489                         }
490
491                         Action action = GetAction (args [0]);
492                         ObjectType type = ObjectType.None;
493
494                         int n = 1;
495                         if (action != Action.Ssl) {
496                                 type = GetObjectType (args [n]);
497                                 if (type != ObjectType.None)
498                                         n++;
499                         }
500                         
501                         bool verbose = (GetCommand (args [n]) == "V");
502                         if (verbose)
503                                 n++;
504                         bool machine = (GetCommand (args [n]) == "M");
505                         if (machine)
506                                 n++;
507
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;
514                                 }
515                                 if (type == ObjectType.CTL) {
516                                         Console.WriteLine ("CTL are not supported");
517                                         return;
518                                 }
519
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;
531                                 }
532                         }
533
534                         string file = (n < args.Length) ? args [n] : null;
535
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 ());
556                                 }
557                         }
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);
563                                 }
564                         }
565                 }
566         }
567 }