Merge pull request #3563 from lewurm/interpreter
[mono.git] / mcs / class / System / Mono.Btls / X509CertificateImplBtls.cs
1 //
2 // X509CertificateImplBtls.cs
3 //
4 // Author:
5 //       Martin Baulig <martin.baulig@xamarin.com>
6 //
7 // Copyright (c) 2016 Xamarin Inc. (http://www.xamarin.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining a copy
10 // of this software and associated documentation files (the "Software"), to deal
11 // in the Software without restriction, including without limitation the rights
12 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 // copies of the Software, and to permit persons to whom the Software is
14 // furnished to do so, subject to the following conditions:
15 //
16 // The above copyright notice and this permission notice shall be included in
17 // all copies or substantial portions of the Software.
18 //
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 // THE SOFTWARE.
26 #if SECURITY_DEP && MONO_FEATURE_BTLS
27 #if MONO_SECURITY_ALIAS
28 extern alias MonoSecurity;
29 #endif
30
31 #if MONO_SECURITY_ALIAS
32 using MX = MonoSecurity::Mono.Security.X509;
33 #else
34 using MX = Mono.Security.X509;
35 #endif
36
37 using System;
38 using System.Text;
39 using System.Collections;
40 using System.Security;
41 using System.Security.Cryptography;
42 using System.Security.Cryptography.X509Certificates;
43 using Mono.Security.Cryptography;
44
45 namespace Mono.Btls
46 {
47         class X509CertificateImplBtls : X509Certificate2Impl
48         {
49                 MonoBtlsX509 x509;
50                 MonoBtlsKey nativePrivateKey;
51                 X500DistinguishedName subjectName;
52                 X500DistinguishedName issuerName;
53                 X509CertificateImplCollection intermediateCerts;
54                 PublicKey publicKey;
55                 bool archived;
56                 bool disallowFallback;
57
58                 internal X509CertificateImplBtls (bool disallowFallback = false)
59                 {
60                         this.disallowFallback = disallowFallback;
61                 }
62
63                 internal X509CertificateImplBtls (MonoBtlsX509 x509, bool disallowFallback = false)
64                 {
65                         this.disallowFallback = disallowFallback;
66                         this.x509 = x509.Copy ();
67                 }
68
69                 X509CertificateImplBtls (X509CertificateImplBtls other)
70                 {
71                         disallowFallback = other.disallowFallback;
72                         x509 = other.x509 != null ? other.x509.Copy () : null;
73                         nativePrivateKey = other.nativePrivateKey != null ? other.nativePrivateKey.Copy () : null;
74                         fallback = other.fallback != null ? (X509Certificate2Impl)other.fallback.Clone () : null;
75                         if (other.intermediateCerts != null)
76                                 intermediateCerts = other.intermediateCerts.Clone ();
77                 }
78
79                 internal X509CertificateImplBtls (byte[] data, MonoBtlsX509Format format, bool disallowFallback = false)
80                 {
81                         this.disallowFallback = disallowFallback;
82                         x509 = MonoBtlsX509.LoadFromData (data, format);
83                 }
84
85                 public override bool IsValid {
86                         get { return x509 != null && x509.IsValid; }
87                 }
88
89                 public override IntPtr Handle {
90                         get { return x509.Handle.DangerousGetHandle (); }
91                 }
92
93                 public override IntPtr GetNativeAppleCertificate ()
94                 {
95                         return IntPtr.Zero;
96                 }
97
98                 internal MonoBtlsX509 X509 {
99                         get {
100                                 ThrowIfContextInvalid ();
101                                 return x509;
102                         }
103                 }
104
105                 internal MonoBtlsKey NativePrivateKey {
106                         get {
107                                 ThrowIfContextInvalid ();
108                                 if (nativePrivateKey == null && FallbackImpl.HasPrivateKey) {
109                                         var key = FallbackImpl.PrivateKey as RSA;
110                                         if (key == null)
111                                                 throw new NotSupportedException ("Currently only supports RSA private keys.");
112                                         nativePrivateKey = MonoBtlsKey.CreateFromRSAPrivateKey (key);
113                                 }
114                                 return nativePrivateKey;
115                         }
116                 }
117
118                 public override X509CertificateImpl Clone ()
119                 {
120                         ThrowIfContextInvalid ();
121                         return new X509CertificateImplBtls (this);
122                 }
123
124                 public override bool Equals (X509CertificateImpl other, out bool result)
125                 {
126                         var otherBoringImpl = other as X509CertificateImplBtls;
127                         if (otherBoringImpl == null) {
128                                 result = false;
129                                 return false;
130                         }
131
132                         result = MonoBtlsX509.Compare (X509, otherBoringImpl.X509) == 0;
133                         return true;
134                 }
135
136                 protected override byte[] GetCertHash (bool lazy)
137                 {
138                         return X509.GetCertHash ();
139                 }
140
141                 public override byte[] GetRawCertData ()
142                 {
143                         return X509.GetRawData (MonoBtlsX509Format.DER);
144                 }
145
146                 public override string GetSubjectName (bool legacyV1Mode)
147                 {
148                         if (legacyV1Mode)
149                                 return SubjectName.Decode (X500DistinguishedNameFlags.None);
150                         return SubjectName.Name;
151                 }
152
153                 public override string GetIssuerName (bool legacyV1Mode)
154                 {
155                         if (legacyV1Mode)
156                                 return IssuerName.Decode (X500DistinguishedNameFlags.None);
157                         return IssuerName.Name;
158                 }
159
160                 public override DateTime GetValidFrom ()
161                 {
162                         return X509.GetNotBefore ().ToLocalTime ();
163                 }
164
165                 public override DateTime GetValidUntil ()
166                 {
167                         return X509.GetNotAfter ().ToLocalTime ();
168                 }
169
170                 public override byte[] GetPublicKey ()
171                 {
172                         return X509.GetPublicKeyData ();
173                 }
174
175                 public override byte[] GetSerialNumber ()
176                 {
177                         return X509.GetSerialNumber (true);
178                 }
179
180                 public override string GetKeyAlgorithm ()
181                 {
182                         return PublicKey.Oid.Value;
183                 }
184
185                 public override byte[] GetKeyAlgorithmParameters ()
186                 {
187                         return PublicKey.EncodedParameters.RawData;
188                 }
189
190                 public override byte[] Export (X509ContentType contentType, byte[] password)
191                 {
192                         ThrowIfContextInvalid ();
193
194                         switch (contentType) {
195                         case X509ContentType.Cert:
196                                 return GetRawCertData ();
197                         case X509ContentType.Pfx: // this includes Pkcs12
198                                 // TODO
199                                 throw new NotSupportedException ();
200                         case X509ContentType.SerializedCert:
201                                 // TODO
202                                 throw new NotSupportedException ();
203                         default:
204                                 string msg = Locale.GetText ("This certificate format '{0}' cannot be exported.", contentType);
205                                 throw new CryptographicException (msg);
206                         }
207                 }
208
209                 internal override X509CertificateImplCollection IntermediateCertificates {
210                         get { return intermediateCerts; }
211                 }
212
213                 public override string ToString (bool full)
214                 {
215                         ThrowIfContextInvalid ();
216
217                         if (!full) {
218                                 var summary = GetSubjectName (false);
219                                 return string.Format ("[X509Certificate: {0}]", summary);
220                         }
221
222                         string nl = Environment.NewLine;
223                         StringBuilder sb = new StringBuilder ();
224                         sb.AppendFormat ("[Subject]{0}  {1}{0}{0}", nl, GetSubjectName (false));
225
226                         sb.AppendFormat ("[Issuer]{0}  {1}{0}{0}", nl, GetIssuerName (false));
227                         sb.AppendFormat ("[Not Before]{0}  {1}{0}{0}", nl, GetValidFrom ().ToLocalTime ());
228                         sb.AppendFormat ("[Not After]{0}  {1}{0}{0}", nl, GetValidUntil ().ToLocalTime ());
229                         sb.AppendFormat ("[Thumbprint]{0}  {1}{0}", nl, X509Helper.ToHexString (GetCertHash ()));
230
231                         sb.Append (nl);
232                         return sb.ToString ();
233                 }
234
235                 protected override void Dispose (bool disposing)
236                 {
237                         if (x509 != null) {
238                                 x509.Dispose ();
239                                 x509 = null;
240                         }
241                 }
242
243 #region X509Certificate2Impl
244
245                 X509Certificate2Impl fallback;
246
247                 void MustFallback ()
248                 {
249                         if (disallowFallback)
250                                 throw new InvalidOperationException ();
251                         if (fallback != null)
252                                 return;
253                         fallback = X509Helper2.Import (GetRawCertData (), null, X509KeyStorageFlags.DefaultKeySet, true);
254                 }
255
256                 internal override X509Certificate2Impl FallbackImpl {
257                         get {
258                                 MustFallback ();
259                                 return fallback;
260                         }
261                 }
262
263                 [MonoTODO]
264                 public override bool Archived {
265                         get {
266                                 ThrowIfContextInvalid ();
267                                 return archived;
268                         }
269                         set {
270                                 ThrowIfContextInvalid ();
271                                 archived = value;
272                         }
273                 }
274
275                 public override X509ExtensionCollection Extensions {
276                         get { return FallbackImpl.Extensions; }
277                 }
278
279                 public override bool HasPrivateKey {
280                         get { return nativePrivateKey != null || FallbackImpl.HasPrivateKey; }
281                 }
282
283                 public override X500DistinguishedName IssuerName {
284                         get {
285                                 ThrowIfContextInvalid ();
286                                 if (issuerName == null) {
287                                         using (var xname = x509.GetIssuerName ()) {
288                                                 var encoding = xname.GetRawData (false);
289                                                 var canonEncoding = xname.GetRawData (true);
290                                                 var name = MonoBtlsUtils.FormatName (xname, true, ", ", true);
291                                                 issuerName = new X500DistinguishedName (encoding, canonEncoding, name);
292                                         }
293                                 }
294                                 return issuerName;
295                         }
296                 }
297
298                 public override AsymmetricAlgorithm PrivateKey {
299                         get {
300                                 if (nativePrivateKey == null || !nativePrivateKey.IsRsa)
301                                         return FallbackImpl.PrivateKey;
302                                 var bytes = nativePrivateKey.GetBytes (true);
303                                 return PKCS8.PrivateKeyInfo.DecodeRSA (bytes);
304                         }
305                         set {
306                                 nativePrivateKey = null;
307                                 FallbackImpl.PrivateKey = value;
308                         }
309                 }
310
311                 public override PublicKey PublicKey {
312                         get {
313                                 ThrowIfContextInvalid ();
314                                 if (publicKey == null) {
315                                         var keyAsn = X509.GetPublicKeyAsn1 ();
316                                         var keyParamAsn = X509.GetPublicKeyParameters ();
317                                         publicKey = new PublicKey (keyAsn.Oid, keyParamAsn, keyAsn);
318                                 }
319                                 return publicKey;
320                         }
321                 }
322
323                 public override Oid SignatureAlgorithm {
324                         get {
325                                 ThrowIfContextInvalid ();
326                                 return X509.GetSignatureAlgorithm ();
327                         }
328                 }
329
330                 public override X500DistinguishedName SubjectName {
331                         get {
332                                 ThrowIfContextInvalid ();
333                                 if (subjectName == null) {
334                                         using (var xname = x509.GetSubjectName ()) {
335                                                 var encoding = xname.GetRawData (false);
336                                                 var canonEncoding = xname.GetRawData (true);
337                                                 var name = MonoBtlsUtils.FormatName (xname, true, ", ", true);
338                                                 subjectName = new X500DistinguishedName (encoding, canonEncoding, name);
339                                         }
340                                 }
341                                 return subjectName;
342                         }
343                 }
344
345                 public override int Version {
346                         get { return X509.GetVersion (); }
347                 }
348
349                 public override string GetNameInfo (X509NameType nameType, bool forIssuer)
350                 {
351                         return FallbackImpl.GetNameInfo (nameType, forIssuer);
352                 }
353
354                 public override void Import (byte[] data, string password, X509KeyStorageFlags keyStorageFlags)
355                 {
356                         Reset ();
357                         if (password == null) {
358                                 try {
359                                         Import (data);
360                                 } catch (Exception e) {
361                                         try {
362                                                  ImportPkcs12 (data, null);
363                                         } catch {
364                                                 string msg = Locale.GetText ("Unable to decode certificate.");
365                                                 // inner exception is the original (not second) exception
366                                                 throw new CryptographicException (msg, e);
367                                         }
368                                 }
369                         } else {
370                                 // try PKCS#12
371                                 try {
372                                         ImportPkcs12 (data, password);
373                                 } catch (Exception e) {
374                                         try {
375                                                 // it's possible to supply a (unrequired/unusued) password
376                                                 // fix bug #79028
377                                                 Import (data);
378                                         } catch {
379                                                 string msg = Locale.GetText ("Unable to decode certificate.");
380                                                 // inner exception is the original (not second) exception
381                                                 throw new CryptographicException (msg, e);
382                                         }
383                                 }
384                         }
385                 }
386
387                 void Import (byte[] data)
388                 {
389                         // Does it look like PEM?
390                         if ((data.Length > 0) && (data [0] != 0x30))
391                                 x509 = MonoBtlsX509.LoadFromData (data, MonoBtlsX509Format.PEM);
392                         else
393                                 x509 = MonoBtlsX509.LoadFromData (data, MonoBtlsX509Format.DER);
394                 }
395
396                 void ImportPkcs12 (byte[] data, string password)
397                 {
398                         using (var pkcs12 = new MonoBtlsPkcs12 ()) {
399                                 if (string.IsNullOrEmpty (password)) {
400                                         try {
401                                                 // Support both unencrypted PKCS#12..
402                                                 pkcs12.Import (data, null);
403                                         } catch {
404                                                 // ..and PKCS#12 encrypted with an empty password
405                                                 pkcs12.Import (data, string.Empty);
406                                         }
407                                 } else {
408                                         pkcs12.Import (data, password);
409                                 }
410
411                                 x509 = pkcs12.GetCertificate (0);
412                                 if (pkcs12.HasPrivateKey)
413                                         nativePrivateKey = pkcs12.GetPrivateKey ();
414                                 if (pkcs12.Count > 1) {
415                                         intermediateCerts = new X509CertificateImplCollection ();
416                                         for (int i = 0; i < pkcs12.Count; i++) {
417                                                 using (var ic = pkcs12.GetCertificate (i)) {
418                                                         if (MonoBtlsX509.Compare (ic, x509) == 0)
419                                                                 continue;
420                                                         var impl = new X509CertificateImplBtls (ic, true);
421                                                         intermediateCerts.Add (impl, true);
422                                                 }
423                                         }
424                                 }
425                         }
426                 }
427
428                 public override byte[] Export (X509ContentType contentType, string password)
429                 {
430                         ThrowIfContextInvalid ();
431
432                         switch (contentType) {
433                         case X509ContentType.Cert:
434                                 return GetRawCertData ();
435                         case X509ContentType.Pfx: // this includes Pkcs12
436                                 return ExportPkcs12 (password);
437                         case X509ContentType.SerializedCert:
438                                 // TODO
439                                 throw new NotSupportedException ();
440                         default:
441                                 string msg = Locale.GetText ("This certificate format '{0}' cannot be exported.", contentType);
442                                 throw new CryptographicException (msg);
443                         }
444                 }
445
446                 byte[] ExportPkcs12 (string password)
447                 {
448                         var pfx = new MX.PKCS12 ();
449                         try {
450                                 var attrs = new Hashtable ();
451                                 var localKeyId = new ArrayList ();
452                                 localKeyId.Add (new byte[] { 1, 0, 0, 0 });
453                                 attrs.Add (MX.PKCS9.localKeyId, localKeyId);
454                                 if (password != null)
455                                         pfx.Password = password;
456                                 pfx.AddCertificate (new MX.X509Certificate (GetRawCertData ()), attrs);
457                                 if (IntermediateCertificates != null) {
458                                         for (int i = 0; i < IntermediateCertificates.Count; i++)
459                                                 pfx.AddCertificate (new MX.X509Certificate (IntermediateCertificates [i].GetRawCertData ()));
460                                 }
461                                 var privateKey = PrivateKey;
462                                 if (privateKey != null)
463                                         pfx.AddPkcs8ShroudedKeyBag (privateKey, attrs);
464                                 return pfx.GetBytes ();
465                         } finally {
466                                 pfx.Password = null;
467                         }
468                 }
469
470                 public override bool Verify (X509Certificate2 thisCertificate)
471                 {
472                         using (var chain = new MonoBtlsX509Chain ()) {
473                                 chain.AddCertificate (x509.Copy ());
474                                 if (intermediateCerts != null) {
475                                         for (int i = 0; i < intermediateCerts.Count; i++) {
476                                                 var intermediate = (X509CertificateImplBtls)intermediateCerts [i];
477                                                 chain.AddCertificate (intermediate.x509.Copy ());
478                                         }
479                                 }
480                                 return MonoBtlsProvider.ValidateCertificate (chain, null);
481                         }
482                 }
483
484                 public override void Reset ()
485                 {
486                         if (x509 != null) {
487                                 x509.Dispose ();
488                                 x509 = null;
489                         }
490                         if (nativePrivateKey != null) {
491                                 nativePrivateKey = null;
492                         }
493                         subjectName = null;
494                         issuerName = null;
495                         archived = false;
496                         publicKey = null;
497                         intermediateCerts = null;
498                         if (fallback != null)
499                                 fallback.Reset ();
500                 }
501
502 #endregion
503         }
504 }
505 #endif