[System] Use 'ObjCRuntimeInternal' as the namespace instead of 'ObjCRuntime'. (#4820)
[mono.git] / mcs / class / System / Mono.AppleTls / Certificate.cs
1 // 
2 // Certificate.cs: Implements the managed SecCertificate wrapper.
3 //
4 // Authors: 
5 //      Miguel de Icaza
6 //  Sebastien Pouliot  <sebastien@xamarin.com>
7 //
8 // Copyright 2010 Novell, Inc
9 // Copyright 2012-2013 Xamarin Inc.
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 #if SECURITY_DEP && MONO_FEATURE_APPLETLS
32
33 using System;
34 using System.Collections.Generic;
35 using System.Runtime.InteropServices;
36 using System.Security.Cryptography.X509Certificates;
37 using Mono.Net;
38
39 using ObjCRuntimeInternal;
40
41 namespace Mono.AppleTls {
42
43         partial class SecCertificate : INativeObject, IDisposable {
44                 internal IntPtr handle;
45                 
46                 internal SecCertificate (IntPtr handle, bool owns = false)
47                 {
48                         if (handle == IntPtr.Zero)
49                                 throw new Exception ("Invalid handle");
50
51                         this.handle = handle;
52                         if (!owns)
53                                 CFObject.CFRetain (handle);
54                 }
55                 
56                 [DllImport (AppleTlsContext.SecurityLibrary, EntryPoint="SecCertificateGetTypeID")]
57                 public extern static IntPtr GetTypeID ();
58                         
59                 [DllImport (AppleTlsContext.SecurityLibrary)]
60                 extern static IntPtr SecCertificateCreateWithData (IntPtr allocator, IntPtr cfData);
61
62                 public SecCertificate (X509Certificate certificate)
63                 {
64                         if (certificate == null)
65                                 throw new ArgumentNullException ("certificate");
66
67                         handle = certificate.Impl.GetNativeAppleCertificate ();
68                         if (handle != IntPtr.Zero) {
69                                 CFObject.CFRetain (handle);
70                                 return;
71                         }
72
73                         using (CFData cert = CFData.FromData (certificate.GetRawCertData ())) {
74                                 Initialize (cert);
75                         }
76                 }
77
78                 internal SecCertificate (X509CertificateImpl impl)
79                 {
80                         handle = impl.GetNativeAppleCertificate ();
81                         if (handle != IntPtr.Zero) {
82                                 CFObject.CFRetain (handle);
83                                 return;
84                         }
85
86                         using (CFData cert = CFData.FromData (impl.GetRawCertData ())) {
87                                 Initialize (cert);
88                         }
89                 }
90
91                 void Initialize (CFData data)
92                 {
93                         handle = SecCertificateCreateWithData (IntPtr.Zero, data.Handle);
94                         if (handle == IntPtr.Zero)
95                                 throw new ArgumentException ("Not a valid DER-encoded X.509 certificate");
96                 }
97
98                 [DllImport (AppleTlsContext.SecurityLibrary)]
99                 extern static IntPtr SecCertificateCopySubjectSummary (IntPtr cert);
100
101                 public string SubjectSummary {
102                         get {
103                                 if (handle == IntPtr.Zero)
104                                         throw new ObjectDisposedException ("SecCertificate");
105                                 
106                                 IntPtr subjectSummaryHandle = IntPtr.Zero;
107                                 try {
108                                         subjectSummaryHandle = SecCertificateCopySubjectSummary (handle);
109                                         CFString subjectSummary = CFString.AsString (subjectSummaryHandle);
110                                         return subjectSummary;
111                                 }
112                                 finally {
113                                         if (subjectSummaryHandle != IntPtr.Zero)
114                                                 CFObject.CFRelease (subjectSummaryHandle);
115                                 }
116                         }
117                 }
118
119                 [DllImport (AppleTlsContext.SecurityLibrary)]
120                 extern static /* CFDataRef */ IntPtr SecCertificateCopyData (/* SecCertificateRef */ IntPtr cert);
121
122                 public CFData DerData {
123                         get {
124                                 if (handle == IntPtr.Zero)
125                                         throw new ObjectDisposedException ("SecCertificate");
126
127                                 IntPtr data = SecCertificateCopyData (handle);
128                                 if (data == IntPtr.Zero)
129                                         throw new ArgumentException ("Not a valid certificate");
130                                 return new CFData (data, true);
131                         }
132                 }
133
134                 public X509Certificate ToX509Certificate ()
135                 {
136                         if (handle == IntPtr.Zero)
137                                 throw new ObjectDisposedException ("SecCertificate");
138
139                         return new X509Certificate (handle);
140                 }
141
142                 internal static bool Equals (SecCertificate first, SecCertificate second)
143                 {
144                         /*
145                          * This is a little bit expensive, but unfortunately there is no better API to compare two
146                          * SecCertificateRef's for equality.
147                          */
148                         if (first == null)
149                                 throw new ArgumentNullException ("first");
150                         if (second == null)
151                                 throw new ArgumentNullException ("second");
152                         if (first.Handle == second.Handle)
153                                 return true;
154
155                         using (var firstData = first.DerData)
156                         using (var secondData = second.DerData) {
157                                 if (firstData.Handle == secondData.Handle)
158                                         return true;
159
160                                 if (firstData.Length != secondData.Length)
161                                         return false;
162                                 IntPtr length = (IntPtr)firstData.Length;
163                                 for (long i = 0; i < (long)length; i++) {
164                                         if (firstData [i] != secondData [i])
165                                                 return false;
166                                 }
167
168                                 return true;
169                         }
170                 }
171
172                 ~SecCertificate ()
173                 {
174                         Dispose (false);
175                 }
176
177                 public IntPtr Handle {
178                         get {
179                                 return handle;
180                         }
181                 }
182
183                 public void Dispose ()
184                 {
185                         Dispose (true);
186                         GC.SuppressFinalize (this);
187                 }
188
189                 protected virtual void Dispose (bool disposing)
190                 {
191                         if (handle != IntPtr.Zero){
192                                 CFObject.CFRelease (handle);
193                                 handle = IntPtr.Zero;
194                         }
195                 }
196         }
197
198         partial class SecIdentity : INativeObject, IDisposable {
199                  
200                 static readonly CFString ImportExportPassphase;
201                 static readonly CFString ImportItemIdentity;
202                 static readonly CFString ImportExportAccess;
203                 static readonly CFString ImportExportKeychain;
204                 
205                 static SecIdentity ()
206                 {
207                         var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
208                         if (handle == IntPtr.Zero)
209                                 return;
210
211                         try {           
212                                 ImportExportPassphase = CFObject.GetStringConstant (handle, "kSecImportExportPassphrase");
213                                 ImportItemIdentity = CFObject.GetStringConstant (handle, "kSecImportItemIdentity");
214                                 ImportExportAccess = CFObject.GetStringConstant (handle, "kSecImportExportAccess");
215                                 ImportExportKeychain = CFObject.GetStringConstant (handle, "kSecImportExportKeychain");
216                         } finally {
217                                 CFObject.dlclose (handle);
218                         }
219                 }
220
221                 internal IntPtr handle;
222                 
223                 internal SecIdentity (IntPtr handle, bool owns = false)
224                 {
225                         this.handle = handle;
226                         if (!owns)
227                                 CFObject.CFRetain (handle);
228                 }
229
230                 [DllImport (AppleTlsContext.SecurityLibrary, EntryPoint="SecIdentityGetTypeID")]
231                 public extern static IntPtr GetTypeID ();
232
233                 [DllImport (AppleTlsContext.SecurityLibrary)]
234                 extern static /* OSStatus */ SecStatusCode SecIdentityCopyCertificate (/* SecIdentityRef */ IntPtr identityRef,  /* SecCertificateRef* */ out IntPtr certificateRef);
235
236                 public SecCertificate Certificate {
237                         get {
238                                 if (handle == IntPtr.Zero)
239                                         throw new ObjectDisposedException ("SecIdentity");
240                                 IntPtr cert;
241                                 SecStatusCode result = SecIdentityCopyCertificate (handle, out cert);
242                                 if (result != SecStatusCode.Success)
243                                         throw new InvalidOperationException (result.ToString ());
244                                 return new SecCertificate (cert, true);
245                         }
246                 }
247
248                 internal class ImportOptions
249                 {
250 #if !MONOTOUCH
251                         public SecAccess Access {
252                                 get; set;
253                         }
254                         public SecKeyChain KeyChain {
255                                 get; set;
256                         }
257 #endif
258                 }
259
260                 static CFDictionary CreateImportOptions (CFString password, ImportOptions options = null)
261                 {
262                         if (options == null)
263                                 return CFDictionary.FromObjectAndKey (password.Handle, ImportExportPassphase.Handle);
264
265                         var items = new List<Tuple<IntPtr, IntPtr>> ();
266                         items.Add (new Tuple<IntPtr, IntPtr> (ImportExportPassphase.Handle, password.Handle));
267
268 #if !MONOTOUCH
269                         if (options.KeyChain != null)
270                                 items.Add (new Tuple<IntPtr, IntPtr> (ImportExportKeychain.Handle, options.KeyChain.Handle));
271                         if (options.Access != null)
272                                 items.Add (new Tuple<IntPtr, IntPtr> (ImportExportAccess.Handle, options.Access.Handle));
273 #endif
274
275                         return CFDictionary.FromKeysAndObjects (items);
276                 }
277
278                 public static SecIdentity Import (byte[] data, string password, ImportOptions options = null)
279                 {
280                         if (data == null)
281                                 throw new ArgumentNullException ("data");
282                         if (string.IsNullOrEmpty (password)) // SecPKCS12Import() doesn't allow empty passwords.
283                                 throw new ArgumentException ("password");
284                         using (var pwstring = CFString.Create (password))
285                         using (var optionDict = CreateImportOptions (pwstring, options)) {
286                                 CFDictionary [] array;
287                                 SecStatusCode result = SecImportExport.ImportPkcs12 (data, optionDict, out array);
288                                 if (result != SecStatusCode.Success)
289                                         throw new InvalidOperationException (result.ToString ());
290
291                                 return new SecIdentity (array [0].GetValue (ImportItemIdentity.Handle));
292                         }
293                 }
294
295                 public static SecIdentity Import (X509Certificate2 certificate, ImportOptions options = null)
296                 {
297                         if (certificate == null)
298                                 throw new ArgumentNullException ("certificate");
299                         if (!certificate.HasPrivateKey)
300                                 throw new InvalidOperationException ("Need X509Certificate2 with a private key.");
301
302                         /*
303                          * SecPSK12Import does not allow any empty passwords, so let's generate
304                          * a semi-random one here.
305                          */
306                         var password = Guid.NewGuid ().ToString ();
307                         var pkcs12 = certificate.Export (X509ContentType.Pfx, password);
308                         return Import (pkcs12, password, options);
309                 }
310
311                 ~SecIdentity ()
312                 {
313                         Dispose (false);
314                 }
315
316                 public IntPtr Handle {
317                         get {
318                                 return handle;
319                         }
320                 }
321
322                 public void Dispose ()
323                 {
324                         Dispose (true);
325                         GC.SuppressFinalize (this);
326                 }
327
328                 protected virtual void Dispose (bool disposing)
329                 {
330                         if (handle != IntPtr.Zero){
331                                 CFObject.CFRelease (handle);
332                                 handle = IntPtr.Zero;
333                         }
334                 }
335         }
336
337         partial class SecKey : INativeObject, IDisposable {
338                 internal IntPtr handle;
339                 internal IntPtr owner;
340                 
341                 public SecKey (IntPtr handle, bool owns = false)
342                 {
343                         this.handle = handle;
344                         if (!owns)
345                                 CFObject.CFRetain (handle);
346                 }
347
348                 /*
349                  * SecItemImport() returns a SecArrayRef.  We need to free the array, not the items inside it.
350                  * 
351                  */
352                 internal SecKey (IntPtr handle, IntPtr owner)
353                 {
354                         this.handle = handle;
355                         this.owner = owner;
356                         CFObject.CFRetain (owner);
357                 }
358
359                 [DllImport (AppleTlsContext.SecurityLibrary, EntryPoint="SecKeyGetTypeID")]
360                 public extern static IntPtr GetTypeID ();
361                 
362                 ~SecKey ()
363                 {
364                         Dispose (false);
365                 }
366
367                 public IntPtr Handle {
368                         get {
369                                 return handle;
370                         }
371                 }
372
373                 public void Dispose ()
374                 {
375                         Dispose (true);
376                         GC.SuppressFinalize (this);
377                 }
378
379                 protected virtual void Dispose (bool disposing)
380                 {
381                         if (owner != IntPtr.Zero) {
382                                 CFObject.CFRelease (owner);
383                                 owner = handle = IntPtr.Zero;
384                         } else if (handle != IntPtr.Zero) {
385                                 CFObject.CFRelease (handle);
386                                 handle = IntPtr.Zero;
387                         }
388                 }
389         }
390
391 #if !MONOTOUCH
392         class SecAccess : INativeObject, IDisposable {
393                 internal IntPtr handle;
394
395                 public SecAccess (IntPtr handle, bool owns = false)
396                 {
397                         this.handle = handle;
398                         if (!owns)
399                                 CFObject.CFRetain (handle);
400                 }
401
402                 ~SecAccess ()
403                 {
404                         Dispose (false);
405                 }
406
407                 public IntPtr Handle {
408                         get {
409                                 return handle;
410                         }
411                 }
412
413                 [DllImport (AppleTlsContext.SecurityLibrary)]
414                 extern static /* OSStatus */ SecStatusCode SecAccessCreate (/* CFStringRef */ IntPtr descriptor,  /* CFArrayRef */ IntPtr trustedList, /* SecAccessRef _Nullable * */ out IntPtr accessRef);
415
416                 public static SecAccess Create (string descriptor)
417                 {
418                         var descriptorHandle = CFString.Create (descriptor);
419                         if (descriptorHandle == null)
420                                 throw new InvalidOperationException ();
421
422                         try {
423                                 IntPtr accessRef;
424                                 var result = SecAccessCreate (descriptorHandle.Handle, IntPtr.Zero, out accessRef);
425                                 if (result != SecStatusCode.Success)
426                                         throw new InvalidOperationException (result.ToString ());
427
428                                 return new SecAccess (accessRef, true);
429                         } finally {
430                                 descriptorHandle.Dispose ();
431                         }
432                 }
433
434                 public void Dispose ()
435                 {
436                         Dispose (true);
437                         GC.SuppressFinalize (this);
438                 }
439
440                 protected virtual void Dispose (bool disposing)
441                 {
442                         if (handle != IntPtr.Zero) {
443                                 CFObject.CFRelease (handle);
444                                 handle = IntPtr.Zero;
445                         }
446                 }
447         }
448 #endif
449 }
450 #endif