Dynamic SSL Pinning
for iOS & macOS

Swift SDK for SSL/TLS certificate pinning using a remote, cryptographically signed key registry — enabling pin rotation without releasing a new app version.

Swift Package iOS 15+ macOS 12+ Swift 6.1+ BSD-3-Clause

Sample App

The repository includes a SwiftUI sample app demonstrating SDK usage — toggle between pinned and plain sessions, send requests, and inspect response headers in a debug log.

Sample App screenshot
1

Copy the config template

cp App/Config.xcconfig.example App/Config.xcconfig
2

Fill in your values

SLASH                    = /
SSL_PINNING_ENDPOINT     = https:$(SLASH)$(SLASH)ssl-pinning.example.com/api/v1/google.com.json
SSL_PINNING_SIGNING_KEY_B64 = <base64 of pub.pem>
Note: $(SLASH) is required because // is treated as a comment in xcconfig files.
3

Open and run

Open SslPinning.xcworkspace in Xcode, select the App scheme, and run on a simulator or device.

Installation

1

Add via Xcode

File → Add Package Dependencies and enter the repository URL, or add to your Package.swift:

dependencies: [
    .package(url: "https://github.com/ssl-pinning/apple-sdk.git", from: "1.1.0"),
],
targets: [
    .target(name: "YourTarget", dependencies: ["SslPinning"]),
]
2

Get the public signing key

Obtain the base64-encoded RSA public key from the server operator and store it in your app's configuration. For the sample app, put it in App/Config.xcconfig.

# encode pub.pem to a single-line base64 string
base64 -i pub.pem | tr -d '\n'
Note: pass the output directly as signingKeyBase64. The SDK accepts PEM text, base64(PEM), or raw DER (SPKI).
3

Initialize the SDK

import SslPinning

let config = SslPinningConfig(
    endpointUrl:    "https://ssl-pinning.example.com/api/v1/google.com.json",
    signingKeyBase64: "<base64 of pub.pem>"
)

let result = await SslPinningClient.initialize(config: config)

switch result {
case .success(let client):
    let pinnedSession = client.createPinnedSession() // use for all HTTPS requests
    let plainSession  = client.createPlainSession()  // use only for non-pinned requests
case .failure(let error):
    // initialization failed — do not proceed with network requests
}
async/await: initialize() is an async function — call it from a Task or .task view modifier. Create the sessions once and reuse them for the lifetime of the session.

How It Works

Initialization is a single async call that fetches, verifies, and pins in one step.

#Step
1Fetch the signed key registry from endpointUrl via HTTP GET
2Verify the response signature using JCS canonicalization + RSA PKCS#1 v1.5 + SHA-512
3Each verified key is bound to its domainName as an SPKI SHA-256 pin
4A URLSession with a custom URLSessionDelegate is built for the pinned session
5Both sessions (pinned and plain) are returned to the caller
Fails closed — initialization returns .failure if the endpoint is unreachable, the response format is invalid, the cryptographic signature does not verify, or the key list is empty.

Key Registry Response Format

Your backend endpoint must return JSON in the following format. See ssl-pinning/ssl-pinning for the server implementation.

{
  "payload": {
    "keys": [
      {
        "domainName": "www.example.com",
        "key":        "base64-encoded-sha256-spki-hash",
        "fqdn":       "www.example.com",
        "expire":     5488607,
        "date":       "2025-12-14T21:02:11Z"
      }
    ]
  },
  "signature": "BASE64_ENCODED_RSA_SHA512_SIGNATURE"
}
FieldDescription
domainNameHostname pattern for SPKI pinning. Supports wildcards: *.example.com matches one subdomain level.
keyBase64-encoded SHA-256 hash of the certificate's Subject Public Key Info (SPKI).
fqdnOptional. The fully-qualified domain name the server dialed to extract the key.
expireOptional. Unix timestamp (seconds) after which the key should be considered expired.
signatureRSA PKCS#1 v1.5 + SHA-512 over the JCS-canonicalized JSON of the payload field.

Cryptography

PropertyValue
CanonicalizationJSON Canonicalization Scheme (JCS, RFC 8785) — implemented in pure Swift
Signature algorithmRSA PKCS#1 v1.5
Hash functionSHA-512 (signature) · SHA-256 (SPKI pin)
Public key formatPEM · base64(PEM) · DER/SPKI — RSA 1024 / 2048 / 4096 bit
Pin typeSPKI SHA-256 (leaf certificate only)
FrameworksSecurity.framework · CryptoKit · Foundation only — no third-party dependencies

Security Properties

Property
MITM protectionValidates leaf certificate SPKI hash against a verified remote registry
Custom CA injectionBlocked — pinning bypasses the system trust store for pinned domains
Key substitutionPrevented via RSA-SHA512 cryptographic signature on the registry payload
Key rotationRemote — no app update required; update the registry on the server
Binary hardcodingNone — no pins are embedded in the app binary
Wildcard matching*.example.com matches exactly one subdomain level, never the apex