2 // X509CertificateImplBtls.cs
5 // Martin Baulig <martin.baulig@xamarin.com>
7 // Copyright (c) 2016 Xamarin Inc. (http://www.xamarin.com)
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:
16 // The above copyright notice and this permission notice shall be included in
17 // all copies or substantial portions of the Software.
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
26 #if SECURITY_DEP && MONO_FEATURE_BTLS
27 #if MONO_SECURITY_ALIAS
28 extern alias MonoSecurity;
31 #if MONO_SECURITY_ALIAS
32 using MX = MonoSecurity::Mono.Security.X509;
34 using MX = Mono.Security.X509;
39 using System.Collections;
40 using System.Security;
41 using System.Security.Cryptography;
42 using System.Security.Cryptography.X509Certificates;
43 using Mono.Security.Cryptography;
47 class X509CertificateImplBtls : X509Certificate2Impl
50 MonoBtlsKey nativePrivateKey;
51 X500DistinguishedName subjectName;
52 X500DistinguishedName issuerName;
53 X509CertificateImplCollection intermediateCerts;
56 bool disallowFallback;
58 internal X509CertificateImplBtls (bool disallowFallback = false)
60 this.disallowFallback = disallowFallback;
63 internal X509CertificateImplBtls (MonoBtlsX509 x509, bool disallowFallback = false)
65 this.disallowFallback = disallowFallback;
66 this.x509 = x509.Copy ();
69 X509CertificateImplBtls (X509CertificateImplBtls other)
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 ();
79 internal X509CertificateImplBtls (byte[] data, MonoBtlsX509Format format, bool disallowFallback = false)
81 this.disallowFallback = disallowFallback;
82 x509 = MonoBtlsX509.LoadFromData (data, format);
85 public override bool IsValid {
86 get { return x509 != null && x509.IsValid; }
89 public override IntPtr Handle {
90 get { return x509.Handle.DangerousGetHandle (); }
93 public override IntPtr GetNativeAppleCertificate ()
98 internal MonoBtlsX509 X509 {
100 ThrowIfContextInvalid ();
105 internal MonoBtlsKey NativePrivateKey {
107 ThrowIfContextInvalid ();
108 if (nativePrivateKey == null && FallbackImpl.HasPrivateKey) {
109 var key = FallbackImpl.PrivateKey as RSA;
111 throw new NotSupportedException ("Currently only supports RSA private keys.");
112 nativePrivateKey = MonoBtlsKey.CreateFromRSAPrivateKey (key);
114 return nativePrivateKey;
118 public override X509CertificateImpl Clone ()
120 ThrowIfContextInvalid ();
121 return new X509CertificateImplBtls (this);
124 public override bool Equals (X509CertificateImpl other, out bool result)
126 var otherBoringImpl = other as X509CertificateImplBtls;
127 if (otherBoringImpl == null) {
132 result = MonoBtlsX509.Compare (X509, otherBoringImpl.X509) == 0;
136 protected override byte[] GetCertHash (bool lazy)
138 return X509.GetCertHash ();
141 public override byte[] GetRawCertData ()
143 return X509.GetRawData (MonoBtlsX509Format.DER);
146 public override string GetSubjectName (bool legacyV1Mode)
149 return SubjectName.Decode (X500DistinguishedNameFlags.None);
150 return SubjectName.Name;
153 public override string GetIssuerName (bool legacyV1Mode)
156 return IssuerName.Decode (X500DistinguishedNameFlags.None);
157 return IssuerName.Name;
160 public override DateTime GetValidFrom ()
162 return X509.GetNotBefore ().ToLocalTime ();
165 public override DateTime GetValidUntil ()
167 return X509.GetNotAfter ().ToLocalTime ();
170 public override byte[] GetPublicKey ()
172 return X509.GetPublicKeyData ();
175 public override byte[] GetSerialNumber ()
177 return X509.GetSerialNumber (true);
180 public override string GetKeyAlgorithm ()
182 return PublicKey.Oid.Value;
185 public override byte[] GetKeyAlgorithmParameters ()
187 return PublicKey.EncodedParameters.RawData;
190 public override byte[] Export (X509ContentType contentType, byte[] password)
192 ThrowIfContextInvalid ();
194 switch (contentType) {
195 case X509ContentType.Cert:
196 return GetRawCertData ();
197 case X509ContentType.Pfx: // this includes Pkcs12
199 throw new NotSupportedException ();
200 case X509ContentType.SerializedCert:
202 throw new NotSupportedException ();
204 string msg = Locale.GetText ("This certificate format '{0}' cannot be exported.", contentType);
205 throw new CryptographicException (msg);
209 internal override X509CertificateImplCollection IntermediateCertificates {
210 get { return intermediateCerts; }
213 public override string ToString (bool full)
215 ThrowIfContextInvalid ();
218 var summary = GetSubjectName (false);
219 return string.Format ("[X509Certificate: {0}]", summary);
222 string nl = Environment.NewLine;
223 StringBuilder sb = new StringBuilder ();
224 sb.AppendFormat ("[Subject]{0} {1}{0}{0}", nl, GetSubjectName (false));
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 ()));
232 return sb.ToString ();
235 protected override void Dispose (bool disposing)
243 #region X509Certificate2Impl
245 X509Certificate2Impl fallback;
249 if (disallowFallback)
250 throw new InvalidOperationException ();
251 if (fallback != null)
253 fallback = X509Helper2.Import (GetRawCertData (), null, X509KeyStorageFlags.DefaultKeySet, true);
256 internal override X509Certificate2Impl FallbackImpl {
264 public override bool Archived {
266 ThrowIfContextInvalid ();
270 ThrowIfContextInvalid ();
275 public override X509ExtensionCollection Extensions {
276 get { return FallbackImpl.Extensions; }
279 public override bool HasPrivateKey {
280 get { return nativePrivateKey != null || FallbackImpl.HasPrivateKey; }
283 public override X500DistinguishedName IssuerName {
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);
298 public override AsymmetricAlgorithm PrivateKey {
300 if (nativePrivateKey == null || !nativePrivateKey.IsRsa)
301 return FallbackImpl.PrivateKey;
302 var bytes = nativePrivateKey.GetBytes (true);
303 return PKCS8.PrivateKeyInfo.DecodeRSA (bytes);
306 nativePrivateKey = null;
307 FallbackImpl.PrivateKey = value;
311 public override PublicKey PublicKey {
313 ThrowIfContextInvalid ();
314 if (publicKey == null) {
315 var keyAsn = X509.GetPublicKeyAsn1 ();
316 var keyParamAsn = X509.GetPublicKeyParameters ();
317 publicKey = new PublicKey (keyAsn.Oid, keyParamAsn, keyAsn);
323 public override Oid SignatureAlgorithm {
325 ThrowIfContextInvalid ();
326 return X509.GetSignatureAlgorithm ();
330 public override X500DistinguishedName SubjectName {
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);
345 public override int Version {
346 get { return X509.GetVersion (); }
349 public override string GetNameInfo (X509NameType nameType, bool forIssuer)
351 return FallbackImpl.GetNameInfo (nameType, forIssuer);
354 public override void Import (byte[] data, string password, X509KeyStorageFlags keyStorageFlags)
357 if (password == null) {
360 } catch (Exception e) {
362 ImportPkcs12 (data, null);
364 string msg = Locale.GetText ("Unable to decode certificate.");
365 // inner exception is the original (not second) exception
366 throw new CryptographicException (msg, e);
372 ImportPkcs12 (data, password);
373 } catch (Exception e) {
375 // it's possible to supply a (unrequired/unusued) password
379 string msg = Locale.GetText ("Unable to decode certificate.");
380 // inner exception is the original (not second) exception
381 throw new CryptographicException (msg, e);
387 void Import (byte[] data)
389 // Does it look like PEM?
390 if ((data.Length > 0) && (data [0] != 0x30))
391 x509 = MonoBtlsX509.LoadFromData (data, MonoBtlsX509Format.PEM);
393 x509 = MonoBtlsX509.LoadFromData (data, MonoBtlsX509Format.DER);
396 void ImportPkcs12 (byte[] data, string password)
398 using (var pkcs12 = new MonoBtlsPkcs12 ()) {
399 if (string.IsNullOrEmpty (password)) {
401 // Support both unencrypted PKCS#12..
402 pkcs12.Import (data, null);
404 // ..and PKCS#12 encrypted with an empty password
405 pkcs12.Import (data, string.Empty);
408 pkcs12.Import (data, password);
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)
420 var impl = new X509CertificateImplBtls (ic, true);
421 intermediateCerts.Add (impl, true);
428 public override byte[] Export (X509ContentType contentType, string password)
430 ThrowIfContextInvalid ();
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:
439 throw new NotSupportedException ();
441 string msg = Locale.GetText ("This certificate format '{0}' cannot be exported.", contentType);
442 throw new CryptographicException (msg);
446 byte[] ExportPkcs12 (string password)
448 var pfx = new MX.PKCS12 ();
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 ()));
461 var privateKey = PrivateKey;
462 if (privateKey != null)
463 pfx.AddPkcs8ShroudedKeyBag (privateKey, attrs);
464 return pfx.GetBytes ();
470 public override bool Verify (X509Certificate2 thisCertificate)
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 ());
480 return MonoBtlsProvider.ValidateCertificate (chain, null);
484 public override void Reset ()
490 if (nativePrivateKey != null) {
491 nativePrivateKey = null;
497 intermediateCerts = null;
498 if (fallback != null)