Fix typo in OIDs corresponding to SHA256, SHA384, and SHA512 (#21707)
[mono-project.git] / mcs / tools / security / certmgr.cs
blob88a999004e142858ba4c068fefb593769e097f5f
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.Globalization;
13 using System.IO;
14 using System.Net;
15 using System.Net.Sockets;
16 using System.Net.Security;
17 using System.Reflection;
18 using System.Security.Cryptography;
19 using SSCX = System.Security.Cryptography.X509Certificates;
20 using System.Text;
22 using Mono.Security.Authenticode;
23 using Mono.Security.Cryptography;
24 using Mono.Security.X509;
26 [assembly: AssemblyTitle ("Mono Certificate Manager")]
27 [assembly: AssemblyDescription ("Manage X.509 certificates and CRL from stores.")]
29 namespace Mono.Tools {
31 class CertificateManager {
33 static private void Header ()
35 Console.WriteLine (new AssemblyInfo ().ToString ());
38 static private void Help ()
40 Console.WriteLine ("Usage: certmgr [action] [object-type] [options] store [filename]");
41 Console.WriteLine (" or: certmgr -list object-type [options] store");
42 Console.WriteLine (" or: certmgr -del object-type [options] store certhash");
43 Console.WriteLine (" or: certmgr -ssl [options] url");
44 Console.WriteLine (" or: certmgr -put object-type [options] store certfile");
45 Console.WriteLine (" or: certmgr -importKey [options] store pkcs12file");
46 Console.WriteLine ();
47 Console.WriteLine ("actions");
48 Console.WriteLine ("\t-add\t\tAdd a certificate, CRL or CTL to specified store");
49 Console.WriteLine ("\t-del\t\tRemove a certificate, CRL or CTL to specified store");
50 Console.WriteLine ("\t-put\t\tCopy a certificate, CRL or CTL from a store to a file");
51 Console.WriteLine ("\t-list\t\tList certificates, CRL or CTL in the specified store.");
52 Console.WriteLine ("\t-ssl\t\tDownload and add certificates from an SSL session");
53 Console.WriteLine ("\t-importKey\tImport PKCS12 privateKey to keypair store.");
54 Console.WriteLine ("object types");
55 Console.WriteLine ("\t-c\t\tadd/del/put certificates");
56 Console.WriteLine ("\t-crl\t\tadd/del/put certificate revocation lists");
57 Console.WriteLine ("\t-ctl\t\tadd/del/put certificate trust lists [unsupported]");
58 Console.WriteLine ("other options");
59 Console.WriteLine ("\t-m\t\tuse the machine certificate store (default to user)");
60 Console.WriteLine ("\t-v\t\tverbose mode (display status for every steps)");
61 Console.WriteLine ("\t-p [password]\tPassword used to decrypt PKCS12");
62 Console.WriteLine ("\t-pem\t\tPut certificate in Base-64 encoded format (default DER encoded)");
63 Console.WriteLine ("\t-?\t\th[elp]\tDisplay this help message");
64 Console.WriteLine ();
67 static string GetCommand (string arg)
69 if ((arg == null) || (arg.Length < 1))
70 return null;
72 switch (arg [0]) {
73 case '/':
74 return arg.Substring (1).ToUpper ();
75 case '-':
76 if (arg.Length < 2)
77 return null;
78 int n = ((arg [1] == '-') ? 2 : 1);
79 return arg.Substring (n).ToUpper ();
80 default:
81 return arg;
85 enum Action {
86 None,
87 Add,
88 Delete,
89 Put,
90 List,
91 Ssl,
92 ImportKey
95 static Action GetAction (string arg)
97 Action action = Action.None;
98 switch (GetCommand (arg)) {
99 case "ADD":
100 action = Action.Add;
101 break;
102 case "DEL":
103 case "DELETE":
104 action = Action.Delete;
105 break;
106 case "PUT":
107 action = Action.Put;
108 break;
109 case "LST":
110 case "LIST":
111 action = Action.List;
112 break;
113 case "SSL":
114 case "TLS":
115 action = Action.Ssl;
116 break;
117 case "IMPORTKEY":
118 action = Action.ImportKey;
119 break;
121 return action;
124 enum ObjectType {
125 None,
126 Certificate,
127 CRL,
131 static ObjectType GetObjectType (string arg)
133 ObjectType type = ObjectType.None;
134 switch (GetCommand (arg)) {
135 case "C":
136 case "CERT":
137 case "CERTIFICATE":
138 type = ObjectType.Certificate;
139 break;
140 case "CRL":
141 type = ObjectType.CRL;
142 break;
143 case "CTL":
144 type = ObjectType.CTL;
145 break;
147 return type;
150 static X509Store GetStoreFromName (string storeName, bool machine)
152 X509Stores stores = ((machine) ? X509StoreManager.LocalMachine : X509StoreManager.CurrentUser);
153 X509Store store = null;
154 switch (storeName) {
155 case X509Stores.Names.Personal:
156 return stores.Personal;
157 case X509Stores.Names.OtherPeople:
158 return stores.OtherPeople;
159 case X509Stores.Names.IntermediateCA:
160 return stores.IntermediateCA;
161 case "Root": // special case (same as trusted root)
162 case X509Stores.Names.TrustedRoot:
163 return stores.TrustedRoot;
164 case X509Stores.Names.Untrusted:
165 return stores.Untrusted;
167 return store;
170 static byte[] PEM (string type, byte[] data)
172 string pem = Encoding.ASCII.GetString (data);
173 string header = String.Format ("-----BEGIN {0}-----", type);
174 string footer = String.Format ("-----END {0}-----", type);
175 int start = pem.IndexOf (header) + header.Length;
176 int end = pem.IndexOf (footer, start);
177 string base64 = pem.Substring (start, (end - start));
178 return Convert.FromBase64String (base64);
181 static byte[] ToPEM (string type, byte[] data)
183 string header = String.Format ("-----BEGIN {0}-----", type);
184 string footer = String.Format ("-----END {0}-----", type);
186 string encodedString = Convert.ToBase64String (data);
188 StringBuilder sb = new StringBuilder ();
189 int remaining = encodedString.Length;
190 sb.AppendLine (header);
191 for (int i = 0; i <= encodedString.Length; i += 64) {
192 if (remaining >= 64) {
193 sb.AppendLine (encodedString.Substring (i, 64));
194 } else {
195 sb.AppendLine (encodedString.Substring (i, remaining));
197 remaining -= 64;
199 sb.AppendLine (footer);
200 return Encoding.ASCII.GetBytes (sb.ToString ());
203 static X509CertificateCollection LoadCertificates (string filename, string password, bool verbose)
205 X509Certificate x509 = null;
206 X509CertificateCollection coll = new X509CertificateCollection ();
207 switch (Path.GetExtension (filename).ToUpper ()) {
208 case ".P7B":
209 case ".SPC":
210 SoftwarePublisherCertificate spc = SoftwarePublisherCertificate.CreateFromFile (filename);
211 coll.AddRange (spc.Certificates);
212 spc = null;
213 break;
214 case ".CER":
215 case ".CRT":
216 using (FileStream fs = File.OpenRead (filename)) {
217 byte[] data = new byte [fs.Length];
218 fs.Read (data, 0, data.Length);
219 if (data [0] != 0x30) {
220 // maybe it's ASCII PEM base64 encoded ?
221 data = PEM ("CERTIFICATE", data);
223 if (data != null)
224 x509 = new X509Certificate (data);
226 if (x509 != null)
227 coll.Add (x509);
228 break;
229 case ".P12":
230 case ".PFX":
231 PKCS12 p12 = password == null ? PKCS12.LoadFromFile (filename)
232 : PKCS12.LoadFromFile (filename, password);
233 X509CertificateCollection tmp = new X509CertificateCollection (p12.Certificates);
235 for (int i = 0; i != p12.Keys.Count; i++) {
236 X509Certificate cert = p12.Certificates[i];
237 RSACryptoServiceProvider pk = p12.Keys[i] as RSACryptoServiceProvider;
239 if (pk == null || pk.PublicOnly)
240 continue;
242 if (verbose)
243 Console.WriteLine ("Found key for certificate: {0}", cert.SubjectName);
245 tmp[0].RSA = pk;
247 coll.AddRange(tmp);
248 p12 = null;
249 break;
250 default:
251 Console.WriteLine ("Unknown file extension: {0}",
252 Path.GetExtension (filename));
253 break;
255 return coll;
258 static ArrayList LoadCRLs (string filename)
260 X509Crl crl = null;
261 ArrayList list = new ArrayList ();
262 switch (Path.GetExtension (filename).ToUpper ()) {
263 case ".P7B":
264 case ".SPC":
265 SoftwarePublisherCertificate spc = SoftwarePublisherCertificate.CreateFromFile (filename);
266 list.AddRange (spc.Crls);
267 spc = null;
268 break;
269 case ".CRL":
270 using (FileStream fs = File.OpenRead (filename)) {
271 byte[] data = new byte [fs.Length];
272 fs.Read (data, 0, data.Length);
273 crl = new X509Crl (data);
275 list.Add (crl);
276 break;
277 default:
278 Console.WriteLine ("Unknown file extension: {0}",
279 Path.GetExtension (filename));
280 break;
282 return list;
285 static void Add (ObjectType type, X509Store store, string file, string password, bool verbose)
287 switch (type) {
288 case ObjectType.Certificate:
289 X509CertificateCollection coll = LoadCertificates (file, password, verbose);
290 foreach (X509Certificate x509 in coll) {
291 store.Import (x509);
293 Console.WriteLine ("{0} certificate(s) added to store {1}.",
294 coll.Count, store.Name);
295 break;
296 case ObjectType.CRL:
297 ArrayList list = LoadCRLs (file);
298 foreach (X509Crl crl in list) {
299 store.Import (crl);
301 Console.WriteLine ("{0} CRL(s) added to store {1}.",
302 list.Count, store.Name);
303 break;
304 default:
305 throw new NotSupportedException (type.ToString ());
309 static void Delete (ObjectType type, X509Store store, string hash, bool verbose)
311 switch (type) {
312 case ObjectType.Certificate:
313 foreach (X509Certificate x509 in store.Certificates) {
314 if (hash == CryptoConvert.ToHex (x509.Hash)) {
315 store.Remove (x509);
316 Console.WriteLine ("Certificate removed from store.");
317 return;
320 break;
321 case ObjectType.CRL:
322 foreach (X509Crl crl in store.Crls) {
323 if (hash == CryptoConvert.ToHex (crl.Hash)) {
324 store.Remove (crl);
325 Console.WriteLine ("CRL removed from store.");
326 return;
329 break;
330 default:
331 throw new NotSupportedException (type.ToString ());
335 static void Put (ObjectType type, X509Store store, string file, bool machine, bool pem, bool verbose)
337 if (String.IsNullOrEmpty (file)) {
338 Console.Error.WriteLine("error: no filename provided to put the certificate.");
339 Help();
340 return;
343 switch (type) {
344 case ObjectType.Certificate:
345 for(int i = 0; i < store.Certificates.Count; i++) {
346 Console.WriteLine ("==============Certificate # {0} ==========", i + 1);
347 DisplayCertificate (store.Certificates[i], machine, verbose);
349 int selection;
350 Console.Write("Enter cert # from the above list to put-->");
351 if (!int.TryParse(Console.ReadLine(), out selection) || selection > store.Certificates.Count) {
352 Console.Error.WriteLine ("error: invalid selection.");
353 return;
356 SSCX.X509Certificate2 cert = new SSCX.X509Certificate2 (store.Certificates[selection-1].RawData);
357 byte[] data = null;
358 if(pem) {
359 data = ToPEM ("CERTIFICATE", cert.Export (SSCX.X509ContentType.Cert));
360 } else {
361 data = cert.Export (SSCX.X509ContentType.Cert);
364 using (FileStream fs = File.Create (file)) {
365 fs.Write(data, 0, data.Length);
368 Console.WriteLine ("Certificate put to {0}.", file);
369 break;
370 default:
371 throw new NotSupportedException ("Put " + type + " not supported yet");
375 static void DisplayCertificate (X509Certificate x509, bool machine, bool verbose)
377 Console.WriteLine ("{0}X.509 v{1} Certificate", (x509.IsSelfSigned ? "Self-signed " : String.Empty), x509.Version);
378 Console.WriteLine (" Serial Number: {0}", CryptoConvert.ToHex (x509.SerialNumber));
379 Console.WriteLine (" Issuer Name: {0}", x509.IssuerName);
380 Console.WriteLine (" Subject Name: {0}", x509.SubjectName);
381 Console.WriteLine (" Valid From: {0}", x509.ValidFrom);
382 Console.WriteLine (" Valid Until: {0}", x509.ValidUntil);
383 Console.WriteLine (" Unique Hash: {0}", CryptoConvert.ToHex (x509.Hash));
384 if (verbose) {
385 Console.WriteLine (" Key Algorithm: {0}", x509.KeyAlgorithm);
386 Console.WriteLine (" Algorithm Parameters: {0}", (x509.KeyAlgorithmParameters == null) ? "None" :
387 CryptoConvert.ToHex (x509.KeyAlgorithmParameters));
388 Console.WriteLine (" Public Key: {0}", CryptoConvert.ToHex (x509.PublicKey));
389 Console.WriteLine (" Signature Algorithm: {0}", x509.SignatureAlgorithm);
390 Console.WriteLine (" Algorithm Parameters: {0}", (x509.SignatureAlgorithmParameters == null) ? "None" :
391 CryptoConvert.ToHex (x509.SignatureAlgorithmParameters));
392 Console.WriteLine (" Signature: {0}", CryptoConvert.ToHex (x509.Signature));
393 RSACryptoServiceProvider rsaCsp = x509.RSA as RSACryptoServiceProvider;
394 RSAManaged rsaManaged = x509.RSA as RSAManaged;
395 Console.WriteLine (" Private Key: {0}", ((rsaCsp != null && !rsaCsp.PublicOnly)
396 || (rsaManaged != null && !rsaManaged.PublicOnly)));
397 CspParameters cspParams = new CspParameters ();
398 cspParams.KeyContainerName = CryptoConvert.ToHex (x509.Hash);
399 cspParams.Flags = machine ? CspProviderFlags.UseMachineKeyStore : 0;
400 KeyPairPersistence kpp = new KeyPairPersistence (cspParams);
401 Console.WriteLine (" KeyPair Key: {0}", kpp.Load ());
403 Console.WriteLine ();
406 static void DisplayCrl (X509Crl crl, bool machine, bool verbose)
408 Console.WriteLine ("X.509 v{0} CRL", crl.Version);
409 Console.WriteLine (" Issuer Name: {0}", crl.IssuerName);
410 Console.WriteLine (" This Update: {0}", crl.ThisUpdate);
411 Console.WriteLine (" Next Update: {0} {1}", crl.NextUpdate, crl.IsCurrent ? String.Empty : "update overdue!");
412 Console.WriteLine (" Unique Hash: {0}", CryptoConvert.ToHex (crl.Hash));
413 if (verbose) {
414 Console.WriteLine (" Signature Algorithm: {0}", crl.SignatureAlgorithm);
415 Console.WriteLine (" Signature: {0}", CryptoConvert.ToHex (crl.Signature));
416 int n = 0;
417 foreach (X509Crl.X509CrlEntry entry in crl.Entries) {
418 Console.WriteLine (" #{0}: Serial: {1} revoked on {2}",
419 ++n, CryptoConvert.ToHex (entry.SerialNumber), entry.RevocationDate);
424 static void List (ObjectType type, X509Store store, bool machine, string file, bool verbose)
426 switch (type) {
427 case ObjectType.Certificate:
428 foreach (X509Certificate x509 in store.Certificates) {
429 DisplayCertificate (x509, machine, verbose);
431 break;
432 case ObjectType.CRL:
433 foreach (X509Crl crl in store.Crls) {
434 DisplayCrl (crl, machine, verbose);
436 break;
437 default:
438 throw new NotSupportedException (type.ToString ());
442 static X509CertificateCollection GetCertificatesFromSslSession (string url)
444 Uri uri = new Uri (url);
445 IPHostEntry host = Dns.Resolve (uri.Host);
446 IPAddress ip = host.AddressList [0];
447 Socket socket = new Socket (ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
448 socket.Connect (new IPEndPoint (ip, uri.Port));
449 NetworkStream ns = new NetworkStream (socket, false);
451 var certs = new X509CertificateCollection ();
452 var ssl = new SslStream (ns, false, (s, cert, chain, p) => {
453 var elements = chain?.ChainPolicy?.ExtraStore;
454 if (elements != null && elements.Count > 0) {
455 foreach (var element in elements) {
456 certs.Add (new X509Certificate (element.RawData));
458 } else {
459 certs.Add (new X509Certificate (cert.GetRawCertData ()));
461 return true;
463 ssl.AuthenticateAsClient (uri.Host);
465 return certs;
468 static void Ssl (string host, bool machine, bool verbose)
470 if (verbose) {
471 Console.WriteLine ("Importing certificates from '{0}' into the {1} stores.",
472 host, machine ? "machine" : "user");
474 int n=0;
476 X509CertificateCollection coll = GetCertificatesFromSslSession (host);
477 if (coll != null) {
478 X509Store store = null;
479 // start by the end (root) so we can stop adding them anytime afterward
480 for (int i = coll.Count - 1; i >= 0; i--) {
481 X509Certificate x509 = coll [i];
482 bool selfsign = false;
483 bool failed = false;
484 try {
485 selfsign = x509.IsSelfSigned;
487 catch {
488 // sadly it's hard to interpret old certificates with MD2
489 // without manually changing the machine.config file
490 failed = true;
493 if (selfsign) {
494 // this is a root
495 store = GetStoreFromName (X509Stores.Names.TrustedRoot, machine);
496 } else if (i == 0) {
497 // server certificate isn't (generally) an intermediate CA
498 store = GetStoreFromName (X509Stores.Names.OtherPeople, machine);
499 } else {
500 // all other certificates should be intermediate CA
501 store = GetStoreFromName (X509Stores.Names.IntermediateCA, machine);
504 Console.WriteLine ("{0}{1}X.509 Certificate v{2}",
505 Environment.NewLine,
506 selfsign ? "Self-signed " : String.Empty,
507 x509.Version);
508 Console.WriteLine (" Issued from: {0}", x509.IssuerName);
509 Console.WriteLine (" Issued to: {0}", x509.SubjectName);
510 Console.WriteLine (" Valid from: {0}", x509.ValidFrom);
511 Console.WriteLine (" Valid until: {0}", x509.ValidUntil);
513 if (!x509.IsCurrent)
514 Console.WriteLine (" *** WARNING: Certificate isn't current ***");
515 if ((i > 0) && !selfsign) {
516 X509Certificate signer = coll [i-1];
517 bool signed = false;
518 try {
519 if (signer.RSA != null) {
520 signed = x509.VerifySignature (signer.RSA);
521 } else if (signer.DSA != null) {
522 signed = x509.VerifySignature (signer.DSA);
523 } else {
524 Console.WriteLine (" *** WARNING: Couldn't not find who signed this certificate ***");
525 signed = true; // skip next warning
528 if (!signed)
529 Console.WriteLine (" *** WARNING: Certificate signature is INVALID ***");
531 catch {
532 failed = true;
535 if (failed) {
536 Console.WriteLine (" *** ERROR: Couldn't decode certificate properly ***");
537 Console.WriteLine (" *** try 'man certmgr' for additional help or report to bugzilla.novell.com ***");
538 break;
541 if (store.Certificates.Contains (x509)) {
542 Console.WriteLine ("This certificate is already in the {0} store.", store.Name);
543 } else {
544 Console.Write ("Import this certificate into the {0} store ?", store.Name);
545 string answer = Console.ReadLine ().ToUpper ();
546 if ((answer == "YES") || (answer == "Y")) {
547 store.Import (x509);
548 n++;
549 } else {
550 if (verbose) {
551 Console.WriteLine ("Certificate not imported into store {0}.",
552 store.Name);
554 break;
560 Console.WriteLine ();
561 if (n == 0) {
562 Console.WriteLine ("No certificate were added to the stores.");
563 } else {
564 Console.WriteLine ("{0} certificate{1} added to the stores.",
565 n, (n == 1) ? String.Empty : "s");
569 static void ImportKey (ObjectType type, bool machine, string file, string password, bool verbose)
571 switch (type) {
572 case ObjectType.Certificate:
573 X509CertificateCollection coll = LoadCertificates (file, password, verbose);
574 int count = 0;
576 foreach (X509Certificate x509 in coll) {
577 RSACryptoServiceProvider pk = x509.RSA as RSACryptoServiceProvider;
579 if (pk == null || pk.PublicOnly)
580 continue;
582 CspParameters csp = new CspParameters ();
583 csp.KeyContainerName = CryptoConvert.ToHex (x509.Hash);
584 csp.Flags = machine ? CspProviderFlags.UseMachineKeyStore : 0;
585 RSACryptoServiceProvider rsa = new RSACryptoServiceProvider (csp);
586 rsa.ImportParameters (pk.ExportParameters (true));
587 rsa.PersistKeyInCsp = true;
588 count++;
590 Console.WriteLine ("{0} keys(s) imported to KeyPair {1} persister.",
591 count, machine ? "LocalMachine" : "CurrentUser");
592 break;
593 default:
594 throw new NotSupportedException (type.ToString ());
598 [STAThread]
599 static void Main (string[] args)
601 string password = null;
602 bool verbose = false;
603 bool pem = false;
604 bool machine = false;
606 Header ();
607 if (args.Length < 2) {
608 Help ();
609 return;
612 Action action = GetAction (args [0]);
613 ObjectType type = ObjectType.None;
615 int n = 1;
616 if (action != Action.Ssl) {
617 type = GetObjectType (args [n]);
618 if (type != ObjectType.None)
619 n++;
622 for (int i = n; i < args.Length; i++) {
623 switch (GetCommand (args[i])) {
624 case "V":
625 verbose = true;
626 n++;
627 break;
628 case "M":
629 machine = true;
630 n++;
631 break;
632 case "P":
633 password = args[++n];
634 n++;
635 break;
636 case "PEM":
637 pem = true;
638 n++;
639 break;
643 X509Store store = null;
644 string storeName = null;
645 if (action != Action.Ssl) {
646 if ((action == Action.None) || (type == ObjectType.None)) {
647 Help ();
648 return;
650 if (type == ObjectType.CTL) {
651 Console.WriteLine ("CTL are not supported");
652 return;
655 storeName = args [n++];
656 store = GetStoreFromName (storeName, machine);
657 if (store == null) {
658 Console.WriteLine ("Invalid Store: {0}", storeName);
659 Console.WriteLine ("Valid stores are: {0}, {1}, {2}, {3} and {4}",
660 X509Stores.Names.Personal,
661 X509Stores.Names.OtherPeople,
662 X509Stores.Names.IntermediateCA,
663 X509Stores.Names.TrustedRoot,
664 X509Stores.Names.Untrusted);
665 return;
669 string file = (n < args.Length) ? args [n] : null;
671 // now action!
672 try {
673 switch (action) {
674 case Action.Add:
675 Add (type, store, file, password, verbose);
676 break;
677 case Action.Delete:
678 Delete (type, store, file, verbose);
679 break;
680 case Action.Put:
681 Put (type, store, file, machine, pem, verbose);
682 break;
683 case Action.List:
684 List (type, store, machine, file, verbose);
685 break;
686 case Action.Ssl:
687 Ssl (file, machine, verbose);
688 break;
689 case Action.ImportKey:
690 ImportKey (type, machine, file, password, verbose);
691 break;
692 default:
693 throw new NotSupportedException (action.ToString ());
696 catch (UnauthorizedAccessException uae) {
697 Console.WriteLine ("Access to the {0} '{1}' certificate store has been denied.",
698 (machine ? "machine" : "user"), storeName);
699 if (verbose) {
700 Console.WriteLine (uae);