-
Notifications
You must be signed in to change notification settings - Fork 113
A JSON Web Signature (JWS) represents content secured with digital signatures. It provides integrity protection for this content. A JWS consists of:
- A JSON-based header
- Some arbitrary payload
- A signature over this header and payload
The JWS Header is a JSON object specifying the algorithm used to compute the signature. Optionally it can contain additional properties of the JWS.
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.
The JWS Payload is the data being secured by the JWS.
The following string, encoded as UTF-8 data, is an example of a JWS Payload.
"Trumpets of Mexico 🏜"
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))
The following is the signing input for the header and payload described above:
eyAiYWxnIjogIlJTNTEyIiB9.VHJ1bXBldHMgb2YgTWV4aWNvIPCfj5w
JOSESwift implements compact serialization for JWS. Compact serialization represents a JWS as the following concatenation:
base64url(header) + "." + base64url(payload) + "." + base64url(signature)
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
"""
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.
You need three parts to construct a new JWS
instance in JOSESwift.
- A
JWSHeader
instance - A
Payload
instance - A
Signer
instance which computes the signature
These three parts are described in more detail in the following sections.
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 = "JWE"
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)
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.
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.
}
Now that you have an initialized JWS, you can get its compact serialization using the following properties:
jws.compactSerializedString
// or
jws.compactSerializedData
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.
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.
}
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.
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 🏜"