Bläddra i källkod

Updated certificatesInBundle to support `cer`, `crt` and `der` extensions.

In certificatesInBundle, check for well-formed certificates with extensiions .cer, .crt, .der, and check for upper and lower case extensions (OSX filesystem may or may not be case sensitive. iOS always is).

DER is a binary encoding format
PEM is an ASCII encoding format
.crt is the most common certificate extension, may be DER or PEM
.cer is a Microsoft extension convention, may be DER or PEM
.der is a (poor) extension referring to encoding instead of contents, these may contain a certificate and/or a key.
Jacob Jennings 10 år sedan
förälder
incheckning
234568d497

+ 36 - 0
Alamofire.xcodeproj/project.pbxproj

@@ -144,6 +144,13 @@
 		4DD67C241A5C58FB00ED2280 /* Alamofire.h in Headers */ = {isa = PBXBuildFile; fileRef = F8111E3819A95C8B0040E7D1 /* Alamofire.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		4DD67C241A5C58FB00ED2280 /* Alamofire.h in Headers */ = {isa = PBXBuildFile; fileRef = F8111E3819A95C8B0040E7D1 /* Alamofire.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		4DD67C251A5C590000ED2280 /* Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897FF4019AA800700AB5182 /* Alamofire.swift */; };
 		4DD67C251A5C590000ED2280 /* Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897FF4019AA800700AB5182 /* Alamofire.swift */; };
 		8035DB621BAB492500466CB3 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8111E3319A95C8B0040E7D1 /* Alamofire.framework */; };
 		8035DB621BAB492500466CB3 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8111E3319A95C8B0040E7D1 /* Alamofire.framework */; };
+		B39E2F951C1A744F002DA1A9 /* certDER.cer in Resources */ = {isa = PBXBuildFile; fileRef = B39E2F831C1A72F8002DA1A9 /* certDER.cer */; };
+		B39E2F961C1A744F002DA1A9 /* certDER.crt in Resources */ = {isa = PBXBuildFile; fileRef = B39E2F841C1A72F8002DA1A9 /* certDER.crt */; };
+		B39E2F971C1A744F002DA1A9 /* certDER.der in Resources */ = {isa = PBXBuildFile; fileRef = B39E2F851C1A72F8002DA1A9 /* certDER.der */; };
+		B39E2F981C1A744F002DA1A9 /* certPEM.cer in Resources */ = {isa = PBXBuildFile; fileRef = B39E2F861C1A72F8002DA1A9 /* certPEM.cer */; };
+		B39E2F991C1A744F002DA1A9 /* certPEM.crt in Resources */ = {isa = PBXBuildFile; fileRef = B39E2F871C1A72F8002DA1A9 /* certPEM.crt */; };
+		B39E2F9B1C1A744F002DA1A9 /* randomGibberish.crt in Resources */ = {isa = PBXBuildFile; fileRef = B39E2F891C1A72F8002DA1A9 /* randomGibberish.crt */; };
+		B39E2F9C1C1A744F002DA1A9 /* keyDER.der in Resources */ = {isa = PBXBuildFile; fileRef = B39E2F8A1C1A72F8002DA1A9 /* keyDER.der */; };
 		E4202FCF1B667AA100C997FB /* Upload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDE2C3F1AF89E0700BABAE5 /* Upload.swift */; };
 		E4202FCF1B667AA100C997FB /* Upload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDE2C3F1AF89E0700BABAE5 /* Upload.swift */; };
 		E4202FD01B667AA100C997FB /* ParameterEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE2724E1AF88FB500F1D59A /* ParameterEncoding.swift */; };
 		E4202FD01B667AA100C997FB /* ParameterEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE2724E1AF88FB500F1D59A /* ParameterEncoding.swift */; };
 		E4202FD11B667AA100C997FB /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDE2C391AF899EC00BABAE5 /* Request.swift */; };
 		E4202FD11B667AA100C997FB /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDE2C391AF899EC00BABAE5 /* Request.swift */; };
