{"id":207,"date":"2013-05-25T09:34:26","date_gmt":"2013-05-25T08:34:26","guid":{"rendered":"http:\/\/oso.com.pl\/?p=207"},"modified":"2013-05-25T09:36:11","modified_gmt":"2013-05-25T08:36:11","slug":"the-curious-case-of-duplicated-certificates-sent-by-ios","status":"publish","type":"post","link":"https:\/\/oso.com.pl\/?p=207&lang=en","title":{"rendered":"The curious case of duplicated certificates sent by iOS"},"content":{"rendered":"<p>Here&#8217;s another one on certificates. Let&#8217;s say you are trying to do client certificate-based authentication in your iOS app and you came up with something like this:<\/p>\n<pre class=\"brush: objc; title: ; notranslate\" title=\"\">\r\n\r\n- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {\r\n if (&#x5B;challenge previousFailureCount] &gt; 0) {\r\n\r\n NSLog(@&quot;Incorrect auth challenge %@&quot;, challenge);\r\n &#x5B;&#x5B;challenge sender] cancelAuthenticationChallenge:challenge];\r\n return;\r\n }\r\n\r\n \/\/ Checking the server certificate\r\n if (&#x5B;&#x5B;challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate &amp;&amp;\r\n self.account.clientCertificate.privateRepresentation.length &gt; 0) {\r\n\r\n \/*\r\n Reading the certificate and creating the identity\r\n *\/\r\n\r\n NSData *p12Data = self.account.clientCertificate.privateRepresentation;\r\n\r\n CFStringRef password = (__bridge CFStringRef)(self.account.clientCertificate.privatePassword);\r\n const void *keys&#x5B;] = { kSecImportExportPassphrase };\r\n const void *values&#x5B;] = { password };\r\n CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);\r\n CFArrayRef p12Items;\r\n\r\n OSStatus result = SecPKCS12Import((__bridge CFDataRef)p12Data, optionsDictionary, &amp;p12Items);\r\n\r\n if(result == noErr) {\r\n CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0);\r\n SecIdentityRef identityApp =(SecIdentityRef)CFDictionaryGetValue(identityDict,kSecImportItemIdentity);\r\n\r\n SecCertificateRef certRef;\r\n SecIdentityCopyCertificate(identityApp, &amp;certRef);\r\n\r\n SecCertificateRef certArray&#x5B;1] = { certRef };\r\n CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);\r\n CFRelease(certRef);\r\n\r\n NSURLCredential *credential = &#x5B;NSURLCredential credentialWithIdentity:identityApp certificates:(__bridge NSArray *)myCerts persistence:NSURLCredentialPersistencePermanent];\r\n CFRelease(myCerts);\r\n\r\n &#x5B;&#x5B;challenge sender] useCredential:credential forAuthenticationChallenge:challenge];\r\n }\r\n else {\r\n \/\/ Certificate is invalid or password is invalid given the certificate\r\n NSLog(@&quot;Invalid certificate or password&quot;);\r\n return;\r\n }\r\n } else {\r\n \/\/ For normal authentication based on username and password. This could be NTLM or Default.\r\n if (&#x5B;&#x5B;challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {\r\n NSLog(@&quot;Certificate might be required&quot;);\r\n }\r\n NSURLCredential *credential = &#x5B;NSURLCredential credentialWithUser:self.account.username\r\n password:self.account.password\r\n persistence:NSURLCredentialPersistenceNone];\r\n &#x5B;&#x5B;challenge sender] useCredential:credential forAuthenticationChallenge:challenge];\r\n }\r\n\r\n}\r\n\r\n<\/pre>\n<p>All looks good, doesn&#8217;t it? And in most cases &#8211; it will even work fine. Unless your server handles the handshake, something goes wrong and you look at what happens on the wire:<\/p>\n<p><a href=\"http:\/\/oso.com.pl\/wp-content\/uploads\/2013\/05\/ios_certs.png\"><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"217\" data-permalink=\"https:\/\/oso.com.pl\/?attachment_id=217\" data-orig-file=\"https:\/\/oso.com.pl\/wp-content\/uploads\/2013\/05\/ios_certs.png\" data-orig-size=\"1549,754\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}\" data-image-title=\"iOS certs\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/oso.com.pl\/wp-content\/uploads\/2013\/05\/ios_certs-1024x498.png\" class=\"wp-image-217 alignnone\" alt=\"iOS certs\" src=\"http:\/\/oso.com.pl\/wp-content\/uploads\/2013\/05\/ios_certs.png\" width=\"1084\" height=\"528\" srcset=\"https:\/\/oso.com.pl\/wp-content\/uploads\/2013\/05\/ios_certs.png 1549w, https:\/\/oso.com.pl\/wp-content\/uploads\/2013\/05\/ios_certs-300x146.png 300w, https:\/\/oso.com.pl\/wp-content\/uploads\/2013\/05\/ios_certs-1024x498.png 1024w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/a><\/p>\n<p>Can you see what happens here? The client certificate is sent twice. That is not always an issue, but if your server is parsing the certificates, and expects them to be in order, and expects that the first one is the client certificate and what follows is the issuer&#8230; you are in trouble.<\/p>\n<p>You may argue whether this is a client bug or the server issue&#8230; I would say the client should be fixed. Per\u00c2\u00a0<a href=\"http:\/\/www.ietf.org\/rfc\/rfc2246.txt\" target=\"_blank\">RFC2246<\/a> and <a href=\"http:\/\/tools.ietf.org\/html\/rfc5246\" target=\"_blank\">RFC5246<\/a> the certificate structure should contain sender&#8217;s cert once.<\/p>\n<p><em id=\"__mceDel\">certificate_list<br \/>\nThis is a sequence (chain) of X.509v3 certificates. The sender&#8217;s<br \/>\ncertificate must come first in the list. Each following<br \/>\ncertificate must directly certify the one preceding it. Because<br \/>\ncertificate validation requires that root keys be distributed<br \/>\nindependently, the self-signed certificate which specifies the<br \/>\nroot certificate authority may optionally be omitted from the<br \/>\nchain, under the assumption that the remote end must already<br \/>\npossess it in order to validate it in any case.<br \/>\nThe same message type and structure will be used for the client&#8217;s<br \/>\nresponse to a certificate request message. Note that a client may<br \/>\nsend no certificates if it does not have an appropriate certificate<br \/>\nto send in response to the server&#8217;s authentication request.<\/em><\/p>\n<p>So how do we fix it? Fortunately the fix is trivial. Change this line:<\/p>\n<p>NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp <strong>certificates:(__bridge NSArray *)myCerts<\/strong> persistence:NSURLCredentialPersistencePermanent];<\/p>\n<p>to this:<\/p>\n<p>NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp <strong>certificates:nil<\/strong> persistence:NSURLCredentialPersistencePermanent];<\/p>\n<p>And everybody is happy again. The app sends a single certificate only, no change on the server required.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Here&#8217;s another one on certificates. Let&#8217;s say you are trying to do client certificate-based authentication in your iOS app and you came up with something like this: &#8211; (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if (&#x5B;challenge previousFailureCount] &gt; 0) { NSLog(@&quot;Incorrect auth challenge %@&quot;, challenge); &#x5B;&#x5B;challenge sender] cancelAuthenticationChallenge:challenge]; return; } \/\/ Checking the server certificate if &hellip; <a href=\"https:\/\/oso.com.pl\/?p=207&#038;lang=en\" class=\"more-link\">Czytaj dalej<span class=\"screen-reader-text\"> \u201eThe curious case of duplicated certificates sent by iOS\u201d<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[13],"tags":[],"class_list":["post-207","post","type-post","status-publish","format-standard","hentry","category-mdm-en"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p217OK-3l","jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/oso.com.pl\/index.php?rest_route=\/wp\/v2\/posts\/207","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/oso.com.pl\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/oso.com.pl\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/oso.com.pl\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/oso.com.pl\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=207"}],"version-history":[{"count":16,"href":"https:\/\/oso.com.pl\/index.php?rest_route=\/wp\/v2\/posts\/207\/revisions"}],"predecessor-version":[{"id":224,"href":"https:\/\/oso.com.pl\/index.php?rest_route=\/wp\/v2\/posts\/207\/revisions\/224"}],"wp:attachment":[{"href":"https:\/\/oso.com.pl\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=207"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oso.com.pl\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=207"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oso.com.pl\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=207"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}