Implementation of the 2.0 session state model
[mono.git] / mcs / class / System / System.Security.Cryptography.X509Certificates / X509Certificate2.cs
1 //
2 // System.Security.Cryptography.X509Certificate2 class
3 //
4 // Author:
5 //      Sebastien Pouliot  <sebastien@ximian.com>
6 //
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
9 //
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:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
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.
28 //
29
30 #if NET_2_0 && SECURITY_DEP
31
32 using System.IO;
33 using System.Text;
34 using Mono.Security;
35 using Mono.Security.Cryptography;
36 using MX = Mono.Security.X509;
37
38 namespace System.Security.Cryptography.X509Certificates {
39
40         public class X509Certificate2 : X509Certificate {
41
42                 private bool _archived;
43                 private X509ExtensionCollection _extensions;
44                 private string _name = String.Empty;
45                 private string _serial;
46                 private PublicKey _publicKey;
47                 private X500DistinguishedName issuer_name;
48                 private X500DistinguishedName subject_name;
49                 private Oid signature_algorithm;
50
51                 private MX.X509Certificate _cert;
52
53                 private static string empty_error = Locale.GetText ("Certificate instance is empty.");
54
55                 // constructors
56
57                 public X509Certificate2 ()
58                 {
59                         _cert = null;
60                 }
61
62                 public X509Certificate2 (byte[] rawData)
63                 {
64                         Import (rawData, (string)null, X509KeyStorageFlags.DefaultKeySet);
65                 }
66
67                 public X509Certificate2 (byte[] rawData, string password)
68                 {
69                         Import (rawData, password, X509KeyStorageFlags.DefaultKeySet);
70                 }
71
72                 public X509Certificate2 (byte[] rawData, SecureString password)
73                 {
74                         Import (rawData, password, X509KeyStorageFlags.DefaultKeySet);
75                 }
76
77                 public X509Certificate2 (byte[] rawData, string password, X509KeyStorageFlags keyStorageFlags)
78                 {
79                         Import (rawData, password, keyStorageFlags);
80                 }
81
82                 public X509Certificate2 (byte[] rawData, SecureString password, X509KeyStorageFlags keyStorageFlags)
83                 {
84                         Import (rawData, password, keyStorageFlags);
85                 }
86
87                 public X509Certificate2 (string fileName) : base (fileName) 
88                 {
89                         Import (fileName, (string)null, X509KeyStorageFlags.DefaultKeySet);
90                 }
91
92                 public X509Certificate2 (string fileName, string password)
93                 {
94                         Import (fileName, password, X509KeyStorageFlags.DefaultKeySet);
95                 }
96
97                 public X509Certificate2 (string fileName, SecureString password)
98                 {
99                         Import (fileName, password, X509KeyStorageFlags.DefaultKeySet);
100                 }
101
102                 public X509Certificate2 (string fileName, string password, X509KeyStorageFlags keyStorageFlags)
103                 {
104                         Import (fileName, password, keyStorageFlags);
105                 }
106
107                 public X509Certificate2 (string fileName, SecureString password, X509KeyStorageFlags keyStorageFlags)
108                 {
109                         Import (fileName, password, keyStorageFlags);
110                 }
111
112                 public X509Certificate2 (IntPtr handle) : base (handle) 
113                 {
114                         _cert = new MX.X509Certificate (base.GetRawCertData ());
115                 }
116
117                 public X509Certificate2 (X509Certificate certificate) 
118                         : base (certificate)
119                 {
120                         _cert = new MX.X509Certificate (base.GetRawCertData ());
121                 }
122
123                 // properties
124
125                 public bool Archived {
126                         get {
127                                 if (_cert == null)
128                                         throw new CryptographicException (empty_error);
129                                 return _archived;
130                         }
131                         set {
132                                 if (_cert == null)
133                                         throw new CryptographicException (empty_error);
134                                 _archived = value;
135                         }
136                 }
137
138                 public X509ExtensionCollection Extensions {
139                         get {
140                                 if (_cert == null)
141                                         throw new CryptographicException (empty_error);
142                                 if (_extensions == null)
143                                         _extensions = new X509ExtensionCollection (_cert);
144                                 return _extensions;
145                         }
146                 }
147
148                 public string FriendlyName {
149                         get {
150                                 if (_cert == null)
151                                         throw new CryptographicException (empty_error);
152                                 return _name;
153                         }
154                         set {
155                                 if (_cert == null)
156                                         throw new CryptographicException (empty_error);
157                                 _name = value;
158                         }
159                 }
160
161                 // FIXME - Could be more efficient
162                 public bool HasPrivateKey {
163                         get { return PrivateKey != null; }
164                 }
165
166                 public X500DistinguishedName IssuerName {
167                         get {
168                                 if (_cert == null)
169                                         throw new CryptographicException (empty_error);
170                                 if (issuer_name == null)
171                                         issuer_name = new X500DistinguishedName (_cert.GetIssuerName ().GetBytes ());
172                                 return issuer_name;
173                         }
174                 } 
175
176                 public DateTime NotAfter {
177                         get {
178                                 if (_cert == null)
179                                         throw new CryptographicException (empty_error);
180                                 return _cert.ValidUntil;
181                         }
182                 }
183
184                 public DateTime NotBefore {
185                         get {
186                                 if (_cert == null)
187                                         throw new CryptographicException (empty_error);
188                                 return _cert.ValidFrom;
189                         }
190                 }
191
192                 public AsymmetricAlgorithm PrivateKey {
193                         get {
194                                 if (_cert == null)
195                                         throw new CryptographicException (empty_error);
196                                 try {
197                                         if (_cert.RSA != null) {
198                                                 RSACryptoServiceProvider rcsp = _cert.RSA as RSACryptoServiceProvider;
199                                                 if (rcsp != null)
200                                                         return rcsp.PublicOnly ? null : rcsp;
201                                                 RSAManaged rsam = _cert.RSA as RSAManaged;
202                                                 if (rsam != null)
203                                                         return rsam.PublicOnly ? null : rsam;
204
205                                                 _cert.RSA.ExportParameters (true);
206                                                 return _cert.RSA;
207                                         } else if (_cert.DSA != null) {
208                                                 DSACryptoServiceProvider dcsp = _cert.DSA as DSACryptoServiceProvider;
209                                                 if (dcsp != null)
210                                                         return dcsp.PublicOnly ? null : dcsp;
211         
212                                                 _cert.DSA.ExportParameters (true);
213                                                 return _cert.DSA;
214                                         }
215                                 }
216                                 catch {
217                                 }
218                                 return null;
219                         }
220                         set {
221                                 if (_cert == null)
222                                         throw new CryptographicException (empty_error);
223
224                                 if (value is RSA)
225                                         _cert.RSA = (RSA) value;
226                                 else if (value is DSA)
227                                         _cert.DSA = (DSA) value;
228                                 else
229                                         throw new NotSupportedException ();
230                         }
231                 } 
232
233                 public PublicKey PublicKey {
234                         get { 
235                                 if (_cert == null)
236                                         throw new CryptographicException (empty_error);
237
238                                 if (_publicKey == null) {
239                                         try {
240                                                 _publicKey = new PublicKey (_cert);
241                                         }
242                                         catch (Exception e) {
243                                                 string msg = Locale.GetText ("Unable to decode public key.");
244                                                 throw new CryptographicException (msg, e);
245                                         }
246                                 }
247                                 return _publicKey;
248                         }
249                 } 
250
251                 public byte[] RawData {
252                         get {
253                                 if (_cert == null)
254                                         throw new CryptographicException (empty_error);
255
256                                 return base.GetRawCertData ();
257                         }
258                 } 
259
260                 public string SerialNumber {
261                         get { 
262                                 if (_cert == null)
263                                         throw new CryptographicException (empty_error);
264
265                                 if (_serial == null) {
266                                         StringBuilder sb = new StringBuilder ();
267                                         byte[] serial = _cert.SerialNumber;
268                                         for (int i=serial.Length - 1; i >= 0; i--)
269                                                 sb.Append (serial [i].ToString ("X2"));
270                                         _serial = sb.ToString ();
271                                 }
272                                 return _serial; 
273                         }
274                 } 
275
276                 public Oid SignatureAlgorithm {
277                         get {
278                                 if (_cert == null)
279                                         throw new CryptographicException (empty_error);
280
281                                 if (signature_algorithm == null)
282                                         signature_algorithm = new Oid (_cert.SignatureAlgorithm);
283                                 return signature_algorithm;
284                         }
285                 } 
286
287                 public X500DistinguishedName SubjectName {
288                         get {
289                                 if (_cert == null)
290                                         throw new CryptographicException (empty_error);
291
292                                 if (subject_name == null)
293                                         subject_name = new X500DistinguishedName (_cert.GetSubjectName ().GetBytes ());
294                                 return subject_name;
295                         }
296                 } 
297
298                 public string Thumbprint {
299                         get { return base.GetCertHashString (); }
300                 } 
301
302                 public int Version {
303                         get {
304                                 if (_cert == null)
305                                         throw new CryptographicException (empty_error);
306                                 return _cert.Version;
307                         }
308                 }
309
310                 // methods
311
312                 [MonoTODO ("always return String.Empty for UpnName, DnsFromAlternativeName and UrlName")]
313                 public string GetNameInfo (X509NameType nameType, bool forIssuer) 
314                 {
315                         switch (nameType) {
316                         case X509NameType.SimpleName:
317                                 if (_cert == null)
318                                         throw new CryptographicException (empty_error);
319                                 // return CN= or, if missing, the first part of the DN
320                                 ASN1 sn = forIssuer ? _cert.GetIssuerName () : _cert.GetSubjectName ();
321                                 ASN1 dn = Find (commonName, sn);
322                                 if (dn != null)
323                                         return GetValueAsString (dn);
324                                 if (sn.Count == 0)
325                                         return String.Empty;
326                                 ASN1 last_entry = sn [sn.Count - 1];
327                                 if (last_entry.Count == 0)
328                                         return String.Empty;
329                                 return GetValueAsString (last_entry [0]);
330                         case X509NameType.EmailName:
331                                 // return the E= part of the DN (if present)
332                                 ASN1 e = Find (email, forIssuer ? _cert.GetIssuerName () : _cert.GetSubjectName ());
333                                 if (e != null)
334                                         return GetValueAsString (e);
335                                 return String.Empty;
336                         case X509NameType.UpnName:
337                                 // FIXME - must find/create test case
338                                 return String.Empty;
339                         case X509NameType.DnsName:
340                                 // return the CN= part of the DN (if present)
341                                 ASN1 cn = Find (commonName, forIssuer ? _cert.GetIssuerName () : _cert.GetSubjectName ());
342                                 if (cn != null)
343                                         return GetValueAsString (cn);
344                                 return String.Empty;
345                         case X509NameType.DnsFromAlternativeName:
346                                 // FIXME - must find/create test case
347                                 return String.Empty;
348                         case X509NameType.UrlName:
349                                 // FIXME - must find/create test case
350                                 return String.Empty;
351                         default:
352                                 throw new ArgumentException ("nameType");
353                         }
354                 }
355
356                 static byte[] commonName = { 0x55, 0x04, 0x03 };
357                 static byte[] email = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 };
358
359                 private ASN1 Find (byte[] oid, ASN1 dn)
360                 {
361                         if (dn.Count == 0)
362                                 return null;
363
364                         // process SET
365                         for (int i = 0; i < dn.Count; i++) {
366                                 ASN1 set = dn [i];
367                                 for (int j = 0; j < set.Count; j++) {
368                                         ASN1 pair = set [j];
369                                         if (pair.Count != 2)
370                                                 continue;
371
372                                         ASN1 poid = pair [0];
373                                         if (poid == null)
374                                                 continue;
375
376                                         if (poid.CompareValue (oid))
377                                                 return pair;
378                                 }
379                         }
380                         return null;
381                 }
382
383                 private string GetValueAsString (ASN1 pair)
384                 {
385                         if (pair.Count != 2)
386                                 return String.Empty;
387
388                         ASN1 value = pair [1];
389                         if ((value.Value == null) || (value.Length == 0))
390                                 return String.Empty;
391
392                         if (value.Tag == 0x1E) {
393                                 // BMPSTRING
394                                 StringBuilder sb = new StringBuilder ();
395                                 for (int j = 1; j < value.Value.Length; j += 2)
396                                         sb.Append ((char)value.Value [j]);
397                                 return sb.ToString ();
398                         } else {
399                                 return Encoding.UTF8.GetString (value.Value);
400                         }
401                 }
402
403                 private void ImportPkcs12 (byte[] rawData, string password)
404                 {
405                         MX.PKCS12 pfx = (password == null) ? new MX.PKCS12 (rawData) : new MX.PKCS12 (rawData, password);
406                         if (pfx.Certificates.Count > 0) {
407                                 _cert = pfx.Certificates [0];
408                         } else {
409                                 _cert = null;
410                         }
411                         if (pfx.Keys.Count > 0) {
412                                 _cert.RSA = (pfx.Keys [0] as RSA);
413                                 _cert.DSA = (pfx.Keys [0] as DSA);
414                         }
415                 }
416
417                 public override void Import (byte[] rawData) 
418                 {
419                         Import (rawData, (string)null, X509KeyStorageFlags.DefaultKeySet);
420                 }
421
422                 [MonoTODO ("missing KeyStorageFlags support")]
423                 public override void Import (byte[] rawData, string password, X509KeyStorageFlags keyStorageFlags)
424                 {
425                         base.Import (rawData, password, keyStorageFlags);
426                         if (password == null) {
427                                 try {
428                                         _cert = new Mono.Security.X509.X509Certificate (rawData);
429                                 }
430                                 catch (Exception e) {
431                                         try {
432                                                 ImportPkcs12 (rawData, null);
433                                         }
434                                         catch {
435                                                 string msg = Locale.GetText ("Unable to decode certificate.");
436                                                 // inner exception is the original (not second) exception
437                                                 throw new CryptographicException (msg, e);
438                                         }
439                                 }
440                         } else {
441                                 // try PKCS#12
442                                 try {
443                                         ImportPkcs12 (rawData, password);
444                                 }
445                                 catch {
446                                         // it's possible to supply a (unrequired/unusued) password
447                                         // fix bug #79028
448                                         _cert = new Mono.Security.X509.X509Certificate (rawData);
449                                 }
450                         }
451                 }
452
453                 [MonoTODO ("SecureString is incomplete")]
454                 public override void Import (byte[] rawData, SecureString password, X509KeyStorageFlags keyStorageFlags)
455                 {
456                         Import (rawData, (string) null, keyStorageFlags);
457                 }
458
459                 public override void Import (string fileName) 
460                 {
461                         byte[] rawData = Load (fileName);
462                         Import (rawData, (string)null, X509KeyStorageFlags.DefaultKeySet);
463                 }
464
465                 [MonoTODO ("missing KeyStorageFlags support")]
466                 public override void Import (string fileName, string password, X509KeyStorageFlags keyStorageFlags) 
467                 {
468                         byte[] rawData = Load (fileName);
469                         Import (rawData, password, keyStorageFlags);
470                 }
471
472                 [MonoTODO ("SecureString is incomplete")]
473                 public override void Import (string fileName, SecureString password, X509KeyStorageFlags keyStorageFlags) 
474                 {
475                         byte[] rawData = Load (fileName);
476                         Import (rawData, (string)null, keyStorageFlags);
477                 }
478
479                 private static byte[] Load (string fileName)
480                 {
481                         byte[] data = null;
482                         using (FileStream fs = File.OpenRead (fileName)) {
483                                 data = new byte [fs.Length];
484                                 fs.Read (data, 0, data.Length);
485                                 fs.Close ();
486                         }
487                         return data;
488                 }
489
490                 public override void Reset () 
491                 {
492                         _cert = null;
493                         _archived = false;
494                         _extensions = null;
495                         _name = String.Empty;
496                         _serial = null;
497                         _publicKey = null;
498                         issuer_name = null;
499                         subject_name = null;
500                         signature_algorithm = null;
501                         base.Reset ();
502                 }
503
504                 public override string ToString ()
505                 {
506                         if (_cert == null)
507                                 return "System.Security.Cryptography.X509Certificates.X509Certificate2";
508
509                         return base.ToString (true);
510                 }
511
512                 public override string ToString (bool verbose)
513                 {
514                         if (_cert == null)
515                                 return "System.Security.Cryptography.X509Certificates.X509Certificate2";
516
517                         // the non-verbose X509Certificate2 == verbose X509Certificate
518                         if (!verbose)
519                                 return base.ToString (true);
520
521                         string nl = Environment.NewLine;
522                         StringBuilder sb = new StringBuilder ();
523                         sb.AppendFormat ("[Version]{0}  V{1}{0}{0}", nl, Version);
524                         sb.AppendFormat ("[Subject]{0}  {1}{0}{0}", nl, Subject);
525                         sb.AppendFormat ("[Issuer]{0}  {1}{0}{0}", nl, Issuer);
526                         sb.AppendFormat ("[Serial Number]{0}  {1}{0}{0}", nl, SerialNumber);
527                         sb.AppendFormat ("[Not Before]{0}  {1}{0}{0}", nl, NotBefore);
528                         sb.AppendFormat ("[Not After]{0}  {1}{0}{0}", nl, NotAfter);
529                         sb.AppendFormat ("[Thumbprint]{0}  {1}{0}{0}", nl, Thumbprint);
530                         sb.AppendFormat ("[Signature Algorithm]{0}  {1}({2}){0}{0}", nl, SignatureAlgorithm.FriendlyName, 
531                                 SignatureAlgorithm.Value);
532
533                         AsymmetricAlgorithm key = PublicKey.Key;
534                         sb.AppendFormat ("[Public Key]{0}  Algorithm: ", nl);
535                         if (key is RSA)
536                                 sb.Append ("RSA");
537                         else if (key is DSA)
538                                 sb.Append ("DSA");
539                         else
540                                 sb.Append (key.ToString ());
541                         sb.AppendFormat ("{0}  Length: {1}{0}  Key Blob: ", nl, key.KeySize);
542                         AppendBuffer (sb, PublicKey.EncodedKeyValue.RawData);
543                         sb.AppendFormat ("{0}  Parameters: ", nl);
544                         AppendBuffer (sb, PublicKey.EncodedParameters.RawData);
545                         sb.Append (nl);
546
547                         return sb.ToString ();
548                 }
549
550                 private static void AppendBuffer (StringBuilder sb, byte[] buffer)
551                 {
552                         if (buffer == null)
553                                 return;
554                         for (int i=0; i < buffer.Length; i++) {
555                                 sb.Append (buffer [i].ToString ("x2"));
556                                 if (i < buffer.Length - 1)
557                                         sb.Append (" ");
558                         }
559                 }
560
561                 [MonoTODO ("by default this depends on the incomplete X509Chain")]
562                 public bool Verify ()
563                 {
564                         if (_cert == null)
565                                 throw new CryptographicException (empty_error);
566
567                         X509Chain chain = (X509Chain) CryptoConfig.CreateFromName ("X509Chain");
568                         if (!chain.Build (this))
569                                 return false;
570                         // TODO - check chain and other stuff ???
571                         return true;
572                 }
573
574                 // static methods
575
576                 private static byte[] signedData = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02 };
577
578                 [MonoTODO ("Detection limited to Cert, Pfx, Pkcs12, Pkcs7 and Unknown")]
579                 public static X509ContentType GetCertContentType (byte[] rawData)
580                 {
581                         if ((rawData == null) || (rawData.Length == 0))
582                                 throw new ArgumentException ("rawData");
583
584                         X509ContentType type = X509ContentType.Unknown;
585                         try {
586                                 ASN1 data = new ASN1 (rawData);
587                                 if (data.Tag != 0x30) {
588                                         string msg = Locale.GetText ("Unable to decode certificate.");
589                                         throw new CryptographicException (msg);
590                                 }
591
592                                 if (data.Count == 0)
593                                         return type;
594
595                                 if (data.Count == 3) {
596                                         switch (data [0].Tag) {
597                                         case 0x30:
598                                                 // SEQUENCE / SEQUENCE / BITSTRING
599                                                 if ((data [1].Tag == 0x30) && (data [2].Tag == 0x03))
600                                                         type = X509ContentType.Cert;
601                                                 break;
602                                         case 0x02:
603                                                 // INTEGER / SEQUENCE / SEQUENCE
604                                                 if ((data [1].Tag == 0x30) && (data [2].Tag == 0x30))
605                                                         type = X509ContentType.Pkcs12;
606                                                 // note: Pfx == Pkcs12
607                                                 break;
608                                         }
609                                 }
610
611                                 // check for PKCS#7 (count unknown but greater than 0)
612                                 // SEQUENCE / OID (signedData)
613                                 if ((data [0].Tag == 0x06) && data [0].CompareValue (signedData))
614                                         type = X509ContentType.Pkcs7;
615                         }
616                         catch (Exception e) {
617                                 string msg = Locale.GetText ("Unable to decode certificate.");
618                                 throw new CryptographicException (msg, e);
619                         }
620
621                         return type;
622                 }
623
624                 [MonoTODO ("Detection limited to Cert, Pfx, Pkcs12 and Unknown")]
625                 public static X509ContentType GetCertContentType (string fileName)
626                 {
627                         if (fileName == null)
628                                 throw new ArgumentNullException ("fileName");
629                         if (fileName.Length == 0)
630                                 throw new ArgumentException ("fileName");
631
632                         byte[] data = Load (fileName);
633                         return GetCertContentType (data);
634                 }
635
636                 // internal stuff because X509Certificate2 isn't complete enough
637                 // (maybe X509Certificate3 will be better?)
638
639                 internal MX.X509Certificate MonoCertificate {
640                         get { return _cert; }
641                 }
642         }
643 }
644
645 #endif