Skip to content

Commit 0001f04

Browse files
author
Richard Taylor
committed
Update to secrets protection description
1 parent e81f517 commit 0001f04

3 files changed

Lines changed: 70 additions & 66 deletions

File tree

API-PROTECTION.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ try {
8282
ApproovService.precheck();
8383
}
8484
catch(ApproovRejectionException e) {
85-
// failure due to the attestation being rejected, e.getARC() and e.getRejectionReasons() may be used to present information to the user
86-
// (note e.getRejectionReasons() is only available if the feature is enabled, otherwise it is always an empty string)
85+
// failure due to the attestation being rejected, e.getARC() and e.getRejectionReasons() may be used
86+
// to present information to the user (note e.getRejectionReasons() is only available if the feature
87+
// is enabled, otherwise it is always an empty string)
8788
}
8889
catch(ApproovNetworkException e) {
8990
// failure due to a potentially temporary networking issue, allow for a user initiated retry

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,12 @@ Initially you won't have set which API domains to protect, so the any `addApproo
7777
Your Approov onboarding email should contain a link allowing you to access [Live Metrics Graphs](https://approov.io/docs/latest/approov-usage-documentation/#metrics-graphs). After you've run your app with Approov integration you should be able to see the results in the live metrics within a minute or so. At this stage you could even release your app to get details of your app population and the attributes of the devices they are running upon.
7878

7979
## NEXT STEPS
80-
To actually protect your APIs there are some further steps. Approov provides two different options for protection:
80+
To actually protect your APIs and/or secrets there are some further steps. Approov provides two different options for protection:
8181

8282
* [API PROTECTION](https://github.com/approov/quickstart-android-java-httpsurlconn/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.
8383

84-
* [SECRETS PROTECTION](https://github.com/approov/quickstart-android-java-httpsurlconn/blob/master/SECRETS-PROTECTION.md): If you do not control the backend API(s) being protected, and are therefore unable to modify it to check Approov tokens, you can use this approach instead. It allows app secrets, and API keys, to be protected so that they no longer need to be included in the built code and are only made available to passing apps at runtime.
84+
* [SECRETS PROTECTION](https://github.com/approov/quickstart-android-java-httpsurlconn/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.
8585

86-
Note that it is possible to use both approaches side-by-side in the same app, in case your app uses a mixture of 1st and 3rd party APIs.
86+
Note that it is possible to use both approaches side-by-side in the same app.
8787

8888
See [REFERENCE](https://github.com/approov/quickstart-android-java-httpsurlconn/blob/master/REFERENCE.md) for a complete list of all of the `ApproovService` methods.

SECRETS-PROTECTION.md

Lines changed: 64 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# Secrets Protection
2-
You should use this option if you wish to protect access to 3rd party or managed APIs where you are not able to add an Approov token check to the backend. This allows client secrets, or API keys, used for access to be protected with Approov. Rather than build secrets into an app where they might be reverse engineered, they are only provided at runtime by Approov for apps that pass attestation. This substantially improves your protection and prevents these secrets being abused by attackers. Where you are able to modify the backend we recommend you use API Protection for further enhanced flexibility and security.
3-
4-
This quickstart provides straightforward implementation if the secret is currently supplied in a request header to the API. The `addApproov` method applied to a connection is able to automatically substitute in the secret for headers, but only if the app has passed the Approov attestation checks. If the app fails its checks then you can add a custom [rejection](#handling-rejections) handler.
2+
You should use this option if you wish to protect app secrets, including API keys. Rather than build secrets into an app, where they might be reverse engineered, they are only provided at runtime by Approov for apps that pass attestation. This substantially improves your protection and prevents these secrets being abused by attackers.
53

64
These additional steps require access to the [Approov CLI](https://approov.io/docs/latest/approov-cli-tool-reference/), please follow the [Installation](https://approov.io/docs/latest/approov-installation/) instructions.
75

@@ -19,7 +17,7 @@ approov pin -setManagedTrustRoots on
1917
This ensures connections may only use official certificates, and blocks the use of self signed certificates that might be used by a Man-in-the-Middle (MitM) attacker.
2018

2119
## ADDING API DOMAINS
22-
In order for secrets to be protected for particular API domains it is necessary to inform Approov about them. Execute the following command:
20+
In order for secrets or API keys to be protected when being transmitted externally by the app, it is necessary to inform Approov about the domains on which they may be sent. Execute the following command:
2321

2422
```
2523
approov api -add your.domain -noApproovToken
@@ -33,56 +31,76 @@ It is assumed that you already have some client secrets and/or API keys in your
3331
```
3432
approov secstrings -setEnabled
3533
```
36-
3734
> Note that this command requires an [admin role](https://approov.io/docs/latest/approov-usage-documentation/#account-access-roles).
3835
39-
The quickstart integration works by allowing you to replace the secret in your app with a placeholder value instead, and then the placeholder value is mapped to the actual secret value by `addApproov`, if the app passes Approov attestation. The shipped app code will only contain the placeholder values.
40-
41-
If your app currently uses `your-secret` then replace it in your app with the value `your-secret`. Choose a suitable placeholder name to reflect the type of the secret.
42-
43-
You must inform Approov that it should substitute `your-placeholder` with `your-secret` in requests as follows:
36+
You must inform Approov what the value of each secret is as follows:
4437

4538
```
46-
approov secstrings -addKey your-placeholder -predefinedValue your-secret
39+
approov secstrings -addKey your-secret-name -predefinedValue your-secret-value
4740
```
41+
4842
> Note that this command also requires an [admin role](https://approov.io/docs/latest/approov-usage-documentation/#account-access-roles).
4943
50-
You can add up to 16 different secret values to be substituted in this way.
44+
These values can be changed at any time and will propagate within 5 minutes to all running instances of your apps. Since earlier released versions of the app may have already leaked `your-secret-value`, you may wish to refresh the secret at some later point when any older version of the app is no longer in use. You can of course do this update over-the-air using Approov without any need to modify the app.
5145

52-
If the secret value is provided on the header `your-header` then it is necessary to notify the `ApproovService` that the header is subject to substitution. You do this by making the call once, after initialization:
46+
You can define up to 16 different secret values in this way.
47+
48+
# SUBSTITUTING THE SECRET AUTOMATICALLY
49+
If the secret is presented in an API header or query parameter, and you are able to use the `ApproovService` networking stack, then Approov can automatically substitute the secret value at runtime. You should use this method wherever possible.
50+
51+
If the published code of your app currently uses `your-secret-value` then replace it with the value `your-secret-name`. This provides a placeholder value which can then be automatically substituted with the actual secret value at runtime, for validly attesting apps. The shipped app code will only contain the placeholder values.
52+
53+
If the secret value needs to be provided on the header `your-header` then it is necessary to notify the `ApproovService` that the header is subject to substitution. You do this by making the call once, after initialization:
5354

5455
```Java
5556
ApproovService.addSubstitutionHeader("your-header", null);
5657
```
5758

58-
With this in place, calls to `addApproov` should replace the `your-placeholder` with `your-secret` as required when the app passes attestation. Since the mapping lookup is performed on the placeholder value you have the flexibility of providing different secrets on different API calls, even if they are passed with the same header name.
59+
With this in place the Approov networking interceptor should replace the `your-secret-name` with `your-secret-value` as required when the app passes attestation. Since the mapping lookup is performed on the secret name you have the flexibility of providing different secrets on different API calls, even if they are passed with the same header name.
5960

6061
You can see a [worked example](https://github.com/approov/quickstart-android-java-httpsurlconn/blob/master/SHAPES-EXAMPLE.md#shapes-app-with-secrets-protection) for the Shapes app.
6162

62-
Since earlier released versions of the app may have already leaked `your-secret`, you may wish to refresh the secret at some later point when any older version of the app is no longer in use. You can of course do this update over-the-air using Approov without any need to modify the app.
63-
6463
If the secret value is provided as a parameter in a URL query string then it is necessary to call a function that may rewrite the URL. This must be done before `openConnection()` is called. For instance, if you wish to substitute the parameter `your-param` then you must call:
6564

6665
```Java
6766
url = ApproovService.substituteQueryParam(url, "your-param");
6867
```
6968

70-
If no substitution is made then the return value is the same as the input [URL](https://developer.android.com/reference/java/net/URL), otherwise a new `URL` is created with the substituted parameter value. The call should transform any instance of a URL such as `https://your.domain/endpoint?your-param=your-placeholder` into `https://your.domain/endpoint?your-param=your-secret`. Note that this should only ever be applied to a `URL` with a host domain that has been added to Approov, so that either pinning or managed trust roots protection is being applied.
69+
If no substitution is made then the return value is the same as the input [URL](https://developer.android.com/reference/java/net/URL), otherwise a new `URL` is created with the substituted parameter value. The call should transform any instance of a URL such as `https://your.domain/endpoint?your-param=your-secret-name` into `https://your.domain/endpoint?your-param=your-secret-value`. Note that this should only ever be applied to a `URL` with a host domain that has been added to Approov, so that either pinning or managed trust roots protection is being applied.
7170

72-
## REGISTERING APPS
73-
In order for Approov to recognize the app as being valid it needs to be registered with the service. Change the directory to the top level of your app project and then register the app with Approov:
71+
## OBTAINING THE SECRET EXPLICITLY
72+
In some cases it might not be possible to automatically substitute a secret in a header or query parameter. This might be because the secret is used in other ways in your application.
7473

75-
```
76-
approov registration -add app/build/outputs/apk/debug/app-debug.apk
77-
```
74+
In this case it is possible to make an explicit call at runtime to obtain the secret value, for apps passing attestation. Here is an example for using the required method in `ApproovService`:
7875

79-
Note, some versions of Android Studio save the app in `app/build/intermediates/apk/debug/app-debug.apk`.
76+
```Java
77+
import io.approov.service.okhttp.ApproovException;
78+
import io.approov.service.okhttp.ApproovNetworkException;
79+
import io.approov.service.okhttp.ApproovRejectionException;
8080

81-
Note, on Windows you need to substitute \ for / in the above command.
81+
...
8282

83-
> **IMPORTANT:** The registration takes up to 30 seconds to propagate across the Approov Cloud Infrastructure, therefore don't try to run the app again before this time has elapsed. During development of your app you can ensure it [always passes](https://approov.io/docs/latest/approov-usage-documentation/#adding-a-device-security-policy) on your device to not have to register the APK each time you modify it.
8483

85-
[Managing Registrations](https://approov.io/docs/latest/approov-usage-documentation/#managing-registrations) provides more details for app registrations, especially for releases to the Play Store. Note that you may also need to apply specific [Android Obfuscation](https://approov.io/docs/latest/approov-usage-documentation/#android-obfuscation) rules for your app when releasing it.
84+
String secret;
85+
try {
86+
secret = ApproovService.fetchSecureString("your-secret-name", null);
87+
}
88+
catch(ApproovRejectionException e) {
89+
// failure due to the attestation being rejected, e.getARC() and e.getRejectionReasons() may be used to
90+
// present information to the user (note e.getRejectionReasons() is only available if the feature is
91+
// enabled, otherwise it is always an empty string)
92+
}
93+
catch(ApproovNetworkException e) {
94+
// failure due to a potentially temporary networking issue, allow for a user initiated retry
95+
}
96+
catch(ApproovException e) {
97+
// a more permanent error, see e.getMessage()
98+
}
99+
// use `secret` as required, but never cache or store its value - note `secret` will be null if the provided
100+
// secret name is not defined
101+
```
102+
103+
> **IMPORTANT:** The secrets obtained should only ever be communicated externally from the app over channels using the Approov networking stack and which have been added as protected API domains. If not then it is possible for them to be intercepted by a Man-in-the-Middle (MitM) attack.
86104
87105
## HANDLING REJECTIONS
88106
If the app is not recognized as being valid by Approov then an `ApproovRejectionException` is thrown on the request and the API call is not completed. The secret value will never be communicated to the app in this case.
@@ -99,6 +117,21 @@ approov policy -setRejectionReasons on
99117
100118
You will then be able to use `getRejectionReasons()` on an `ApproovRejectionException` to obtain a comma separated list of [device properties](https://approov.io/docs/latest/approov-usage-documentation/#device-properties) responsible for causing the rejection.
101119

120+
## REGISTERING APPS
121+
In order for Approov to recognize the app as being valid it needs to be registered with the service. Change the directory to the top level of your app project and then register the app with Approov:
122+
123+
```
124+
approov registration -add app/build/outputs/apk/debug/app-debug.apk
125+
```
126+
127+
Note, some versions of Android Studio save the app in `app/build/intermediates/apk/debug/app-debug.apk`.
128+
129+
Note, on Windows you need to substitute \ for / in the above command.
130+
131+
> **IMPORTANT:** The registration takes up to 30 seconds to propagate across the Approov Cloud Infrastructure, therefore don't try to run the app again before this time has elapsed. During development of your app you can ensure it [always passes](https://approov.io/docs/latest/approov-usage-documentation/#adding-a-device-security-policy) on your device to not have to register the APK each time you modify it.
132+
133+
[Managing Registrations](https://approov.io/docs/latest/approov-usage-documentation/#managing-registrations) provides more details for app registrations, especially for releases to the Play Store. Note that you may also need to apply specific [Android Obfuscation](https://approov.io/docs/latest/approov-usage-documentation/#android-obfuscation) rules for your app when releasing it.
134+
102135
## FURTHER OPTIONS
103136

104137
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.
@@ -113,40 +146,9 @@ ApproovService.addSubstitutionHeader("Authorization", "Bearer ");
113146
This causes the `Bearer` prefix to be stripped before doing the lookup for the substitution, and the `Bearer` prefix added to the actual secret value as part of the substitution.
114147

115148
### App Instance Secure Strings
116-
As shown, it is possible to set predefined secret strings that are only communicated to passing apps. It is also possible to get and set secure string values for each app instance. These are never communicated to the Approov cloud service, but are encrypted at rest using keys which can only be retrieved by passing apps.
117-
118-
Here is an example of using the required method in `ApproovService`:
119-
120-
```Java
121-
import io.approov.service.httpsurlconn.ApproovException;
122-
import io.approov.service.httpsurlconn.ApproovNetworkException;
123-
import io.approov.service.httpsurlconn.ApproovRejectionException;
124-
125-
...
126-
127-
String key;
128-
String newDef;
129-
String secret;
130-
// define key and newDef here
131-
try {
132-
secret = ApproovService.fetchSecureString(key, newDef);
133-
}
134-
catch(ApproovRejectionException e) {
135-
// failure due to the attestation being rejected, e.getARC() and e.getRejectionReasons() may be used to present information to the user
136-
// (note e.getRejectionReasons() is only available if the feature is enabled, otherwise it is always an empty string)
137-
}
138-
catch(ApproovNetworkException e) {
139-
// failure due to a potentially temporary networking issue, allow for a user initiated retry
140-
}
141-
catch(ApproovException e) {
142-
// a more permanent error, see e.getMessage()
143-
}
144-
// use `secret` as required, but never cache or store its value - note `secret` will be null if the provided key is not defined
145-
```
146-
147-
to lookup a secure string with the given `key`, returning `null` if it is not defined. Note that you should never cache this value in your code. Approov does the caching for you in a secure way. You may define a new value for the `key` by passing a new value in `newDef` rather than `null`. An empty string `newDef` is used to delete the secure string.
149+
In addition to secret values defined in the Approov cloud, it is also possible to get and set secure string values independently for each app instance. These are never communicated to the Approov cloud service, but are encrypted at rest using keys which can only be retrieved by passing apps. You can use this feature to protect user authorization tokens issued to individual apps or other sensitive customer data, for instance.
148150

149-
This method is also useful for providing runtime secrets protection when the values are not passed on headers. Secure strings set using this method may be substituted using subsequent `addApproov` calls.
151+
App instance secure strings can be set and retrived using the [secret fetching code](#obtaining-the-secret-explicitly). You can define a new value for a given secret name by passing a value in the second parameter of `fetchSecureString`, rather than `null`. An empty string may be used to delete the secure string completely.
150152

151153
### Prefetching
152154
If you wish to reduce the latency associated with substituting the first secret, then make this call immediately after initializing `ApproovService`:
@@ -171,8 +173,9 @@ try {
171173
ApproovService.precheck();
172174
}
173175
catch(ApproovRejectionException e) {
174-
// failure due to the attestation being rejected, e.getARC() and e.getRejectionReasons() may be used to present information to the user
175-
// (note e.getRejectionReasons() is only available if the feature is enabled, otherwise it is always an empty string)
176+
// failure due to the attestation being rejected, e.getARC() and e.getRejectionReasons() may be used
177+
// to present information to the user (note e.getRejectionReasons() is only available if the feature
178+
// is enabled, otherwise it is always an empty string)
176179
}
177180
catch(ApproovNetworkException e) {
178181
// failure due to a potentially temporary networking issue, allow for a user initiated retry

0 commit comments

Comments
 (0)