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
.RSA
);
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.ximian.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
);