Skip to content

hellocoop/swift-httpsig

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

swift-httpsig

Swift implementation of RFC 9421 HTTP Message Signatures with the Signature-Key header extension.

Requirements

  • iOS 17.4+ / macOS 14+
  • Swift 5.9+
  • No external dependencies (uses CryptoKit and Security frameworks)

Installation

Add to your Package.swift:

dependencies: [
    .package(url: "https://github.com/hellocoop/swift-httpsig.git", from: "0.1.0"),
],
targets: [
    .target(
        name: "YourTarget",
        dependencies: [
            .product(name: "HTTPMessageSignatures", package: "swift-httpsig"),
        ]
    ),
]

Usage

Signing a Request

import HTTPMessageSignatures

// Create a signing key (Secure Enclave on device, CryptoKit for testing)
let key = CryptoKitP256SigningKey()

// Create a signer with default components
let signer = HTTPMessageSigner(
    key: key,
    label: "sig",
    components: ["@method", "@authority", "@path", "signature-key"]
)

// Sign a request (adds Signature-Input, Signature, and Signature-Key headers)
var request = URLRequest(url: URL(string: "https://wallet.hello.coop/api/v1/mobile/register")!)
request.httpMethod = "POST"
let signedRequest = try signer.sign(request)

Important: @authority Component

The @authority component in the signature base is derived from the request URL. When verifying on the server, do not use the Host header from the incoming request — reverse proxies (nginx, ALB, CloudFront) commonly rewrite the Host header to the internal upstream hostname.

Instead, use a server-side environment constant for the expected authority:

// Node.js server — CORRECT
const AUTHORITY = `${HOST}.${DOMAIN}` // e.g. "wallet.hello.coop"

// In signature base construction:
case '@authority':
    value = AUTHORITY  // NOT req.headers.host
// Swift client — the URL already contains the correct authority
// No special handling needed; URLRequest.url.host() is used automatically

Verifying a Request

let result = try HTTPMessageVerifier.verify(request: signedRequest)
// result.jwk - the public key that verified the signature
// result.parameters.created - when the signature was created
// result.components - which components were covered

Secure Enclave Key (iOS)

let key = try SecureEnclaveSigningKey()

// Persist the key handle for later use
let keyData = key.dataRepresentation
UserDefaults.standard.set(keyData, forKey: "deviceKey")

// Restore later
let restored = try SecureEnclaveSigningKey(dataRepresentation: keyData)

JWK Thumbprint

let thumbprint = try JWKThumbprint.compute(key.publicKeyJWK)

Signature-Key Schemes

Scheme Format Use Case
hwk Inline JWK parameters Device-bound keys
jwt JWT with cnf.jwk Delegated/attested keys
jwks_uri JWKS discovery URI Server keys

License

MIT

About

Swift implementation of HTTP Message Signatures (RFC 9421) with Signature-Key header (draft-hardt-httpbis-signature-key)

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors