diff --git a/API-PROTECTION.md b/API-PROTECTION.md index bec2377..f12fd7d 100644 --- a/API-PROTECTION.md +++ b/API-PROTECTION.md @@ -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. @@ -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: diff --git a/README.md b/README.md index fea9069..d01fc01 100644 --- a/README.md +++ b/README.md @@ -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("") let session = ApproovURLSession(URLSessionConfiguration.default) ``` Additionally, the Approov SDK wrapper class, `ApproovService` needs to be initialized before using the `ApproovURLSession` object. The `` 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) @@ -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. diff --git a/REFERENCE.md b/REFERENCE.md index 6d99097..0c4d420 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -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. @@ -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. diff --git a/SHAPES-EXAMPLE.md b/SHAPES-EXAMPLE.md index 148e78d..c2738e0 100644 --- a/SHAPES-EXAMPLE.md +++ b/SHAPES-EXAMPLE.md @@ -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. @@ -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: @@ -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): + +

+ +

+ + 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): diff --git a/shapes-app/ApproovShapes.xcodeproj/project.pbxproj b/shapes-app/ApproovShapes.xcodeproj/project.pbxproj index a5d21c3..b363592 100644 --- a/shapes-app/ApproovShapes.xcodeproj/project.pbxproj +++ b/shapes-app/ApproovShapes.xcodeproj/project.pbxproj @@ -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; @@ -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; diff --git a/shapes-app/ApproovShapes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/shapes-app/ApproovShapes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..5d8673e --- /dev/null +++ b/shapes-app/ApproovShapes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -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 +} diff --git a/shapes-app/ApproovShapes/AppDelegate.swift b/shapes-app/ApproovShapes/AppDelegate.swift index 20dd6ce..3fafdd3 100644 --- a/shapes-app/ApproovShapes/AppDelegate.swift +++ b/shapes-app/ApproovShapes/AppDelegate.swift @@ -1,6 +1,6 @@ // MIT License // -// Copyright (c) 2016-present, Critical Blue Ltd. +// Copyright (c) 2016-present, Approov Ltd. // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files // (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, @@ -51,4 +51,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } - diff --git a/shapes-app/ApproovShapes/Base.lproj/Main.storyboard b/shapes-app/ApproovShapes/Base.lproj/Main.storyboard index 374deda..2acb10f 100644 --- a/shapes-app/ApproovShapes/Base.lproj/Main.storyboard +++ b/shapes-app/ApproovShapes/Base.lproj/Main.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -13,13 +11,13 @@ - + -