2 // System.Security.Cryptography.X509Certificate2 class
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 #if MONO_SECURITY_ALIAS
33 extern alias MonoSecurity;
34 using MonoSecurity::Mono.Security;
35 using MonoSecurity::Mono.Security.Cryptography;
36 using MX = MonoSecurity::Mono.Security.X509;
39 using Mono.Security.Cryptography;
40 using MX = Mono.Security.X509;
48 namespace System.Security.Cryptography.X509Certificates {
51 public class X509Certificate2 : X509Certificate {
53 // Used in Mono.Security HttpsClientStream
54 public X509Certificate2 (byte[] rawData)
59 private bool _archived;
60 private X509ExtensionCollection _extensions;
61 private string _name = String.Empty;
62 private string _serial;
63 private PublicKey _publicKey;
64 private X500DistinguishedName issuer_name;
65 private X500DistinguishedName subject_name;
66 private Oid signature_algorithm;
68 private MX.X509Certificate _cert;
70 private static string empty_error = Locale.GetText ("Certificate instance is empty.");
74 public X509Certificate2 ()
79 public X509Certificate2 (byte[] rawData)
81 Import (rawData, (string)null, X509KeyStorageFlags.DefaultKeySet);
84 public X509Certificate2 (byte[] rawData, string password)
86 Import (rawData, password, X509KeyStorageFlags.DefaultKeySet);
89 public X509Certificate2 (byte[] rawData, SecureString password)
91 Import (rawData, password, X509KeyStorageFlags.DefaultKeySet);
94 public X509Certificate2 (byte[] rawData, string password, X509KeyStorageFlags keyStorageFlags)
96 Import (rawData, password, keyStorageFlags);
99 public X509Certificate2 (byte[] rawData, SecureString password, X509KeyStorageFlags keyStorageFlags)
101 Import (rawData, password, keyStorageFlags);
104 public X509Certificate2 (string fileName)
106 Import (fileName, String.Empty, X509KeyStorageFlags.DefaultKeySet);
109 public X509Certificate2 (string fileName, string password)
111 Import (fileName, password, X509KeyStorageFlags.DefaultKeySet);
114 public X509Certificate2 (string fileName, SecureString password)
116 Import (fileName, password, X509KeyStorageFlags.DefaultKeySet);
119 public X509Certificate2 (string fileName, string password, X509KeyStorageFlags keyStorageFlags)
121 Import (fileName, password, keyStorageFlags);
124 public X509Certificate2 (string fileName, SecureString password, X509KeyStorageFlags keyStorageFlags)
126 Import (fileName, password, keyStorageFlags);
129 public X509Certificate2 (IntPtr handle) : base (handle)
131 _cert = new MX.X509Certificate (base.GetRawCertData ());
134 public X509Certificate2 (X509Certificate certificate)
137 _cert = new MX.X509Certificate (base.GetRawCertData ());
142 public bool Archived {
145 throw new CryptographicException (empty_error);
150 throw new CryptographicException (empty_error);
155 public X509ExtensionCollection Extensions {
158 throw new CryptographicException (empty_error);
159 if (_extensions == null)
160 _extensions = new X509ExtensionCollection (_cert);
165 public string FriendlyName {
168 throw new CryptographicException (empty_error);
173 throw new CryptographicException (empty_error);
178 // FIXME - Could be more efficient
179 public bool HasPrivateKey {
180 get { return PrivateKey != null; }
183 public X500DistinguishedName IssuerName {
186 throw new CryptographicException (empty_error);
187 if (issuer_name == null)
188 issuer_name = new X500DistinguishedName (_cert.GetIssuerName ().GetBytes ());
193 public DateTime NotAfter {
196 throw new CryptographicException (empty_error);
197 return _cert.ValidUntil.ToLocalTime ();
201 public DateTime NotBefore {
204 throw new CryptographicException (empty_error);
205 return _cert.ValidFrom.ToLocalTime ();
209 public AsymmetricAlgorithm PrivateKey {
212 throw new CryptographicException (empty_error);
214 if (_cert.RSA != null) {
215 RSACryptoServiceProvider rcsp = _cert.RSA as RSACryptoServiceProvider;
217 return rcsp.PublicOnly ? null : rcsp;
219 RSAManaged rsam = _cert.RSA as RSAManaged;
221 return rsam.PublicOnly ? null : rsam;
223 _cert.RSA.ExportParameters (true);
225 } else if (_cert.DSA != null) {
226 DSACryptoServiceProvider dcsp = _cert.DSA as DSACryptoServiceProvider;
228 return dcsp.PublicOnly ? null : dcsp;
230 _cert.DSA.ExportParameters (true);
240 throw new CryptographicException (empty_error);
242 // allow NULL so we can "forget" the key associated to the certificate
243 // e.g. in case we want to export it in another format (see bug #396620)
247 } else if (value is RSA)
248 _cert.RSA = (RSA) value;
249 else if (value is DSA)
250 _cert.DSA = (DSA) value;
252 throw new NotSupportedException ();
256 public PublicKey PublicKey {
259 throw new CryptographicException (empty_error);
261 if (_publicKey == null) {
263 _publicKey = new PublicKey (_cert);
265 catch (Exception e) {
266 string msg = Locale.GetText ("Unable to decode public key.");
267 throw new CryptographicException (msg, e);
274 public byte[] RawData {
277 throw new CryptographicException (empty_error);
279 return base.GetRawCertData ();
283 public string SerialNumber {
286 throw new CryptographicException (empty_error);
288 if (_serial == null) {
289 StringBuilder sb = new StringBuilder ();
290 byte[] serial = _cert.SerialNumber;
291 for (int i=serial.Length - 1; i >= 0; i--)
292 sb.Append (serial [i].ToString ("X2"));
293 _serial = sb.ToString ();
299 public Oid SignatureAlgorithm {
302 throw new CryptographicException (empty_error);
304 if (signature_algorithm == null)
305 signature_algorithm = new Oid (_cert.SignatureAlgorithm);
306 return signature_algorithm;
310 public X500DistinguishedName SubjectName {
313 throw new CryptographicException (empty_error);
315 if (subject_name == null)
316 subject_name = new X500DistinguishedName (_cert.GetSubjectName ().GetBytes ());
321 public string Thumbprint {
322 get { return base.GetCertHashString (); }
328 throw new CryptographicException (empty_error);
329 return _cert.Version;
335 [MonoTODO ("always return String.Empty for UpnName, DnsFromAlternativeName and UrlName")]
336 public string GetNameInfo (X509NameType nameType, bool forIssuer)
339 case X509NameType.SimpleName:
341 throw new CryptographicException (empty_error);
342 // return CN= or, if missing, the first part of the DN
343 ASN1 sn = forIssuer ? _cert.GetIssuerName () : _cert.GetSubjectName ();
344 ASN1 dn = Find (commonName, sn);
346 return GetValueAsString (dn);
349 ASN1 last_entry = sn [sn.Count - 1];
350 if (last_entry.Count == 0)
352 return GetValueAsString (last_entry [0]);
353 case X509NameType.EmailName:
354 // return the E= part of the DN (if present)
355 ASN1 e = Find (email, forIssuer ? _cert.GetIssuerName () : _cert.GetSubjectName ());
357 return GetValueAsString (e);
359 case X509NameType.UpnName:
360 // FIXME - must find/create test case
362 case X509NameType.DnsName:
363 // return the CN= part of the DN (if present)
364 ASN1 cn = Find (commonName, forIssuer ? _cert.GetIssuerName () : _cert.GetSubjectName ());
366 return GetValueAsString (cn);
368 case X509NameType.DnsFromAlternativeName:
369 // FIXME - must find/create test case
371 case X509NameType.UrlName:
372 // FIXME - must find/create test case
375 throw new ArgumentException ("nameType");
379 static byte[] commonName = { 0x55, 0x04, 0x03 };
380 static byte[] email = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 };
382 private ASN1 Find (byte[] oid, ASN1 dn)
388 for (int i = 0; i < dn.Count; i++) {
390 for (int j = 0; j < set.Count; j++) {
395 ASN1 poid = pair [0];
399 if (poid.CompareValue (oid))
406 private string GetValueAsString (ASN1 pair)
411 ASN1 value = pair [1];
412 if ((value.Value == null) || (value.Length == 0))
415 if (value.Tag == 0x1E) {
417 StringBuilder sb = new StringBuilder ();
418 for (int j = 1; j < value.Value.Length; j += 2)
419 sb.Append ((char)value.Value [j]);
420 return sb.ToString ();
422 return Encoding.UTF8.GetString (value.Value);
426 private MX.X509Certificate ImportPkcs12 (byte[] rawData, string password)
428 MX.PKCS12 pfx = null;
429 if (string.IsNullOrEmpty (password)) {
431 // Support both unencrypted PKCS#12..
432 pfx = new MX.PKCS12 (rawData, (string)null);
434 // ..and PKCS#12 encrypted with an empty password
435 pfx = new MX.PKCS12 (rawData, string.Empty);
438 pfx = new MX.PKCS12 (rawData, password);
441 if (pfx.Certificates.Count == 0) {
442 // no certificate was found
444 } else if (pfx.Keys.Count == 0) {
445 // no key were found - pick the first certificate
446 return pfx.Certificates [0];
448 // find the certificate that match the first key
449 MX.X509Certificate cert = null;
450 var keypair = (pfx.Keys [0] as AsymmetricAlgorithm);
451 string pubkey = keypair.ToXmlString (false);
452 foreach (var c in pfx.Certificates) {
453 if (((c.RSA != null) && (pubkey == c.RSA.ToXmlString (false))) ||
454 ((c.DSA != null) && (pubkey == c.DSA.ToXmlString (false)))) {
460 cert = pfx.Certificates [0]; // no match, pick first certificate without keys
462 cert.RSA = (keypair as RSA);
463 cert.DSA = (keypair as DSA);
469 public override void Import (byte[] rawData)
471 Import (rawData, (string)null, X509KeyStorageFlags.DefaultKeySet);
474 [MonoTODO ("missing KeyStorageFlags support")]
475 public override void Import (byte[] rawData, string password, X509KeyStorageFlags keyStorageFlags)
477 MX.X509Certificate cert = null;
478 if (password == null) {
480 cert = new MX.X509Certificate (rawData);
482 catch (Exception e) {
484 cert = ImportPkcs12 (rawData, null);
487 string msg = Locale.GetText ("Unable to decode certificate.");
488 // inner exception is the original (not second) exception
489 throw new CryptographicException (msg, e);
495 cert = ImportPkcs12 (rawData, password);
498 // it's possible to supply a (unrequired/unusued) password
500 cert = new MX.X509Certificate (rawData);
503 // we do not have to fully re-decode the certificate since X509Certificate does not deal with keys
505 base.Import (cert.RawData, (string) null, keyStorageFlags);
506 _cert = cert; // becuase base call will call Reset!
510 [MonoTODO ("SecureString is incomplete")]
511 public override void Import (byte[] rawData, SecureString password, X509KeyStorageFlags keyStorageFlags)
513 Import (rawData, (string) null, keyStorageFlags);
516 public override void Import (string fileName)
518 byte[] rawData = File.ReadAllBytes (fileName);
519 Import (rawData, (string)null, X509KeyStorageFlags.DefaultKeySet);
522 [MonoTODO ("missing KeyStorageFlags support")]
523 public override void Import (string fileName, string password, X509KeyStorageFlags keyStorageFlags)
525 byte[] rawData = File.ReadAllBytes (fileName);
526 Import (rawData, password, keyStorageFlags);
529 [MonoTODO ("SecureString is incomplete")]
530 public override void Import (string fileName, SecureString password, X509KeyStorageFlags keyStorageFlags)
532 byte[] rawData = File.ReadAllBytes (fileName);
533 Import (rawData, (string)null, keyStorageFlags);
536 [MonoTODO ("X509ContentType.SerializedCert is not supported")]
537 public override byte[] Export (X509ContentType contentType, string password)
540 throw new CryptographicException (empty_error);
542 switch (contentType) {
543 case X509ContentType.Cert:
544 return _cert.RawData;
545 case X509ContentType.Pfx: // this includes Pkcs12
546 return ExportPkcs12 (password);
547 case X509ContentType.SerializedCert:
549 throw new NotSupportedException ();
551 string msg = Locale.GetText ("This certificate format '{0}' cannot be exported.", contentType);
552 throw new CryptographicException (msg);
556 byte[] ExportPkcs12 (string password)
558 var pfx = new MX.PKCS12 ();
560 if (password != null)
561 pfx.Password = password;
562 pfx.AddCertificate (_cert);
563 var privateKey = PrivateKey;
564 if (privateKey != null)
565 pfx.AddPkcs8ShroudedKeyBag (privateKey);
566 return pfx.GetBytes ();
572 public override void Reset ()
577 _name = String.Empty;
582 signature_algorithm = null;
586 public override string ToString ()
589 return "System.Security.Cryptography.X509Certificates.X509Certificate2";
591 return base.ToString (true);
594 public override string ToString (bool verbose)
597 return "System.Security.Cryptography.X509Certificates.X509Certificate2";
599 // the non-verbose X509Certificate2 == verbose X509Certificate
601 return base.ToString (true);
603 string nl = Environment.NewLine;
604 StringBuilder sb = new StringBuilder ();
605 sb.AppendFormat ("[Version]{0} V{1}{0}{0}", nl, Version);
606 sb.AppendFormat ("[Subject]{0} {1}{0}{0}", nl, Subject);
607 sb.AppendFormat ("[Issuer]{0} {1}{0}{0}", nl, Issuer);
608 sb.AppendFormat ("[Serial Number]{0} {1}{0}{0}", nl, SerialNumber);
609 sb.AppendFormat ("[Not Before]{0} {1}{0}{0}", nl, NotBefore);
610 sb.AppendFormat ("[Not After]{0} {1}{0}{0}", nl, NotAfter);
611 sb.AppendFormat ("[Thumbprint]{0} {1}{0}{0}", nl, Thumbprint);
612 sb.AppendFormat ("[Signature Algorithm]{0} {1}({2}){0}{0}", nl, SignatureAlgorithm.FriendlyName,
613 SignatureAlgorithm.Value);
615 AsymmetricAlgorithm key = PublicKey.Key;
616 sb.AppendFormat ("[Public Key]{0} Algorithm: ", nl);
622 sb.Append (key.ToString ());
623 sb.AppendFormat ("{0} Length: {1}{0} Key Blob: ", nl, key.KeySize);
624 AppendBuffer (sb, PublicKey.EncodedKeyValue.RawData);
625 sb.AppendFormat ("{0} Parameters: ", nl);
626 AppendBuffer (sb, PublicKey.EncodedParameters.RawData);
629 return sb.ToString ();
632 private static void AppendBuffer (StringBuilder sb, byte[] buffer)
636 for (int i=0; i < buffer.Length; i++) {
637 sb.Append (buffer [i].ToString ("x2"));
638 if (i < buffer.Length - 1)
643 [MonoTODO ("by default this depends on the incomplete X509Chain")]
644 public bool Verify ()
647 throw new CryptographicException (empty_error);
649 X509Chain chain = X509Chain.Create ();
650 if (!chain.Build (this))
652 // TODO - check chain and other stuff ???
658 private static byte[] signedData = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02 };
660 [MonoTODO ("Detection limited to Cert, Pfx, Pkcs12, Pkcs7 and Unknown")]
661 public static X509ContentType GetCertContentType (byte[] rawData)
663 if ((rawData == null) || (rawData.Length == 0))
664 throw new ArgumentException ("rawData");
666 X509ContentType type = X509ContentType.Unknown;
668 ASN1 data = new ASN1 (rawData);
669 if (data.Tag != 0x30) {
670 string msg = Locale.GetText ("Unable to decode certificate.");
671 throw new CryptographicException (msg);
677 if (data.Count == 3) {
678 switch (data [0].Tag) {
680 // SEQUENCE / SEQUENCE / BITSTRING
681 if ((data [1].Tag == 0x30) && (data [2].Tag == 0x03))
682 type = X509ContentType.Cert;
685 // INTEGER / SEQUENCE / SEQUENCE
686 if ((data [1].Tag == 0x30) && (data [2].Tag == 0x30))
687 type = X509ContentType.Pkcs12;
688 // note: Pfx == Pkcs12
692 // check for PKCS#7 (count unknown but greater than 0)
693 // SEQUENCE / OID (signedData)
694 if ((data [0].Tag == 0x06) && data [0].CompareValue (signedData))
695 type = X509ContentType.Pkcs7;
697 catch (Exception e) {
698 string msg = Locale.GetText ("Unable to decode certificate.");
699 throw new CryptographicException (msg, e);
705 [MonoTODO ("Detection limited to Cert, Pfx, Pkcs12 and Unknown")]
706 public static X509ContentType GetCertContentType (string fileName)
708 if (fileName == null)
709 throw new ArgumentNullException ("fileName");
710 if (fileName.Length == 0)
711 throw new ArgumentException ("fileName");
713 byte[] data = File.ReadAllBytes (fileName);
714 return GetCertContentType (data);
717 // internal stuff because X509Certificate2 isn't complete enough
718 // (maybe X509Certificate3 will be better?)
720 internal MX.X509Certificate MonoCertificate {
721 get { return _cert; }
725 // HACK - this ensure the type X509Certificate2 and PrivateKey property exists in the build before
726 // Mono.Security.dll is built. This is required to get working client certificate in SSL/TLS
727 public AsymmetricAlgorithm PrivateKey {