Merge pull request #5714 from alexischr/update_bockbuild
[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                                 if (nativePrivateKey != null)
307                                         nativePrivateKey.Dispose ();
308                                 nativePrivateKey = null;
309                                 FallbackImpl.PrivateKey = value;
310                         }
311                 }
312
313                 public override PublicKey PublicKey {
314                         get {
315                                 ThrowIfContextInvalid ();
316                                 if (publicKey == null) {
317                                         var keyAsn = X509.GetPublicKeyAsn1 ();
318                                         var keyParamAsn = X509.GetPublicKeyParameters ();
319                                         publicKey = new PublicKey (keyAsn.Oid, keyParamAsn, keyAsn);
320                                 }
321                                 return publicKey;
322                         }
323                 }
324
325                 public override Oid SignatureAlgorithm {
326                         get {
327                                 ThrowIfContextInvalid ();
328                                 return X509.GetSignatureAlgorithm ();
329                         }
330                 }
331
332                 public override X500DistinguishedName SubjectName {
333                         get {
334                                 ThrowIfContextInvalid ();
335                                 if (subjectName == null) {
336                                         using (var xname = x509.GetSubjectName ()) {
337                                                 var encoding = xname.GetRawData (false);
338                                                 var canonEncoding = xname.GetRawData (true);
339                                                 var name = MonoBtlsUtils.FormatName (xname, true, ", ", true);
340                                                 subjectName = new X500DistinguishedName (encoding, canonEncoding, name);
341                                         }
342                                 }
343                                 return subjectName;
344                         }
345                 }
346
347                 public override int Version {
348                         get { return X509.GetVersion (); }
349                 }
350
351                 public override string GetNameInfo (X509NameType nameType, bool forIssuer)
352                 {
353                         return FallbackImpl.GetNameInfo (nameType, forIssuer);
354                 }
355
356                 public override void Import (byte[] data, string password, X509KeyStorageFlags keyStorageFlags)
357                 {
358                         Reset ();
359                         if (password == null) {
360                                 try {
361                                         Import (data);
362                                 } catch (Exception e) {
363                                         try {
364                                                  ImportPkcs12 (data, null);
365                                         } catch {
366                                                 string msg = Locale.GetText ("Unable to decode certificate.");
367                                                 // inner exception is the original (not second) exception
368                                                 throw new CryptographicException (msg, e);
369                                         }
370                                 }
371                         } else {
372                                 // try PKCS#12
373                                 try {
374                                         ImportPkcs12 (data, password);
375                                 } catch (Exception e) {
376                                         try {
377                                                 // it's possible to supply a (unrequired/unusued) password
378                                                 // fix bug #79028
379                                                 Import (data);
380                                         } catch {
381                                                 string msg = Locale.GetText ("Unable to decode certificate.");
382                                                 // inner exception is the original (not second) exception
383                                                 throw new CryptographicException (msg, e);
384                                         }
385                                 }
386                         }
387                 }
388
389                 void Import (byte[] data)
390                 {
391                         if (data != null) {
392                                 // Does it look like PEM?
393                                 if ((data.Length > 0) && (data [0] != 0x30))
394                                         x509 = MonoBtlsX509.LoadFromData (data, MonoBtlsX509Format.PEM);
395                                 else
396                                         x509 = MonoBtlsX509.LoadFromData (data, MonoBtlsX509Format.DER);
397                         }
398                 }
399
400                 void ImportPkcs12 (byte[] data, string password)
401                 {
402                         using (var pkcs12 = new MonoBtlsPkcs12 ()) {
403                                 if (string.IsNullOrEmpty (password)) {
404                                         try {
405                                                 // Support both unencrypted PKCS#12..
406                                                 pkcs12.Import (data, null);
407                                         } catch {
408                                                 // ..and PKCS#12 encrypted with an empty password
409                                                 pkcs12.Import (data, string.Empty);
410                                         }
411                                 } else {
412                                         pkcs12.Import (data, password);
413                                 }
414
415                                 x509 = pkcs12.GetCertificate (0);
416                                 if (pkcs12.HasPrivateKey)
417                                         nativePrivateKey = pkcs12.GetPrivateKey ();
418                                 if (pkcs12.Count > 1) {
419                                         intermediateCerts = new X509CertificateImplCollection ();
420                                         for (int i = 0; i < pkcs12.Count; i++) {
421                                                 using (var ic = pkcs12.GetCertificate (i)) {
422                                                         if (MonoBtlsX509.Compare (ic, x509) == 0)
423                                                                 continue;
424                                                         var impl = new X509CertificateImplBtls (ic, true);
425                                                         intermediateCerts.Add (impl, true);
426                                                 }
427                                         }
428                                 }
429                         }
430                 }
431
432                 public override byte[] Export (X509ContentType contentType, string password)
433                 {
434                         ThrowIfContextInvalid ();
435
436                         switch (contentType) {
437                         case X509ContentType.Cert:
438                                 return GetRawCertData ();
439                         case X509ContentType.Pfx: // this includes Pkcs12
440                                 return ExportPkcs12 (password);
441                         case X509ContentType.SerializedCert:
442                                 // TODO
443                                 throw new NotSupportedException ();
444                         default:
445                                 string msg = Locale.GetText ("This certificate format '{0}' cannot be exported.", contentType);
446                                 throw new CryptographicException (msg);
447                         }
448                 }
449
450                 byte[] ExportPkcs12 (string password)
451                 {
452                         var pfx = new MX.PKCS12 ();
453                         try {
454                                 var attrs = new Hashtable ();
455                                 var localKeyId = new ArrayList ();
456                                 localKeyId.Add (new byte[] { 1, 0, 0, 0 });
457                                 attrs.Add (MX.PKCS9.localKeyId, localKeyId);
458                                 if (password != null)
459                                         pfx.Password = password;
460                                 pfx.AddCertificate (new MX.X509Certificate (GetRawCertData ()), attrs);
461                                 if (IntermediateCertificates != null) {
462                                         for (int i = 0; i < IntermediateCertificates.Count; i++)
463                                                 pfx.AddCertificate (new MX.X509Certificate (IntermediateCertificates [i].GetRawCertData ()));
464                                 }
465                                 var privateKey = PrivateKey;
466                                 if (privateKey != null)
467                                         pfx.AddPkcs8ShroudedKeyBag (privateKey, attrs);
468                                 return pfx.GetBytes ();
469                         } finally {
470                                 pfx.Password = null;
471                         }
472                 }
473
474                 public override bool Verify (X509Certificate2 thisCertificate)
475                 {
476                         using (var chain = new MonoBtlsX509Chain ()) {
477                                 chain.AddCertificate (x509.Copy ());
478                                 if (intermediateCerts != null) {
479                                         for (int i = 0; i < intermediateCerts.Count; i++) {
480                                                 var intermediate = (X509CertificateImplBtls)intermediateCerts [i];
481                                                 chain.AddCertificate (intermediate.x509.Copy ());
482                                         }
483                                 }
484                                 return MonoBtlsProvider.ValidateCertificate (chain, null);
485                         }
486                 }
487
488                 public override void Reset ()
489                 {
490                         if (x509 != null) {
491                                 x509.Dispose ();
492                                 x509 = null;
493                         }
494                         if (nativePrivateKey != null) {
495                                 nativePrivateKey.Dispose ();
496                                 nativePrivateKey = null;
497                         }
498                         subjectName = null;
499                         issuerName = null;
500                         archived = false;
501                         publicKey = null;
502                         intermediateCerts = null;
503                         if (fallback != null)
504                                 fallback.Reset ();
505                 }
506
507 #endregion
508         }
509 }
510 #endif