Skip to content

Feature/message signing #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion API-PROTECTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,33 @@ If it is not possible to download the correct certificate from the portal then i

> **IMPORTANT:** Apps built to run on the iOS simulator are not code signed and thus auto-registration does not work for them. In this case you can consider [forcing a device ID to pass](https://approov.io/docs/latest/approov-usage-documentation/#forcing-a-device-id-to-pass) to get a valid attestation.

## MESSAGE SIGNING
We provide [installation message signing](https://approov.io/docs/latest/approov-usage-documentation/#installation-message-signing) as an advanced option for situations where an additional level of integrity assurance is required. You should use this option if you would like to ensure strict message integrity between the client app and the backend API. The key pair for message signing is generated automatically when the SDK is first initialized. The public key is transmitted to the Approov servers to be included in Approov tokens in the `ipk` claim. The private key never leaves the device and is held in secure hardware (e.g. TEE/Secure Enclave) to prevent the key material from being stolen.

### Enabling Installation Message Signing

Installation message signing can be enabled by executing the following command:

```shell
approov policy -setInstallPubKey on
```

This causes the public key to be included in any Approov tokens in the `ipk` claim, the presence of which then indicates to the backend that it should expect a valid installation message signature and that this should be verified.

### Adding the Message Signature Automatically

If you are using the `ApproovService` networking stack, then Approov can automatically generate and add the message signature. You should use this method whenever possible. You enable this by making the following call once, after initialization:

```swift
ApproovService.setApproovInterceptorExtensions(
ApproovDefaultMessageSigning().setDefaultFactory(
ApproovDefaultMessageSigning.generateDefaultSignatureParametersFactory()))
```

With this interceptor extension in place the Approov networking interceptor computes the request message signature and adds it to the request as required when the app passes attestation.

You can see a [worked example](https://github.com/approov/quickstart-ios-swift-urlsession/blob/master/SHAPES-EXAMPLE.md#shapes-app-with-installation-message-signing) for the Shapes app.

## FURTHER OPTIONS
See [Exploring Other Approov Features](https://approov.io/docs/latest/approov-usage-documentation/#exploring-other-approov-features) for information about additional Approov features you may wish to try.

Expand All @@ -55,7 +82,7 @@ The default header name of `Approov-Token` can be changed as follows:
ApproovService.setApproovHeader(header: "Authorization", prefix: "Bearer ")
```

The first assignment changes is the new header name and the second a prefix to be added to the Approov token. This is primarily for integrations where the Approov Token JWT might need to be prefixed with `Bearer` and passed in the `Authorization` header.
The first parameter is the new header name and the second a prefix to be added to the Approov token. This is primarily for integrations where the Approov Token JWT might need to be prefixed with `Bearer` and passed in the `Authorization` header.

### Token Binding
If want to use [Token Binding](https://approov.io/docs/latest/approov-usage-documentation/#token-binding) then set the header holding the value to be used for binding as follows:
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,28 @@ Enter the repository`https://github.com/approov/approov-service-urlsession.git`

Once you click `Add Package` the last step will confirm the package product and target selection. The `approov-service-urlsession` is actually an open source wrapper layer that allows you to easily use Approov with `URLSession`. This has a further dependency to the closed source [Approov SDK](https://github.com/approov/approov-ios-sdk).

Alternatively, use `cocoapods` and add the package dependecies similar to how they are used in the example `shapes-app/Podfile` in this repository.
Alternatively, use `cocoapods` and add the package dependencies similar to how they are used in the example `shapes-app/Podfile` in this repository.

## USING APPROOV SERVICE
The `ApproovURLSession` class mimics the interface of the `URLSession` class provided by Apple but includes an additional Approov attestation calls. The simplest way to use the `ApproovURLSession` class is to find and replace all the `URLSession` construction calls with `ApproovURLSession`.

```swift
import ApproovURLSession

try! ApproovService.initialize("<enter-your-config-string-here>")
let session = ApproovURLSession(URLSessionConfiguration.default)
```

Additionally, the Approov SDK wrapper class, `ApproovService` needs to be initialized before using the `ApproovURLSession` object. The `<enter-your-config-string-here>` is a custom string that configures your Approov account access. This will have been provided in your Approov onboarding email (it will be something like `#123456#K/XPlLtfcwnWkzv99Wj5VmAxo4CrU267J1KlQyoz8Qo=`).

For API domains that are configured to be protected with an Approov token, this adds the `Approov-Token` header and pins the connection. This may also substitute header values when using secrets protection.
For API domains that are configured to be protected with an Approov token, this adds the `Approov-Token` header and pins the connection. This may also substitute header values and query parameters when using secrets protection.

## ERROR TYPES
The `ApproovService` functions may throw specific errors to provide additional information:

* `permanentError` might be due to a feature not enabled using the command line
* `rejectionError` an attestation has been rejected, the `ARC` and `rejectionReasons` may contain specific device information that would help troubleshooting
* `networkingError` generaly can be retried since it can be temporary network issue
* `networkingError` generally can be retried since it can be temporary network issue
* `pinningError` is a certificate error
* `configurationError` a configuration feature is disabled or wrongly configured (i.e. attempting to initialize with different config from a previous instantiation)
* `initializationFailure` the ApproovService failed to be initialized (subsequent network requests will not be performed)
Expand All @@ -49,7 +51,7 @@ Your Approov onboarding email should contain a link allowing you to access [Live
## NEXT STEPS
To actually protect your APIs and/or secrets there are some further steps. Approov provides two different options for protection:

* [API PROTECTION](https://github.com/approov/quickstart-ios-swift-urlsession/blob/master/API-PROTECTION.md): You should use this if you control the backend API(s) being protected and are able to modify them to ensure that a valid Approov token is being passed by the app. An [Approov Token](https://approov.io/docs/latest/approov-usage-documentation/#approov-tokens) is short lived crytographically signed JWT proving the authenticity of the call.
* [API PROTECTION](https://github.com/approov/quickstart-ios-swift-urlsession/blob/master/API-PROTECTION.md): You should use this if you control the backend API(s) being protected and are able to modify them to ensure that a valid Approov token is being passed by the app. An [Approov Token](https://approov.io/docs/latest/approov-usage-documentation/#approov-tokens) is short lived cryptographically signed JWT proving the authenticity of the call.

* [SECRETS PROTECTION](https://github.com/approov/quickstart-ios-swift-urlsession/blob/master/SECRETS-PROTECTION.md): This allows app secrets, including API keys for 3rd party services, to be protected so that they no longer need to be included in the released app code. These secrets are only made available to valid apps at runtime.

Expand Down
42 changes: 38 additions & 4 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,30 @@ If a method throws an `ApproovError.rejectionError`, then this indicates the pro
Initializes the Approov SDK and thus enables the Approov features. The `config` will have been provided in the initial onboarding or email or can be [obtained using the Approov CLI](https://approov.io/docs/latest/approov-usage-documentation/#getting-the-initial-sdk-configuration). This will generate an error if a second attempt is made at initialization with a different `config`.

```swift
public static func initialize(config: String) throws
public static func initialize(config: String, comment: String? = nil) throws
```

It is possible to pass an empty `config` string to indicate that no initialization is required. Only do this if you are also using a different Approov quickstart in your app (which will use the same underlying Approov SDK) and this will have been initialized first.

The optional `comment` parameter allows to provide further options to the initialization. Please refer to the [Approov SDK documentation](https://approov.io/docs/latest/approov-direct-sdk-integration/#sdk-initialization-options) for details.

## setApproovInterceptorExtensions
Sets the interceptor extensions callback handler. This facility supports message signing that is independent from the rest of the attestation flow. The default ApproovService layer issues no callbacks. Provide a non-null handler to add functionality to the attestation flow. The configuration used to control installation message signing is passed in the `callbacks` parameter. The behavior of the provided configuration must remain constant while in use by the ApproovService.

```swift
public static func setApproovInterceptorExtensions(_ callbacks: ApproovInterceptorExtensions?)
```

Provide an ApproovDefaultMessageSigning object instantiated as shown below to enable installation message signing:

```java
ApproovService.setApproovInterceptorExtensions(
new ApproovDefaultMessageSigning().setDefaultFactory(
ApproovDefaultMessageSigning.generateDefaultSignatureParametersFactory()));
```

Passing `nil` to this method will disable message signing.

## setProceedOnNetworkFail
If `proceedOnNetworkFail` is set to `true` then this indicates that the networking should proceed anyway if it is not possible to obtain an Approov token due to a networking failure. If this is called then the backend API can receive calls without the expected Approov token header being added, or without header substitutions being made. This should only ever be used if there is some particular reason, perhaps due to local network conditions, that you believe that traffic to the Approov cloud service will be particularly problematic.

Expand Down Expand Up @@ -134,13 +153,28 @@ public static func fetchToken(url: String) throws -> String
This throws `ApproovError` if there was a problem obtaining an Approov token. This may require network access so may take some time to complete, and should not be called from the UI thread.

## getMessageSignature
Gets the [message signature](https://approov.io/docs/latest/approov-usage-documentation/#message-signing) for the given `message`. This is returned as a base64 encoded signature. This feature uses an account specific message signing key that is transmitted to the SDK after a successful fetch if the facility is enabled for the account. Note that if the attestation failed then the signing key provided is actually random so that the signature will be incorrect. An Approov token should always be included in the message being signed and sent alongside this signature to prevent replay attacks.

**DEPRECATED**, replaced by `getAccountMessageSignature`.
```swift
public static func getMessageSignature(message: String) -> String?
```

This return `nil` if there was an error obtaining the signature.
## getAccountMessageSignature
Gets the [account message signature](https://approov.io/docs/latest/approov-usage-documentation/#account-message-signing) for the given `message`. This is returned as a base64 encoded signature. This feature uses an account specific message signing key that is transmitted to the SDK after a successful fetch if the facility is enabled for the account. Note that if the attestation failed then the signing key provided is actually random so that the signature will be incorrect. An Approov token should always be included in the message being signed and sent alongside this signature to prevent replay attacks.

```swift
public static func getAccountMessageSignature(message: String) -> String?
```

This returns `nil` if there was an error obtaining the signature.

## getInstallMessageSignature
Gets the [install message signature](https://approov.io/docs/latest/approov-usage-documentation/#installation-message-signing) for the given message. This is returned as the base64 encoding of the signature in ASN.1 DER format. This feature uses an app install specific message signing key that is generated the first time an app launches. This signing mechanism uses an ECC key pair where the private key is managed by the secure element or trusted execution environment of the device. An Approov token should always be included in the message being signed and sent alongside this signature to prevent replay attacks.

```swift
public static func getInstallMessageSignature(message: String) -> String?
```

This returns `nil` if there was an error obtaining the signature.

## fetchSecureString
Fetches a [secure string](https://approov.io/docs/latest/approov-usage-documentation/#secure-strings) with the given `key` if `newDef` is `nil`. Returns `nil` if the `key` secure string is not defined. If `newDef` is not `nil` then a secure string for the particular app instance may be defined. In this case the new value is returned as the secure string. Use of an empty string for `newDef` removes the string entry. Note that the returned string should NEVER be cached by your app, you should call this function when it is needed.
Expand Down
42 changes: 38 additions & 4 deletions SHAPES-EXAMPLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This quickstart is written specifically for native iOS and watchOS apps that are
* An iOS mobile device or simulator with iOS 12 or higher or a watchOS device with watchOS 7.0 or higher
* The contents of this repo

## RUNNING THE SHAPES APP WITHOUT APPROOV
## RUN THE SHAPES APP WITHOUT APPROOV

Open the `ApproovShapes.xcodeproj` project in the `shapes-app` folder using `File->Open` in Xcode. Ensure the `ApproovShapes` project is selected at the top of Xcode's project explorer panel.

Expand Down Expand Up @@ -47,9 +47,9 @@ The subsequent steps of this guide show you how to provide better protection, ei

The Approov integration is available via the [`Swift Package Manager`](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app). This allows inclusion into the project by simply specifying a dependency in the `File -> Add Packages...` Xcode option if the project is selected:

![Add Packag Dependency](readme-images/AddPackage.png)
![Add Package Dependency](readme-images/AddPackage.png)

Enter the repository`https://github.com/approov/approov-service-urlsession.git` into the search box. You will then have to select the relevant version you wish to use. To do so, select the `Exact Version`, after which the latest `tag` from the selected repository should be selected. If you would like to use an earlier version, just replace the latest one, but bear in mind that the combined watchOS and iOS support began with version `3.3.0`.
Enter the repository `https://github.com/approov/approov-service-urlsession.git` into the search box. You will then have to select the relevant version you wish to use. To do so, select the `Exact Version`, after which the latest `tag` from the selected repository should be selected. If you would like to use an earlier version, just replace the latest one, but bear in mind that the combined watchOS and iOS support began with version `3.3.0`.

Once you click `Add Package` the last step will confirm the package product and target selection. Please, make sure you select a library for each one of your targets:

Expand Down Expand Up @@ -143,9 +143,43 @@ If you still don't get a valid shape then there are some things you can try. Rem
* Also, you can use a debugger and get valid Approov tokens on any device if you [mark the signing certificate as being for development](https://approov.io/docs/latest/approov-usage-documentation/#development-app-signing-certificates).
* Inspect any exceptions for additional information.

## SHAPES APP WITH INSTALLATION MESSAGE SIGNING

This section shows how to add message signing as an additional layer of protection in addition to an Approov token.

1. Make sure we are using the `https://shapes.approov.io/v5/shapes/` endpoint of the shapes server. The v5 endpoint performs a message signature check in addition to the Approov token check. Find the following line in `ViewController.swift` and `ContentView.swift` source file and uncomment it to point to `v5` (commenting the previous definition):

```swift
//*** UNCOMMENT THE LINE BELOW FOR APPROOV USING INSTALLATION MESSAGE SIGNING
let currentShapesEndpoint = "v5"
```

2. Uncomment the message signing setup code in `ViewController.swift` and `ContentView.swift`. This adds an interceptor extension to the ApproovService which adds the message signature to the request automatically.

```swift
//*** UNCOMMENT THE LINES BELOW FOR APPROOV USING INSTALLATION MESSAGE SIGNING
ApproovService.setApproovInterceptorExtensions(
ApproovDefaultMessageSigning().setDefaultFactory(
ApproovDefaultMessageSigning.generateDefaultSignatureParametersFactory()))
```

3. Configure Approov to add the public message signing key to the approov token. This key is used by the v5 endpoint to perform its message signature check.

```shell
approov policy -setInstallPubKey on
```

4. Build and run the app again and press the `Shape` button. You should see this (or another shape):

<p>
<img src="readme-images/shape-approoved.png" width="256" title="Shape Approoved">
</p>

This indicates that in addition to the app obtaining a validly signed Approov token, the message also has a valid signature.

## SHAPES APP WITH SECRETS PROTECTION

This section provides an illustration of an alternative option for Approov protection if you are not able to modify the backend to add an Approov Token check. We are still going to be using `https://shapes.approov.io/v1/shapes/` that simply checks for an API key, so please change back the code so it points to `https://shapes.approov.io/v1/shapes/`.
This section provides an illustration of an alternative option for Approov protection if you are not able to modify the backend to add an Approov Token check. We are going to be using `https://shapes.approov.io/v1/shapes/` that simply checks for an API key. Change back the code so it points to `https://shapes.approov.io/v1/shapes/`.

The `apiSecretKey` variable also needs to be changed as follows, removing the actual API key out of the code. Find this line and uncomment it (commenting the previous definition):

Expand Down
4 changes: 2 additions & 2 deletions shapes-app/ApproovShapes.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = ios.swift.shapes.demo.approov.io;
PRODUCT_BUNDLE_IDENTIFIER = "swift-urlsession.shapes.approov.io";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_INSTALL_OBJC_HEADER = NO;
Expand Down Expand Up @@ -603,7 +603,7 @@
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = ios.swift.shapes.demo.approov.io;
PRODUCT_BUNDLE_IDENTIFIER = "swift-urlsession.shapes.approov.io";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_INSTALL_OBJC_HEADER = NO;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"originHash" : "7d37158cbc2a86c26e3252ae40232214ff4c1bc78285745545f8ffcf19adfd97",
"pins" : [
{
"identity" : "swift-http-structured-headers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-structured-headers.git",
"state" : {
"revision" : "8e769facea6b7d46ea60e5e93635a384fd573480",
"version" : "1.2.1"
}
}
],
"version" : 3
}
Loading