Merge pull request #3716 from vargaz/unbox-stobj-null
[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
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 privateKey;
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                         privateKey = other.privateKey != null ? other.privateKey.Copy () : null;
74                         if (other.intermediateCerts != null)
75                                 intermediateCerts = other.intermediateCerts.Clone ();
76                 }
77
78                 internal X509CertificateImplBtls (byte[] data, MonoBtlsX509Format format, bool disallowFallback = false)
79                 {
80                         this.disallowFallback = disallowFallback;
81                         x509 = MonoBtlsX509.LoadFromData (data, format);
82                 }
83
84                 public override bool IsValid {
85                         get { return x509 != null && x509.IsValid; }
86                 }
87
88                 public override IntPtr Handle {
89                         get { return x509.Handle.DangerousGetHandle (); }
90                 }
91
92                 public override IntPtr GetNativeAppleCertificate ()
93                 {
94                         return IntPtr.Zero;
95                 }
96
97                 internal MonoBtlsX509 X509 {
98                         get {
99                                 ThrowIfContextInvalid ();
100                                 return x509;
101                         }
102                 }
103
104                 internal MonoBtlsKey NativePrivateKey {
105                         get {
106                                 ThrowIfContextInvalid ();
107                                 return privateKey;
108                         }
109                 }
110
111                 public override X509CertificateImpl Clone ()
112                 {
113                         ThrowIfContextInvalid ();
114                         return new X509CertificateImplBtls (this);
115                 }
116
117                 public override bool Equals (X509CertificateImpl other, out bool result)
118                 {
119                         var otherBoringImpl = other as X509CertificateImplBtls;
120                         if (otherBoringImpl == null) {
121                                 result = false;
122                                 return false;
123                         }
124
125                         result = MonoBtlsX509.Compare (X509, otherBoringImpl.X509) == 0;
126                         return true;
127                 }
128
129                 protected override byte[] GetCertHash (bool lazy)
130                 {
131                         return X509.GetCertHash ();
132                 }
133
134                 public override byte[] GetRawCertData ()
135                 {
136                         return X509.GetRawData (MonoBtlsX509Format.DER);
137                 }
138
139                 public override string GetSubjectName (bool legacyV1Mode)
140                 {
141                         if (legacyV1Mode)
142                                 return SubjectName.Decode (X500DistinguishedNameFlags.None);
143                         return SubjectName.Name;
144                 }
145
146                 public override string GetIssuerName (bool legacyV1Mode)
147                 {
148                         if (legacyV1Mode)
149                                 return IssuerName.Decode (X500DistinguishedNameFlags.None);
150                         return IssuerName.Name;
151                 }
152
153                 public override DateTime GetValidFrom ()
154                 {
155                         return X509.GetNotBefore ().ToLocalTime ();
156                 }
157
158                 public override DateTime GetValidUntil ()
159                 {
160                         return X509.GetNotAfter ().ToLocalTime ();
161                 }
162
163                 public override byte[] GetPublicKey ()
164                 {
165                         return X509.GetPublicKeyData ();
166                 }
167
168                 public override byte[] GetSerialNumber ()
169                 {
170                         return X509.GetSerialNumber (true);
171                 }
172
173                 public override string GetKeyAlgorithm ()
174                 {
175                         return PublicKey.Oid.Value;
176                 }
177
178                 public override byte[] GetKeyAlgorithmParameters ()
179                 {
180                         return PublicKey.EncodedParameters.RawData;
181                 }
182
183                 public override byte[] Export (X509ContentType contentType, byte[] password)
184                 {
185                         ThrowIfContextInvalid ();
186
187                         switch (contentType) {
188                         case X509ContentType.Cert:
189                                 return GetRawCertData ();
190                         case X509ContentType.Pfx: // this includes Pkcs12
191                                 // TODO
192                                 throw new NotSupportedException ();
193                         case X509ContentType.SerializedCert:
194                                 // TODO
195                                 throw new NotSupportedException ();
196                         default:
197                                 string msg = Locale.GetText ("This certificate format '{0}' cannot be exported.", contentType);
198                                 throw new CryptographicException (msg);
199                         }
200                 }
201
202                 internal override X509CertificateImplCollection IntermediateCertificates {
203                         get { return intermediateCerts; }
204                 }
205
206                 public override string ToString (bool full)
207                 {
208                         ThrowIfContextInvalid ();
209
210                         if (!full) {
211                                 var summary = GetSubjectName (false);
212                                 return string.Format ("[X509Certificate: {0}]", summary);
213                         }
214
215                         string nl = Environment.NewLine;
216                         StringBuilder sb = new StringBuilder ();
217                         sb.AppendFormat ("[Subject]{0}  {1}{0}{0}", nl, GetSubjectName (false));
218
219                         sb.AppendFormat ("[Issuer]{0}  {1}{0}{0}", nl, GetIssuerName (false));
220                         sb.AppendFormat ("[Not Before]{0}  {1}{0}{0}", nl, GetValidFrom ().ToLocalTime ());
221                         sb.AppendFormat ("[Not After]{0}  {1}{0}{0}", nl, GetValidUntil ().ToLocalTime ());
222                         sb.AppendFormat ("[Thumbprint]{0}  {1}{0}", nl, X509Helper.ToHexString (GetCertHash ()));
223
224                         sb.Append (nl);
225                         return sb.ToString ();
226                 }
227
228                 protected override void Dispose (bool disposing)
229                 {
230                         if (x509 != null) {
231                                 x509.Dispose ();
232                                 x509 = null;
233                         }
234                 }
235
236 #region X509Certificate2Impl
237
238                 X509Certificate2Impl fallback;
239
240                 void MustFallback ()
241                 {
242                         if (disallowFallback)
243                                 throw new InvalidOperationException ();
244                         if (fallback != null)
245                                 return;
246                         fallback = X509Helper2.Import (GetRawCertData (), null, X509KeyStorageFlags.DefaultKeySet, true);
247                 }
248
249                 internal override X509Certificate2Impl FallbackImpl {
250                         get {
251                                 MustFallback ();
252                                 return fallback;
253                         }
254                 }
255
256                 [MonoTODO]
257                 public override bool Archived {
258                         get {
259                                 ThrowIfContextInvalid ();
260                                 return archived;
261                         }
262                         set {
263                                 ThrowIfContextInvalid ();
264                                 archived = value;
265                         }
266                 }
267
268                 public override X509ExtensionCollection Extensions {
269                         get { return FallbackImpl.Extensions; }
270                 }
271
272                 public override bool HasPrivateKey {
273                         get { return privateKey != null; }
274                 }
275
276                 public override X500DistinguishedName IssuerName {
277                         get {
278                                 ThrowIfContextInvalid ();
279                                 if (issuerName == null) {
280                                         using (var xname = x509.GetIssuerName ()) {
281                                                 var encoding = xname.GetRawData (false);
282                                                 var canonEncoding = xname.GetRawData (true);
283                                                 var name = MonoBtlsUtils.FormatName (xname, true, ", ", true);
284                                                 issuerName = new X500DistinguishedName (encoding, canonEncoding, name);
285                                         }
286                                 }
287                                 return issuerName;
288                         }
289                 }
290
291                 public override AsymmetricAlgorithm PrivateKey {
292                         get {
293                                 if (privateKey == null || !privateKey.IsRsa)
294                                         return null;
295                                 var bytes = privateKey.GetBytes (true);
296                                 return PKCS8.PrivateKeyInfo.DecodeRSA (bytes);
297                         }
298                         set { FallbackImpl.PrivateKey = value; }
299                 }
300
301                 public override PublicKey PublicKey {
302                         get {
303                                 ThrowIfContextInvalid ();
304                                 if (publicKey == null) {
305                                         var keyAsn = X509.GetPublicKeyAsn1 ();
306                                         var keyParamAsn = X509.GetPublicKeyParameters ();
307                                         publicKey = new PublicKey (keyAsn.Oid, keyParamAsn, keyAsn);
308                                 }
309                                 return publicKey;
310                         }
311                 }
312
313                 public override Oid SignatureAlgorithm {
314                         get {
315                                 ThrowIfContextInvalid ();
316                                 return X509.GetSignatureAlgorithm ();
317                         }
318                 }
319
320                 public override X500DistinguishedName SubjectName {
321                         get {
322                                 ThrowIfContextInvalid ();
323                                 if (subjectName == null) {
324                                         using (var xname = x509.GetSubjectName ()) {
325                                                 var encoding = xname.GetRawData (false);
326                                                 var canonEncoding = xname.GetRawData (true);
327                                                 var name = MonoBtlsUtils.FormatName (xname, true, ", ", true);
328                                                 subjectName = new X500DistinguishedName (encoding, canonEncoding, name);
329                                         }
330                                 }
331                                 return subjectName;
332                         }
333                 }
334
335                 public override int Version {
336                         get { return X509.GetVersion (); }
337                 }
338
339                 public override string GetNameInfo (X509NameType nameType, bool forIssuer)
340                 {
341                         return FallbackImpl.GetNameInfo (nameType, forIssuer);
342                 }
343
344                 public override void Import (byte[] data, string password, X509KeyStorageFlags keyStorageFlags)
345                 {
346                         if (password == null) {
347                                 try {
348                                         Import (data);
349                                 } catch (Exception e) {
350                                         try {
351                                                  ImportPkcs12 (data, null);
352                                         } catch {
353                                                 string msg = Locale.GetText ("Unable to decode certificate.");
354                                                 // inner exception is the original (not second) exception
355                                                 throw new CryptographicException (msg, e);
356                                         }
357                                 }
358                         } else {
359                                 // try PKCS#12
360                                 try {
361                                         ImportPkcs12 (data, password);
362                                 } catch (Exception e) {
363                                         try {
364                                                 // it's possible to supply a (unrequired/unusued) password
365                                                 // fix bug #79028
366                                                 Import (data);
367                                         } catch {
368                                                 string msg = Locale.GetText ("Unable to decode certificate.");
369                                                 // inner exception is the original (not second) exception
370                                                 throw new CryptographicException (msg, e);
371                                         }
372                                 }
373                         }
374                 }
375
376                 void Import (byte[] data)
377                 {
378                         // Does it look like PEM?
379                         if ((data.Length > 0) && (data [0] != 0x30))
380                                 x509 = MonoBtlsX509.LoadFromData (data, MonoBtlsX509Format.PEM);
381                         else
382                                 x509 = MonoBtlsX509.LoadFromData (data, MonoBtlsX509Format.DER);
383                 }
384
385                 void ImportPkcs12 (byte[] data, string password)
386                 {
387                         using (var pkcs12 = new MonoBtlsPkcs12 ()) {
388                                 if (string.IsNullOrEmpty (password)) {
389                                         try {
390                                                 // Support both unencrypted PKCS#12..
391                                                 pkcs12.Import (data, null);
392                                         } catch {
393                                                 // ..and PKCS#12 encrypted with an empty password
394                                                 pkcs12.Import (data, string.Empty);
395                                         }
396                                 } else {
397                                         pkcs12.Import (data, password);
398                                 }
399
400                                 x509 = pkcs12.GetCertificate (0);
401                                 if (pkcs12.HasPrivateKey)
402                                         privateKey = pkcs12.GetPrivateKey ();
403                                 if (pkcs12.Count > 1) {
404                                         intermediateCerts = new X509CertificateImplCollection ();
405                                         for (int i = 0; i < pkcs12.Count; i++) {
406                                                 using (var ic = pkcs12.GetCertificate (i)) {
407                                                         if (MonoBtlsX509.Compare (ic, x509) == 0)
408                                                                 continue;
409                                                         var impl = new X509CertificateImplBtls (ic, true);
410                                                         intermediateCerts.Add (impl, true);
411                                                 }
412                                         }
413                                 }
414                         }
415                 }
416
417                 public override byte[] Export (X509ContentType contentType, string password)
418                 {
419                         ThrowIfContextInvalid ();
420
421                         switch (contentType) {
422                         case X509ContentType.Cert:
423                                 return GetRawCertData ();
424                         case X509ContentType.Pfx: // this includes Pkcs12
425                                 return ExportPkcs12 (password);
426                         case X509ContentType.SerializedCert:
427                                 // TODO
428                                 throw new NotSupportedException ();
429                         default:
430                                 string msg = Locale.GetText ("This certificate format '{0}' cannot be exported.", contentType);
431                                 throw new CryptographicException (msg);
432                         }
433                 }
434
435                 byte[] ExportPkcs12 (string password)
436                 {
437                         var pfx = new MX.PKCS12 ();
438                         try {
439                                 var attrs = new Hashtable ();
440                                 var localKeyId = new ArrayList ();
441                                 localKeyId.Add (new byte[] { 1, 0, 0, 0 });
442                                 attrs.Add (MX.PKCS9.localKeyId, localKeyId);
443                                 if (password != null)
444                                         pfx.Password = password;
445                                 pfx.AddCertificate (new MX.X509Certificate (GetRawCertData ()), attrs);
446                                 if (IntermediateCertificates != null) {
447                                         for (int i = 0; i < IntermediateCertificates.Count; i++)
448                                                 pfx.AddCertificate (new MX.X509Certificate (IntermediateCertificates [i].GetRawCertData ()));
449                                 }
450                                 var privateKey = PrivateKey;
451                                 if (privateKey != null)
452                                         pfx.AddPkcs8ShroudedKeyBag (privateKey, attrs);
453                                 return pfx.GetBytes ();
454                         } finally {
455                                 pfx.Password = null;
456                         }
457                 }
458
459                 public override bool Verify (X509Certificate2 thisCertificate)
460                 {
461                         using (var chain = new MonoBtlsX509Chain ()) {
462                                 chain.AddCertificate (x509.Copy ());
463                                 if (intermediateCerts != null) {
464                                         for (int i = 0; i < intermediateCerts.Count; i++) {
465                                                 var intermediate = (X509CertificateImplBtls)intermediateCerts [i];
466                                                 chain.AddCertificate (intermediate.x509.Copy ());
467                                         }
468                                 }
469                                 return MonoBtlsProvider.ValidateCertificate (chain, null);
470                         }
471                 }
472
473                 public override void Reset ()
474                 {
475                         if (x509 != null) {
476                                 x509.Dispose ();
477                                 x509 = null;
478                         }
479                         if (privateKey != null) {
480                                 privateKey = null;
481                                 privateKey = null;
482                         }
483                         subjectName = null;
484                         issuerName = null;
485                         archived = false;
486                         publicKey = null;
487                         intermediateCerts = null;
488                         if (fallback != null)
489                                 fallback.Reset ();
490                 }
491
492 #endregion
493         }
494 }
495 #endif