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
;
12 using System
.Globalization
;
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
;
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");
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");
67 static string GetCommand (string arg
)
69 if ((arg
== null) || (arg
.Length
< 1))
74 return arg
.Substring (1).ToUpper ();
78 int n
= ((arg
[1] == '-') ? 2 : 1);
79 return arg
.Substring (n
).ToUpper ();
95 static Action
GetAction (string arg
)
97 Action action
= Action
.None
;
98 switch (GetCommand (arg
)) {
104 action
= Action
.Delete
;
111 action
= Action
.List
;
118 action
= Action
.ImportKey
;
131 static ObjectType
GetObjectType (string arg
)
133 ObjectType type
= ObjectType
.None
;
134 switch (GetCommand (arg
)) {
138 type
= ObjectType
.Certificate
;
141 type
= ObjectType
.CRL
;
144 type
= ObjectType
.CTL
;
150 static X509Store
GetStoreFromName (string storeName
, bool machine
)
152 X509Stores stores
= ((machine
) ? X509StoreManager
.LocalMachine
: X509StoreManager
.CurrentUser
);
153 X509Store store
= null;
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
;
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));
195 sb
.AppendLine (encodedString
.Substring (i
, remaining
));
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 ()) {
210 SoftwarePublisherCertificate spc
= SoftwarePublisherCertificate
.CreateFromFile (filename
);
211 coll
.AddRange (spc
.Certificates
);
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
);
224 x509
= new X509Certificate (data
);
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
)
243 Console
.WriteLine ("Found key for certificate: {0}", cert
.SubjectName
);
251 Console
.WriteLine ("Unknown file extension: {0}",
252 Path
.GetExtension (filename
));
258 static ArrayList
LoadCRLs (string filename
)
261 ArrayList list
= new ArrayList ();
262 switch (Path
.GetExtension (filename
).ToUpper ()) {
265 SoftwarePublisherCertificate spc
= SoftwarePublisherCertificate
.CreateFromFile (filename
);
266 list
.AddRange (spc
.Crls
);
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
);
278 Console
.WriteLine ("Unknown file extension: {0}",
279 Path
.GetExtension (filename
));
285 static void Add (ObjectType type
, X509Store store
, string file
, string password
, bool verbose
)
288 case ObjectType
.Certificate
:
289 X509CertificateCollection coll
= LoadCertificates (file
, password
, verbose
);
290 foreach (X509Certificate x509
in coll
) {
293 Console
.WriteLine ("{0} certificate(s) added to store {1}.",
294 coll
.Count
, store
.Name
);
297 ArrayList list
= LoadCRLs (file
);
298 foreach (X509Crl crl
in list
) {
301 Console
.WriteLine ("{0} CRL(s) added to store {1}.",
302 list
.Count
, store
.Name
);
305 throw new NotSupportedException (type
.ToString ());
309 static void Delete (ObjectType type
, X509Store store
, string hash
, bool verbose
)
312 case ObjectType
.Certificate
:
313 foreach (X509Certificate x509
in store
.Certificates
) {
314 if (hash
== CryptoConvert
.ToHex (x509
.Hash
)) {
316 Console
.WriteLine ("Certificate removed from store.");
322 foreach (X509Crl crl
in store
.Crls
) {
323 if (hash
== CryptoConvert
.ToHex (crl
.Hash
)) {
325 Console
.WriteLine ("CRL removed from store.");
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.");
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
);
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.");
356 SSCX
.X509Certificate2 cert
= new SSCX
.X509Certificate2 (store
.Certificates
[selection
-1].RawData
);
359 data
= ToPEM ("CERTIFICATE", cert
.Export (SSCX
.X509ContentType
.Cert
));
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
);
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
));
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
));
414 Console
.WriteLine (" Signature Algorithm: {0}", crl
.SignatureAlgorithm
);
415 Console
.WriteLine (" Signature: {0}", CryptoConvert
.ToHex (crl
.Signature
));
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
)
427 case ObjectType
.Certificate
:
428 foreach (X509Certificate x509
in store
.Certificates
) {
429 DisplayCertificate (x509
, machine
, verbose
);
433 foreach (X509Crl crl
in store
.Crls
) {
434 DisplayCrl (crl
, machine
, verbose
);
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
));
459 certs
.Add (new X509Certificate (cert
.GetRawCertData ()));
463 ssl
.AuthenticateAsClient (uri
.Host
);
468 static void Ssl (string host
, bool machine
, bool verbose
)
471 Console
.WriteLine ("Importing certificates from '{0}' into the {1} stores.",
472 host
, machine
? "machine" : "user");
476 X509CertificateCollection coll
= GetCertificatesFromSslSession (host
);
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;
485 selfsign
= x509
.IsSelfSigned
;
488 // sadly it's hard to interpret old certificates with MD2
489 // without manually changing the machine.config file
495 store
= GetStoreFromName (X509Stores
.Names
.TrustedRoot
, machine
);
497 // server certificate isn't (generally) an intermediate CA
498 store
= GetStoreFromName (X509Stores
.Names
.OtherPeople
, machine
);
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}",
506 selfsign
? "Self-signed " : String
.Empty
,
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
);
514 Console
.WriteLine (" *** WARNING: Certificate isn't current ***");
515 if ((i
> 0) && !selfsign
) {
516 X509Certificate signer
= coll
[i
-1];
519 if (signer
.RSA
!= null) {
520 signed
= x509
.VerifySignature (signer
.RSA
);
521 } else if (signer
.DSA
!= null) {
522 signed
= x509
.VerifySignature (signer
.DSA
);
524 Console
.WriteLine (" *** WARNING: Couldn't not find who signed this certificate ***");
525 signed
= true; // skip next warning
529 Console
.WriteLine (" *** WARNING: Certificate signature is INVALID ***");
536 Console
.WriteLine (" *** ERROR: Couldn't decode certificate properly ***");
537 Console
.WriteLine (" *** try 'man certmgr' for additional help or report to bugzilla.novell.com ***");
541 if (store
.Certificates
.Contains (x509
)) {
542 Console
.WriteLine ("This certificate is already in the {0} store.", store
.Name
);
544 Console
.Write ("Import this certificate into the {0} store ?", store
.Name
);
545 string answer
= Console
.ReadLine ().ToUpper ();
546 if ((answer
== "YES") || (answer
== "Y")) {
551 Console
.WriteLine ("Certificate not imported into store {0}.",
560 Console
.WriteLine ();
562 Console
.WriteLine ("No certificate were added to the stores.");
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
)
572 case ObjectType
.Certificate
:
573 X509CertificateCollection coll
= LoadCertificates (file
, password
, verbose
);
576 foreach (X509Certificate x509
in coll
) {
577 RSACryptoServiceProvider pk
= x509
.RSA
as RSACryptoServiceProvider
;
579 if (pk
== null || pk
.PublicOnly
)
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;
590 Console
.WriteLine ("{0} keys(s) imported to KeyPair {1} persister.",
591 count
, machine
? "LocalMachine" : "CurrentUser");
594 throw new NotSupportedException (type
.ToString ());
599 static void Main (string[] args
)
601 string password
= null;
602 bool verbose
= false;
604 bool machine
= false;
607 if (args
.Length
< 2) {
612 Action action
= GetAction (args
[0]);
613 ObjectType type
= ObjectType
.None
;
616 if (action
!= Action
.Ssl
) {
617 type
= GetObjectType (args
[n
]);
618 if (type
!= ObjectType
.None
)
622 for (int i
= n
; i
< args
.Length
; i
++) {
623 switch (GetCommand (args
[i
])) {
633 password
= args
[++n
];
643 X509Store store
= null;
644 string storeName
= null;
645 if (action
!= Action
.Ssl
) {
646 if ((action
== Action
.None
) || (type
== ObjectType
.None
)) {
650 if (type
== ObjectType
.CTL
) {
651 Console
.WriteLine ("CTL are not supported");
655 storeName
= args
[n
++];
656 store
= GetStoreFromName (storeName
, machine
);
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
);
669 string file
= (n
< args
.Length
) ? args
[n
] : null;
675 Add (type
, store
, file
, password
, verbose
);
678 Delete (type
, store
, file
, verbose
);
681 Put (type
, store
, file
, machine
, pem
, verbose
);
684 List (type
, store
, machine
, file
, verbose
);
687 Ssl (file
, machine
, verbose
);
689 case Action
.ImportKey
:
690 ImportKey (type
, machine
, file
, password
, verbose
);
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
);
700 Console
.WriteLine (uae
);