@@ -246,6 +253,13 @@
 		4CF626F81BA7CB3E0011A099 /* Alamofire tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Alamofire tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		4CF626F81BA7CB3E0011A099 /* Alamofire tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Alamofire tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		4DD67C0B1A5C55C900ED2280 /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		4DD67C0B1A5C55C900ED2280 /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		72998D721BF26173006D3F69 /* Info-tvOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = "<group>"; };
 		72998D721BF26173006D3F69 /* Info-tvOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = "<group>"; };
+		B39E2F831C1A72F8002DA1A9 /* certDER.cer */ = {isa = PBXFileReference; lastKnownFileType = file; name = certDER.cer; path = selfSignedAndMalformedCerts/certDER.cer; sourceTree = "<group>"; };
+		B39E2F841C1A72F8002DA1A9 /* certDER.crt */ = {isa = PBXFileReference; lastKnownFileType = file; name = certDER.crt; path = selfSignedAndMalformedCerts/certDER.crt; sourceTree = "<group>"; };
+		B39E2F851C1A72F8002DA1A9 /* certDER.der */ = {isa = PBXFileReference; lastKnownFileType = file; name = certDER.der; path = selfSignedAndMalformedCerts/certDER.der; sourceTree = "<group>"; };
+		B39E2F861C1A72F8002DA1A9 /* certPEM.cer */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = certPEM.cer; path = selfSignedAndMalformedCerts/certPEM.cer; sourceTree = "<group>"; };
+		B39E2F871C1A72F8002DA1A9 /* certPEM.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = certPEM.crt; path = selfSignedAndMalformedCerts/certPEM.crt; sourceTree = "<group>"; };
+		B39E2F891C1A72F8002DA1A9 /* randomGibberish.crt */ = {isa = PBXFileReference; lastKnownFileType = file; name = randomGibberish.crt; path = selfSignedAndMalformedCerts/randomGibberish.crt; sourceTree = "<group>"; };
+		B39E2F8A1C1A72F8002DA1A9 /* keyDER.der */ = {isa = PBXFileReference; lastKnownFileType = file; name = keyDER.der; path = selfSignedAndMalformedCerts/keyDER.der; sourceTree = "<group>"; };
 		E4202FE01B667AA100C997FB /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		E4202FE01B667AA100C997FB /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		F8111E3319A95C8B0040E7D1 /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		F8111E3319A95C8B0040E7D1 /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		F8111E3719A95C8B0040E7D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		F8111E3719A95C8B0040E7D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -362,6 +376,7 @@
 		4C33A1171B5207DB00873DFF /* Certificates */ = {
 		4C33A1171B5207DB00873DFF /* Certificates */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				B39E2F821C1A72E5002DA1A9 /* Varying Encoding Types and Extensions */,
 				4C812C391B535F060017E0BF /* alamofire.org */,
 				4C812C391B535F060017E0BF /* alamofire.org */,
 				4C812C381B535F000017E0BF /* disig.sk */,
 				4C812C381B535F000017E0BF /* disig.sk */,
 			);
 			);
@@ -484,6 +499,20 @@
 			name = Features;
 			name = Features;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
