Skip to content
Daniel edited this page Oct 8, 2018 · 24 revisions

Contents

Primer

A JSON Web Signature (JWS) represents content secured with digital signatures. It provides integrity protection for this content. A JWS consists of:

  1. A JSON-based header
  2. Some arbitrary payload
  3. A signature over this header and payload

Header

The JWS Header is a JSON object specifying the algorithm used to compute the signature. Optionally it can contain additional properties of the JWS.

Example

The following header specifies that the JWS' signature is computed using the RSASSA-PKCS1-v1_5 using SHA-512 algorithm.

{ "alg": "RS512" }

A detailed list describing possible header parameters can be found here.

Payload

The JWS Payload is the data being secured by the JWS.

Example

The following string, encoded as UTF-8 data, is an example of a JWS Payload.

"Trumpets of Mexico 🏜"

Signature

The JWS Signature is a digital signature over the JWS Header and JWS Payload. It’s computed using the algorithm specified in the JWS Header.

The input to the signing function (also called signing input) is the following concatenation:

ascii(base64URL(utf8(JWS Header)) + "." + base64url(payload))

Example

The following is the signing input for the header and payload described above:

eyAiYWxnIjogIlJTNTEyIiB9.VHJ1bXBldHMgb2YgTWV4aWNvIPCfj5w

Serialization

JOSESwift implements compact serialization for JWS. Compact serialization represents a JWS as the following concatenation:

base64url(header) + "." + base64url(payload) + "." + base64url(signature)

Example

Given the following header and payload:

// Header
{ "alg": "RS512" }

// Payload
"Trumpets of Mexico 🏜"

We get the following values, encoded as base64url strings:

// base64url(header)
"eyAiYWxnIjogIlJTNTEyIiB9"

// base64url(payload)
"VHJ1bXBldHMgb2YgTWV4aWNvIPCfj5w"

// base64url(signature)
"TwJS6...YvlTQ"

Which yields the following compact serialization:

"""
eyAiYWxnIjogIlJTNTEyIiB9.VHJ1bXBldHMgb2YgTWV4aWNvIPCfj5w.TwJS6...YvlTQ
"""

Usage

This section describes how the above concepts are implemented and used in JOSESwift.

A JWS in JOSESwift is an immutable struct:

struct JWS {
    let header: JWSHeader
    let payload: Payload
    let signature: Data
}

In JOSESwift you can construct a new JWS from some arbitrary payload or parse an existing compact serialized JWS to extract its payload.

Constructing a New JWS

You need three parts to construct a new JWS instance in JOSESwift.

  1. A JWSHeader instance
  2. A Payload instance
  3. A Signer instance which computes the signature

These three parts are described in more detail in the following sections.

Header

A JWSHeader is a struct, holding a dictionary of parameters:

struct JWSHeader {
    let parameters: [String: Any]
}

For convenient use, a JWSHeader has an initializer that lets you provide the value of the algorithm parameter:

init(algorithm: SignatureAlgorithm)

To instantiate a JWSHeader you specify the signing algorithm that it should carry:

let header = JWSHeader(algorithm: .RS512)

Optionally you can set addtitional parameters:

header.kid = "2018-10-08"

header.typ = "JWS"

Payload

Payload has an initializer that lets you specify the data that your JWS should represent:

init(_ payload: Data)

To instantiate Payload you provide it with the data that it should carry:

let message = "Trumpets of Mexico 🏜"

let data = message.data(using: .utf8)!

let payload = Payload(data)

Signer

A Signer handles all cryptographic functionality needed to compute a JWS Signature. It computes the signing input from the header and payload. It then passes this signing input to the respective libraries providing the cryptographic primitives.

Signer provides an initializer which lets you specify the desired signing algorithm and your private key:

init?(signingAlgorithm: SignatureAlgorithm, privateKey: KeyType)

Note that the KeyType depends on the underlying crypto implementation. On iOS it’s SecKey per default. The initializer will return nil if you specify the wrong key type.

Make sure to check that you get a valid Signer instance after calling the initializer:

let privateKey: SecKey = /* ... */

guard let signer = Signer(signingAlgorithm: .RS512, privateKey: privateKey) else {
    // Wrong key type.
}

// You have a valid signer.

As a framework user, you don’t need to worry about calling the signer’s sign function. It happens automatically during the initialization of a JWS.

Initialization

Once you have created the header, payload, and signer, you can construct a JWS using the following initializer:

init<KeyType>(header: JWSHeader, payload: Payload, signer: Signer<KeyType>) throws

Make sure to check if any errors occur during the initialization:

do {
    let jws = try JWS(header: header, payload: payload, signer: signer)
} catch {
    // Signing went wrong.
}

Serialization

Now that you have an initialized JWS, you can get its compact serialization using the following properties:

jws.compactSerializedString

// or

jws.compactSerializedData

Parsing an Existing Serialization

If you have an existing JWS in compact serialization, you can construct a full JWS instance from it. You can then verify the signature and extract the payload it carries.

Initialization

Use one of the following initializers to construct a JWS from a compact serialization:

init(compactSerialization: String) throws

// or

init(compactSerialization: Data) throws

Make sure to check if any errors occur during the initialization:

do {
    let jws = try JWS(compactSerialization: serialization)
} catch {
    // Something went wrong.
}

Validation

Before extracting the payload it’s a good idea to check the JWS’ signature. That way you can verify the payload’s integrity.

A JWS provides two functions to verify its signature. Note that the KeyType depends on the underlying crypto implementation. On iOS it’s SecKey per default.

// Option 1
func isValid<KeyType>(for publicKey: KeyType) -> Bool

// and

// Option 2
func validate<KeyType>(with publicKey: KeyType) throws -> JWS

It is sufficient to use only one of those functions for validations.

Option 1 is good if you only care about the correctness of the signature. It does not give you any hints towards the cause of an invalid signature:

let publicKey: SecKey = /* ... */

guard jws.isValid(for: publicKey) else {
    // Either an error occurred or the signature is invalid.
}

// The signature is valid!

Option 2 lets you differentiate between a general error or an invalid signature.

let publicKey: SecKey = /* ... */

do {
    _ = try jws.validate(with: publicKey)
} catch SwiftJOSEError.signatureInvalid {
    // The signature is invalid!
} catch {
    // An error occurred during validation!
}

// The signature is valid!

Note that the KeyType depends on the underlying crypto implementation. On iOS it’s SecKey per default. Validation will fail if you specify the wrong key type.

Extracting the Payload

You have now validated the payload’s integrity by checking the signature. Extract the payload by calling:

let payload = jws.payload

let data = payload.data()

let message = String(data: data, encoding: .utf8)!

print(message) // "Trumpets of Mexico 🏜"

Pro Tip: A handy shortcut for signature validation and extracting the payload is:

guard let payload = try? jws.validate(with: publicKey).payload else {
    // Something went wrong!
}

let message = String(data: payload.data(), encoding: .utf8)!

print(message) // "Trumpets of Mexico 🏜"