Skip to content

Commit aeabe77

Browse files
authored
Add X-Android-Package and X-Android-Cert headers. (#2980)
* Add X-Android-Package and X-Android-Cert headers. * Update unit tests.
1 parent c30a15a commit aeabe77

File tree

2 files changed

+53
-28
lines changed

2 files changed

+53
-28
lines changed

appcheck/firebase-appcheck/src/main/java/com/google/firebase/appcheck/internal/NetworkClient.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@
1616

1717
import static com.google.android.gms.common.internal.Preconditions.checkNotNull;
1818

19+
import android.content.Context;
20+
import android.content.pm.PackageManager.NameNotFoundException;
21+
import android.util.Log;
1922
import androidx.annotation.IntDef;
2023
import androidx.annotation.NonNull;
2124
import androidx.annotation.VisibleForTesting;
25+
import com.google.android.gms.common.util.AndroidUtilsLight;
26+
import com.google.android.gms.common.util.Hex;
2227
import com.google.firebase.FirebaseApp;
2328
import com.google.firebase.FirebaseException;
2429
import com.google.firebase.appcheck.FirebaseAppCheck;
@@ -40,6 +45,8 @@
4045
*/
4146
public class NetworkClient {
4247

48+
private static final String TAG = NetworkClient.class.getName();
49+
4350
private static final String SAFETY_NET_EXCHANGE_URL_TEMPLATE =
4451
"https://firebaseappcheck.googleapis.com/v1beta/projects/%s/apps/%s:exchangeSafetyNetToken?key=%s";
4552
private static final String DEBUG_EXCHANGE_URL_TEMPLATE =
@@ -49,7 +56,10 @@ public class NetworkClient {
4956
private static final String UTF_8 = "UTF-8";
5057
@VisibleForTesting static final String X_FIREBASE_CLIENT = "X-Firebase-Client";
5158
@VisibleForTesting static final String X_FIREBASE_CLIENT_LOG_TYPE = "X-Firebase-Client-Log-Type";
59+
@VisibleForTesting static final String X_ANDROID_PACKAGE = "X-Android-Package";
60+
@VisibleForTesting static final String X_ANDROID_CERT = "X-Android-Cert";
5261

62+
private final Context context;
5363
private final DefaultFirebaseAppCheck firebaseAppCheck;
5464
private final String apiKey;
5565
private final String appId;
@@ -65,6 +75,7 @@ public class NetworkClient {
6575

6676
public NetworkClient(@NonNull FirebaseApp firebaseApp) {
6777
checkNotNull(firebaseApp);
78+
this.context = firebaseApp.getApplicationContext();
6879
this.firebaseAppCheck = (DefaultFirebaseAppCheck) FirebaseAppCheck.getInstance(firebaseApp);
6980
this.apiKey = firebaseApp.getOptions().getApiKey();
7081
this.appId = firebaseApp.getOptions().getApplicationId();
@@ -97,6 +108,10 @@ public AppCheckTokenResponse exchangeAttestationForAppCheckToken(
97108
X_FIREBASE_CLIENT_LOG_TYPE, firebaseAppCheck.getHeartbeatCode());
98109
}
99110

111+
// Headers for Android API key restrictions.
112+
urlConnection.setRequestProperty(X_ANDROID_PACKAGE, context.getPackageName());
113+
urlConnection.setRequestProperty(X_ANDROID_CERT, getFingerprintHashForPackage());
114+
100115
try (OutputStream out =
101116
new BufferedOutputStream(urlConnection.getOutputStream(), requestBytes.length)) {
102117
out.write(requestBytes, /* off= */ 0, requestBytes.length);
@@ -130,6 +145,23 @@ public AppCheckTokenResponse exchangeAttestationForAppCheckToken(
130145
}
131146
}
132147

148+
/** Gets the Android package's SHA-1 fingerprint. */
149+
private String getFingerprintHashForPackage() {
150+
byte[] hash;
151+
152+
try {
153+
hash = AndroidUtilsLight.getPackageCertificateHashBytes(context, context.getPackageName());
154+
if (hash == null) {
155+
Log.e(TAG, "Could not get fingerprint hash for package: " + context.getPackageName());
156+
return null;
157+
}
158+
return Hex.bytesToStringUppercase(hash, /* zeroTerminated= */ false);
159+
} catch (NameNotFoundException e) {
160+
Log.e(TAG, "No such package: " + context.getPackageName(), e);
161+
return null;
162+
}
163+
}
164+
133165
private static String getUrlTemplate(@AttestationTokenType int tokenType) {
134166
switch (tokenType) {
135167
case SAFETY_NET:

appcheck/firebase-appcheck/src/test/java/com/google/firebase/appcheck/internal/NetworkClientTest.java

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020
import static com.google.firebase.appcheck.internal.HttpErrorResponse.CODE_KEY;
2121
import static com.google.firebase.appcheck.internal.HttpErrorResponse.ERROR_KEY;
2222
import static com.google.firebase.appcheck.internal.HttpErrorResponse.MESSAGE_KEY;
23+
import static com.google.firebase.appcheck.internal.NetworkClient.X_ANDROID_CERT;
24+
import static com.google.firebase.appcheck.internal.NetworkClient.X_ANDROID_PACKAGE;
2325
import static com.google.firebase.appcheck.internal.NetworkClient.X_FIREBASE_CLIENT;
2426
import static com.google.firebase.appcheck.internal.NetworkClient.X_FIREBASE_CLIENT_LOG_TYPE;
2527
import static org.junit.Assert.assertThrows;
2628
import static org.mockito.ArgumentMatchers.any;
29+
import static org.mockito.ArgumentMatchers.eq;
2730
import static org.mockito.Mockito.doReturn;
2831
import static org.mockito.Mockito.spy;
2932
import static org.mockito.Mockito.verify;
@@ -116,13 +119,7 @@ public void exchangeSafetyNetToken_successResponse_returnsAppCheckTokenResponse(
116119
verify(networkClient).createHttpUrlConnection(expectedUrl);
117120
verify(outputStream)
118121
.write(JSON_REQUEST.getBytes(), /* off= */ 0, JSON_REQUEST.getBytes().length);
119-
120-
String userAgent = ((DefaultFirebaseAppCheck) FirebaseAppCheck.getInstance()).getUserAgent();
121-
String heartBeatCode =
122-
((DefaultFirebaseAppCheck) FirebaseAppCheck.getInstance()).getHeartbeatCode();
123-
verify(mockHttpUrlConnection).setRequestProperty(X_FIREBASE_CLIENT, userAgent);
124-
verify(mockHttpUrlConnection).setRequestProperty(X_FIREBASE_CLIENT_LOG_TYPE, heartBeatCode);
125-
assertThat(userAgent).contains(SDK_NAME);
122+
verifyRequestHeaders();
126123
}
127124

128125
@Test
@@ -146,13 +143,7 @@ public void exchangeSafetyNetToken_errorResponse_throwsException() throws Except
146143
verify(networkClient).createHttpUrlConnection(expectedUrl);
147144
verify(outputStream)
148145
.write(JSON_REQUEST.getBytes(), /* off= */ 0, JSON_REQUEST.getBytes().length);
149-
150-
String userAgent = ((DefaultFirebaseAppCheck) FirebaseAppCheck.getInstance()).getUserAgent();
151-
String heartBeatCode =
152-
((DefaultFirebaseAppCheck) FirebaseAppCheck.getInstance()).getHeartbeatCode();
153-
verify(mockHttpUrlConnection).setRequestProperty(X_FIREBASE_CLIENT, userAgent);
154-
verify(mockHttpUrlConnection).setRequestProperty(X_FIREBASE_CLIENT_LOG_TYPE, heartBeatCode);
155-
assertThat(userAgent).contains(SDK_NAME);
146+
verifyRequestHeaders();
156147
}
157148

158149
@Test
@@ -174,13 +165,7 @@ public void exchangeDebugToken_successResponse_returnsAppCheckTokenResponse() th
174165
verify(networkClient).createHttpUrlConnection(expectedUrl);
175166
verify(outputStream)
176167
.write(JSON_REQUEST.getBytes(), /* off= */ 0, JSON_REQUEST.getBytes().length);
177-
178-
String userAgent = ((DefaultFirebaseAppCheck) FirebaseAppCheck.getInstance()).getUserAgent();
179-
String heartBeatCode =
180-
((DefaultFirebaseAppCheck) FirebaseAppCheck.getInstance()).getHeartbeatCode();
181-
verify(mockHttpUrlConnection).setRequestProperty(X_FIREBASE_CLIENT, userAgent);
182-
verify(mockHttpUrlConnection).setRequestProperty(X_FIREBASE_CLIENT_LOG_TYPE, heartBeatCode);
183-
assertThat(userAgent).contains(SDK_NAME);
168+
verifyRequestHeaders();
184169
}
185170

186171
@Test
@@ -204,13 +189,7 @@ public void exchangeDebugToken_errorResponse_throwsException() throws Exception
204189
verify(networkClient).createHttpUrlConnection(expectedUrl);
205190
verify(outputStream)
206191
.write(JSON_REQUEST.getBytes(), /* off= */ 0, JSON_REQUEST.getBytes().length);
207-
208-
String userAgent = ((DefaultFirebaseAppCheck) FirebaseAppCheck.getInstance()).getUserAgent();
209-
String heartBeatCode =
210-
((DefaultFirebaseAppCheck) FirebaseAppCheck.getInstance()).getHeartbeatCode();
211-
verify(mockHttpUrlConnection).setRequestProperty(X_FIREBASE_CLIENT, userAgent);
212-
verify(mockHttpUrlConnection).setRequestProperty(X_FIREBASE_CLIENT_LOG_TYPE, heartBeatCode);
213-
assertThat(userAgent).contains(SDK_NAME);
192+
verifyRequestHeaders();
214193
}
215194

216195
@Test
@@ -222,6 +201,20 @@ public void exchangeUnknownAttestation_throwsException() {
222201
JSON_REQUEST.getBytes(), NetworkClient.UNKNOWN));
223202
}
224203

204+
private void verifyRequestHeaders() {
205+
String userAgent = ((DefaultFirebaseAppCheck) FirebaseAppCheck.getInstance()).getUserAgent();
206+
String heartBeatCode =
207+
((DefaultFirebaseAppCheck) FirebaseAppCheck.getInstance()).getHeartbeatCode();
208+
209+
verify(mockHttpUrlConnection).setRequestProperty(X_FIREBASE_CLIENT, userAgent);
210+
verify(mockHttpUrlConnection).setRequestProperty(X_FIREBASE_CLIENT_LOG_TYPE, heartBeatCode);
211+
verify(mockHttpUrlConnection)
212+
.setRequestProperty(
213+
X_ANDROID_PACKAGE, ApplicationProvider.getApplicationContext().getPackageName());
214+
verify(mockHttpUrlConnection).setRequestProperty(eq(X_ANDROID_CERT), any());
215+
assertThat(userAgent).contains(SDK_NAME);
216+
}
217+
225218
private static JSONObject createAttestationResponse() throws Exception {
226219
JSONObject responseBodyJson = new JSONObject();
227220
responseBodyJson.put(ATTESTATION_TOKEN_KEY, ATTESTATION_TOKEN);

0 commit comments

Comments
 (0)