+		B39E2F821C1A72E5002DA1A9 /* Varying Encoding Types and Extensions */ = {
+			isa = PBXGroup;
+			children = (
+				B39E2F831C1A72F8002DA1A9 /* certDER.cer */,
+				B39E2F841C1A72F8002DA1A9 /* certDER.crt */,
+				B39E2F851C1A72F8002DA1A9 /* certDER.der */,
+				B39E2F861C1A72F8002DA1A9 /* certPEM.cer */,
+				B39E2F871C1A72F8002DA1A9 /* certPEM.crt */,
+				B39E2F891C1A72F8002DA1A9 /* randomGibberish.crt */,
+				B39E2F8A1C1A72F8002DA1A9 /* keyDER.der */,
+			);
+			name = "Varying Encoding Types and Extensions";
+			sourceTree = "<group>";
+		};
 		F8111E2919A95C8B0040E7D1 = {
 		F8111E2919A95C8B0040E7D1 = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -825,21 +854,28 @@
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
 				4C812C3B1B535F220017E0BF /* alamofire-root-ca.cer in Resources */,
 				4C812C3B1B535F220017E0BF /* alamofire-root-ca.cer in Resources */,
+				B39E2F971C1A744F002DA1A9 /* certDER.der in Resources */,
 				4C812C4B1B535F400017E0BF /* signed-by-ca1.cer in Resources */,
 				4C812C4B1B535F400017E0BF /* signed-by-ca1.cer in Resources */,
 				4C812C541B535F540017E0BF /* expired.cer in Resources */,
 				4C812C541B535F540017E0BF /* expired.cer in Resources */,
 				4C812C491B535F400017E0BF /* multiple-dns-names.cer in Resources */,
 				4C812C491B535F400017E0BF /* multiple-dns-names.cer in Resources */,
 				4C812C4D1B535F400017E0BF /* test.alamofire.org.cer in Resources */,
 				4C812C4D1B535F400017E0BF /* test.alamofire.org.cer in Resources */,
+				B39E2F981C1A744F002DA1A9 /* certPEM.cer in Resources */,
 				4C812C411B535F2E0017E0BF /* alamofire-signing-ca2.cer in Resources */,
 				4C812C411B535F2E0017E0BF /* alamofire-signing-ca2.cer in Resources */,
+				B39E2F961C1A744F002DA1A9 /* certDER.crt in Resources */,
 				4C33A13B1B5207DB00873DFF /* unicorn.png in Resources */,
 				4C33A13B1B5207DB00873DFF /* unicorn.png in Resources */,
 				4C812C561B535F540017E0BF /* missing-dns-name-and-uri.cer in Resources */,
 				4C812C561B535F540017E0BF /* missing-dns-name-and-uri.cer in Resources */,
 				4C812C581B535F540017E0BF /* signed-by-ca2.cer in Resources */,
 				4C812C581B535F540017E0BF /* signed-by-ca2.cer in Resources */,
 				4C812C651B535F6D0017E0BF /* testssl-expire.disig.sk.cer in Resources */,
 				4C812C651B535F6D0017E0BF /* testssl-expire.disig.sk.cer in Resources */,
+				B39E2F951C1A744F002DA1A9 /* certDER.cer in Resources */,
 				4C812C611B535F6D0017E0BF /* intermediate-ca-disig.cer in Resources */,
 				4C812C611B535F6D0017E0BF /* intermediate-ca-disig.cer in Resources */,
 				4C33A1391B5207DB00873DFF /* rainbow.jpg in Resources */,
 				4C33A1391B5207DB00873DFF /* rainbow.jpg in Resources */,
 				4C812C631B535F6D0017E0BF /* root-ca-disig.cer in Resources */,
 				4C812C631B535F6D0017E0BF /* root-ca-disig.cer in Resources */,
 				4C812C5C1B535F540017E0BF /* valid-uri.cer in Resources */,
 				4C812C5C1B535F540017E0BF /* valid-uri.cer in Resources */,
+				B39E2F9B1C1A744F002DA1A9 /* randomGibberish.crt in Resources */,
 				4C812C3F1B535F2E0017E0BF /* alamofire-signing-ca1.cer in Resources */,
 				4C812C3F1B535F2E0017E0BF /* alamofire-signing-ca1.cer in Resources */,
+				B39E2F9C1C1A744F002DA1A9 /* keyDER.der in Resources */,
 				4C812C5A1B535F540017E0BF /* valid-dns-name.cer in Resources */,
 				4C812C5A1B535F540017E0BF /* valid-dns-name.cer in Resources */,
+				B39E2F991C1A744F002DA1A9 /* certPEM.crt in Resources */,
 				4C812C471B535F400017E0BF /* wildcard.alamofire.org.cer in Resources */,
 				4C812C471B535F400017E0BF /* wildcard.alamofire.org.cer in Resources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;

+ 5 - 1
Source/ServerTrustPolicy.swift

@@ -128,7 +128,11 @@ public enum ServerTrustPolicy {
     public static func certificatesInBundle(bundle: NSBundle = NSBundle.mainBundle()) -> [SecCertificate] {
     public static func certificatesInBundle(bundle: NSBundle = NSBundle.mainBundle()) -> [SecCertificate] {
         var certificates: [SecCertificate] = []
         var certificates: [SecCertificate] = []
 
 
-        for path in bundle.pathsForResourcesOfType(".cer", inDirectory: nil) {
+        let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
+            bundle.pathsForResourcesOfType(fileExtension, inDirectory: nil)
+        }.flatten())
+
+        for path in paths {
             if let
             if let
                 certificateData = NSData(contentsOfFile: path),
                 certificateData = NSData(contentsOfFile: path),
                 certificate = SecCertificateCreateWithData(nil, certificateData)
                 certificate = SecCertificateCreateWithData(nil, certificateData)

BIN
Tests/Resources/Certificates/selfSignedAndMalformedCerts/certDER.cer


BIN
Tests/Resources/Certificates/selfSignedAndMalformedCerts/certDER.crt


BIN
Tests/Resources/Certificates/selfSignedAndMalformedCerts/certDER.der


+ 24 - 0
Tests/Resources/Certificates/selfSignedAndMalformedCerts/certPEM.cer

@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID9DCCAtygAwIBAgIJAIqBVOBRW4qMMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTERMA8GA1UEBxMIQmVya2VsZXkxKjAoBgNVBAoT
+IVRlc3RpbmcgQ2VydGlmaWNhdGVzIGluIEFsYW1vRmlyZTAeFw0xNTEyMTEwMjA5
+MDlaFw0xNjEyMTAwMjA5MDlaMFkxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTER
+MA8GA1UEBxMIQmVya2VsZXkxKjAoBgNVBAoTIVRlc3RpbmcgQ2VydGlmaWNhdGVz
+IGluIEFsYW1vRmlyZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJy9
+FTR4QzhYyo2v2yv8SwlBQ32MHQF8Ez1J+YBsyZjcTVOGJtyPxrbJxGuRhyDzKUqz
+X/zTsT+JPvZQXXBmyq0l0DhCcK84cHyVLcdEAzukam85EpJRWzSg8kDKzuTx2oLk
+X8Zdcj7EEtYWV/5+/YahM4tXYhg+Lqm6koJEVHMld6zfedC7HN+jsTb73IrAY0dd
+BPl7WPgDIPEl0kcGI6F7NS0+CKsgX412E5+TGUkvA8VI+e9+cn/EXBdklVQQGSOu
+rHpcoOi1VqnQI0hGXlFi4MpamwMG2yArIUU0TXZ7G+/AbUYiGdB6ogvg5UTCfyZy
+UXVljSJyzYmLs7hXQK8CAwEAAaOBvjCBuzAdBgNVHQ4EFgQU9EaWHrJGYvpCEW5f
+CUEMRk9DlN8wgYsGA1UdIwSBgzCBgIAU9EaWHrJGYvpCEW5fCUEMRk9DlN+hXaRb
+MFkxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTERMA8GA1UEBxMIQmVya2VsZXkx
+KjAoBgNVBAoTIVRlc3RpbmcgQ2VydGlmaWNhdGVzIGluIEFsYW1vRmlyZYIJAIqB
+VOBRW4qMMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEeHeXNZGHJW
+VImbOHrYmSsZ5jFnzjGw8ynkOrcoJzaxg3OHoo/pNCQ7KcrIa5YPNFiNoaSa/Lzn
+LBt6HkM1Vi1rMaERHLWp/W5ruInCu4CuVtQshdNcOEofJ03wdrQylOBZq8MZkTVn
+NcqUFg/sBANM/9WhafVi7XaUjWl+V7ZnzdbKP/ywvsiJ+wyKMA7Wt19HMrV2dTBz
+CD4vxpwOBev0oTp2NvAHdgNkeK52skHoz+MY8uivVJQr4hqLYJPXUyAcVZCaqeK/
+hxDkbRo6eZsYcjTRqMKtGMVjHHd8alXcVJwcuWkhUYxy8jRf0kFj/9mMie9jRokJ
+ovKLbNJfEbI=
+-----END CERTIFICATE-----

+ 24 - 0
Tests/Resources/Certificates/selfSignedAndMalformedCerts/certPEM.crt

@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID9DCCAtygAwIBAgIJAIqBVOBRW4qMMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTERMA8GA1UEBxMIQmVya2VsZXkxKjAoBgNVBAoT
+IVRlc3RpbmcgQ2VydGlmaWNhdGVzIGluIEFsYW1vRmlyZTAeFw0xNTEyMTEwMjA5
+MDlaFw0xNjEyMTAwMjA5MDlaMFkxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTER
+MA8GA1UEBxMIQmVya2VsZXkxKjAoBgNVBAoTIVRlc3RpbmcgQ2VydGlmaWNhdGVz
+IGluIEFsYW1vRmlyZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJy9
+FTR4QzhYyo2v2yv8SwlBQ32MHQF8Ez1J+YBsyZjcTVOGJtyPxrbJxGuRhyDzKUqz
+X/zTsT+JPvZQXXBmyq0l0DhCcK84cHyVLcdEAzukam85EpJRWzSg8kDKzuTx2oLk
+X8Zdcj7EEtYWV/5+/YahM4tXYhg+Lqm6koJEVHMld6zfedC7HN+jsTb73IrAY0dd
+BPl7WPgDIPEl0kcGI6F7NS0+CKsgX412E5+TGUkvA8VI+e9+cn/EXBdklVQQGSOu
+rHpcoOi1VqnQI0hGXlFi4MpamwMG2yArIUU0TXZ7G+/AbUYiGdB6ogvg5UTCfyZy
+UXVljSJyzYmLs7hXQK8CAwEAAaOBvjCBuzAdBgNVHQ4EFgQU9EaWHrJGYvpCEW5f
+CUEMRk9DlN8wgYsGA1UdIwSBgzCBgIAU9EaWHrJGYvpCEW5fCUEMRk9DlN+hXaRb
+MFkxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTERMA8GA1UEBxMIQmVya2VsZXkx
+KjAoBgNVBAoTIVRlc3RpbmcgQ2VydGlmaWNhdGVzIGluIEFsYW1vRmlyZYIJAIqB
+VOBRW4qMMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEeHeXNZGHJW
+VImbOHrYmSsZ5jFnzjGw8ynkOrcoJzaxg3OHoo/pNCQ7KcrIa5YPNFiNoaSa/Lzn
+LBt6HkM1Vi1rMaERHLWp/W5ruInCu4CuVtQshdNcOEofJ03wdrQylOBZq8MZkTVn
+NcqUFg/sBANM/9WhafVi7XaUjWl+V7ZnzdbKP/ywvsiJ+wyKMA7Wt19HMrV2dTBz
+CD4vxpwOBev0oTp2NvAHdgNkeK52skHoz+MY8uivVJQr4hqLYJPXUyAcVZCaqeK/
+hxDkbRo6eZsYcjTRqMKtGMVjHHd8alXcVJwcuWkhUYxy8jRf0kFj/9mMie9jRokJ
+ovKLbNJfEbI=
+-----END CERTIFICATE-----

BIN
Tests/Resources/Certificates/selfSignedAndMalformedCerts/keyDER.der


BIN
Tests/Resources/Certificates/selfSignedAndMalformedCerts/randomGibberish.crt


+ 25 - 0
Tests/ServerTrustPolicyTests.swift

@@ -1386,3 +1386,28 @@ class ServerTrustPolicyCustomEvaluationTestCase: ServerTrustPolicyTestCase {
         XCTAssertFalse(serverTrustIsValid, "server trust should not pass evaluation")
         XCTAssertFalse(serverTrustIsValid, "server trust should not pass evaluation")
     }
     }
 }
 }
+
+// MARK: -
+
+class ServerTrustPolicyCertificatesInBundleTestCase: ServerTrustPolicyTestCase {
+    func testOnlyValidCertificatesAreDetected() {
+        // Given
+        // Files present in bundle in the form of type+encoding+extension [key|cert][DER|PEM].[cer|crt|der|key|pem]
+        // certDER.cer: DER-encoded well-formed certificate
+        // certDER.crt: DER-encoded well-formed certificate
+        // certDER.der: DER-encoded well-formed certificate
+        // certPEM.*: PEM-encoded well-formed certificates, expected to fail: Apple API only handles DER encoding
+        // devURandomGibberish.crt: Random data, should fail
+        // keyDER.der: DER-encoded key, not a certificate, should fail
+
+        // When
+        let certificates = ServerTrustPolicy.certificatesInBundle(
+            NSBundle(forClass: ServerTrustPolicyCertificatesInBundleTestCase.self)
+        )
+
+        // Then
+        // Expectation: 15 well-formed certificates in the test bundle outside the scope of this test + 3 valid
+        // certificates (certDER.*) = 18.
+        XCTAssertEqual(certificates.count, 18, "Expected 18 well-formed certificates")
+    }
+}