Skip to content

Commit d8cc780

Browse files
authored
Merge pull request #4 from approov/feature/message-signing
Feature/message signing
2 parents 0ad877a + e844e26 commit d8cc780

File tree

11 files changed

+162
-46
lines changed

11 files changed

+162
-46
lines changed

API-PROTECTION.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,33 @@ If it is not possible to download the correct certificate from the portal then i
3636

3737
> **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.
3838
39+
## MESSAGE SIGNING
40+
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.
41+
42+
### Enabling Installation Message Signing
43+
44+
Installation message signing can be enabled by executing the following command:
45+
46+
```shell
47+
approov policy -setInstallPubKey on
48+
```
49+
50+
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.
51+
52+
### Adding the Message Signature Automatically
53+
54+
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:
55+
56+
```swift
57+
ApproovService.setApproovInterceptorExtensions(
58+
ApproovDefaultMessageSigning().setDefaultFactory(
59+
ApproovDefaultMessageSigning.generateDefaultSignatureParametersFactory()))
60+
```
61+
62+
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.
63+
64+
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.
65+
3966
## FURTHER OPTIONS
4067
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.
4168

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

58-
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.
85+
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.
5986

6087
### Token Binding
6188
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:

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,28 @@ Enter the repository`https://github.com/approov/approov-service-urlsession.git`
1717

1818
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).
1919

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

2222
## USING APPROOV SERVICE
2323
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`.
2424

2525
```swift
26+
import ApproovURLSession
27+
2628
try! ApproovService.initialize("<enter-your-config-string-here>")
2729
let session = ApproovURLSession(URLSessionConfiguration.default)
2830
```
2931

3032
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=`).
3133

32-
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.
34+
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.
3335

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

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

52-
* [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.
54+
* [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.
5355

5456
* [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.
5557

REFERENCE.md

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,30 @@ If a method throws an `ApproovError.rejectionError`, then this indicates the pro
1313
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`.
1414

1515
```swift
16-
public static func initialize(config: String) throws
16+
public static func initialize(config: String, comment: String? = nil) throws
1717
```
1818

1919
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.
2020

21+
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.
22+
23+
## setApproovInterceptorExtensions
24+
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.
25+
26+
```swift
27+
public static func setApproovInterceptorExtensions(_ callbacks: ApproovInterceptorExtensions?)
28+
```
29+
30+
Provide an ApproovDefaultMessageSigning object instantiated as shown below to enable installation message signing:
31+
32+
```java
33+
ApproovService.setApproovInterceptorExtensions(
34+
new ApproovDefaultMessageSigning().setDefaultFactory(
35+
ApproovDefaultMessageSigning.generateDefaultSignatureParametersFactory()));
36+
```
37+
38+
Passing `nil` to this method will disable message signing.
39+
2140
## setProceedOnNetworkFail
2241
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.
2342

@@ -134,13 +153,28 @@ public static func fetchToken(url: String) throws -> String
134153
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.
135154

136155
## getMessageSignature
137-
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.
138-
156+
**DEPRECATED**, replaced by `getAccountMessageSignature`.
139157
```swift
140158
public static func getMessageSignature(message: String) -> String?
141159
```
142160

143-
This return `nil` if there was an error obtaining the signature.
161+
## getAccountMessageSignature
162+
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.
163+
164+
```swift
165+
public static func getAccountMessageSignature(message: String) -> String?
166+
```
167+
168+
This returns `nil` if there was an error obtaining the signature.
169+
170+
## getInstallMessageSignature
171+
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.
172+
173+
```swift
174+
public static func getInstallMessageSignature(message: String) -> String?
175+
```
176+
177+
This returns `nil` if there was an error obtaining the signature.
144178

145179
## fetchSecureString
146180
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.

SHAPES-EXAMPLE.md

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This quickstart is written specifically for native iOS and watchOS apps that are
99
* An iOS mobile device or simulator with iOS 12 or higher or a watchOS device with watchOS 7.0 or higher
1010
* The contents of this repo
1111

12-
## RUNNING THE SHAPES APP WITHOUT APPROOV
12+
## RUN THE SHAPES APP WITHOUT APPROOV
1313

1414
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.
1515

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

4848
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:
4949

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

52-
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`.
52+
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`.
5353

5454
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:
5555

@@ -143,9 +143,43 @@ If you still don't get a valid shape then there are some things you can try. Rem
143143
* 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).
144144
* Inspect any exceptions for additional information.
145145

146+
## SHAPES APP WITH INSTALLATION MESSAGE SIGNING
147+
148+
This section shows how to add message signing as an additional layer of protection in addition to an Approov token.
149+
150+
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):
151+
152+
```swift
153+
//*** UNCOMMENT THE LINE BELOW FOR APPROOV USING INSTALLATION MESSAGE SIGNING
154+
let currentShapesEndpoint = "v5"
155+
```
156+
157+
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.
158+
159+
```swift
160+
//*** UNCOMMENT THE LINES BELOW FOR APPROOV USING INSTALLATION MESSAGE SIGNING
161+
ApproovService.setApproovInterceptorExtensions(
162+
ApproovDefaultMessageSigning().setDefaultFactory(
163+
ApproovDefaultMessageSigning.generateDefaultSignatureParametersFactory()))
164+
```
165+
166+
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.
167+
168+
```shell
169+
approov policy -setInstallPubKey on
170+
```
171+
172+
4. Build and run the app again and press the `Shape` button. You should see this (or another shape):
173+
174+
<p>
175+
<img src="readme-images/shape-approoved.png" width="256" title="Shape Approoved">
176+
</p>
177+
178+
This indicates that in addition to the app obtaining a validly signed Approov token, the message also has a valid signature.
179+
146180
## SHAPES APP WITH SECRETS PROTECTION
147181

148-
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/`.
182+
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/`.
149183

150184
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):
151185

shapes-app/ApproovShapes.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,7 @@
570570
"@executable_path/Frameworks",
571571
);
572572
OTHER_LDFLAGS = "-ObjC";
573-
PRODUCT_BUNDLE_IDENTIFIER = ios.swift.shapes.demo.approov.io;
573+
PRODUCT_BUNDLE_IDENTIFIER = "swift-urlsession.shapes.approov.io";
574574
PRODUCT_NAME = "$(TARGET_NAME)";
575575
PROVISIONING_PROFILE_SPECIFIER = "";
576576
SWIFT_INSTALL_OBJC_HEADER = NO;
@@ -603,7 +603,7 @@
603603
"@executable_path/Frameworks",
604604
);
605605
OTHER_LDFLAGS = "-ObjC";
606-
PRODUCT_BUNDLE_IDENTIFIER = ios.swift.shapes.demo.approov.io;
606+
PRODUCT_BUNDLE_IDENTIFIER = "swift-urlsession.shapes.approov.io";
607607
PRODUCT_NAME = "$(TARGET_NAME)";
608608
PROVISIONING_PROFILE_SPECIFIER = "";
609609
SWIFT_INSTALL_OBJC_HEADER = NO;

shapes-app/ApproovShapes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)