com.google.firebase.auth.internal.CryptoSigners Maven / Gradle / Ivy
package com.google.firebase.auth.internal;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseInterceptor;
import com.google.api.client.http.json.JsonHttpContent;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.util.Key;
import com.google.api.client.util.StringUtils;
import com.google.auth.ServiceAccountSigner;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteStreams;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.ImplFirebaseTrampolines;
import com.google.firebase.internal.FirebaseRequestInitializer;
import com.google.firebase.internal.NonNull;
import java.io.IOException;
import java.util.Map;
/**
* A set of {@link CryptoSigner} implementations and utilities for interacting with them.
*/
public class CryptoSigners {
private static final String METADATA_SERVICE_URL =
"http://metadata/computeMetadata/v1/instance/service-accounts/default/email";
/**
* A {@link CryptoSigner} implementation that uses service account credentials or equivalent
* crypto-capable credentials for signing data.
*/
static class ServiceAccountCryptoSigner implements CryptoSigner {
private final ServiceAccountSigner signer;
ServiceAccountCryptoSigner(@NonNull ServiceAccountSigner signer) {
this.signer = checkNotNull(signer);
}
@Override
public byte[] sign(byte[] payload) {
return signer.sign(payload);
}
@Override
public String getAccount() {
return signer.getAccount();
}
}
/**
* @ {@link CryptoSigner} implementation that uses the
*
* Google IAM service to sign data.
*/
static class IAMCryptoSigner implements CryptoSigner {
private static final String IAM_SIGN_BLOB_URL =
"https://iam.googleapis.com/v1/projects/-/serviceAccounts/%s:signBlob";
private final HttpRequestFactory requestFactory;
private final JsonFactory jsonFactory;
private final String serviceAccount;
private HttpResponseInterceptor interceptor;
IAMCryptoSigner(
@NonNull HttpRequestFactory requestFactory,
@NonNull JsonFactory jsonFactory,
@NonNull String serviceAccount) {
this.requestFactory = checkNotNull(requestFactory);
this.jsonFactory = checkNotNull(jsonFactory);
checkArgument(!Strings.isNullOrEmpty(serviceAccount));
this.serviceAccount = serviceAccount;
}
void setInterceptor(HttpResponseInterceptor interceptor) {
this.interceptor = interceptor;
}
@Override
public byte[] sign(byte[] payload) throws IOException {
String encodedUrl = String.format(IAM_SIGN_BLOB_URL, serviceAccount);
HttpResponse response = null;
String encodedPayload = BaseEncoding.base64().encode(payload);
Map content = ImmutableMap.of("bytesToSign", encodedPayload);
try {
HttpRequest request = requestFactory.buildPostRequest(new GenericUrl(encodedUrl),
new JsonHttpContent(jsonFactory, content));
request.setParser(new JsonObjectParser(jsonFactory));
request.setResponseInterceptor(interceptor);
response = request.execute();
SignBlobResponse parsed = response.parseAs(SignBlobResponse.class);
return BaseEncoding.base64().decode(parsed.signature);
} finally {
if (response != null) {
try {
response.disconnect();
} catch (IOException ignored) {
// Ignored
}
}
}
}
@Override
public String getAccount() {
return serviceAccount;
}
}
public static class SignBlobResponse {
@Key("signature")
private String signature;
}
/**
* Initializes a {@link CryptoSigner} instance for the given Firebase app. Follows the protocol
* documented at go/firebase-admin-sign.
*/
public static CryptoSigner getCryptoSigner(FirebaseApp firebaseApp) throws IOException {
GoogleCredentials credentials = ImplFirebaseTrampolines.getCredentials(firebaseApp);
// If the SDK was initialized with a service account, use it to sign bytes.
if (credentials instanceof ServiceAccountCredentials) {
return new ServiceAccountCryptoSigner((ServiceAccountCredentials) credentials);
}
FirebaseOptions options = firebaseApp.getOptions();
HttpRequestFactory requestFactory = options.getHttpTransport().createRequestFactory(
new FirebaseRequestInitializer(firebaseApp));
JsonFactory jsonFactory = options.getJsonFactory();
// If the SDK was initialized with a service account email, use it with the IAM service
// to sign bytes.
String serviceAccountId = options.getServiceAccountId();
if (!Strings.isNullOrEmpty(serviceAccountId)) {
return new IAMCryptoSigner(requestFactory, jsonFactory, serviceAccountId);
}
// If the SDK was initialized with some other credential type that supports signing
// (e.g. GAE credentials), use it to sign bytes.
if (credentials instanceof ServiceAccountSigner) {
return new ServiceAccountCryptoSigner((ServiceAccountSigner) credentials);
}
// Attempt to discover a service account email from the local Metadata service. Use it
// with the IAM service to sign bytes.
HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(METADATA_SERVICE_URL));
request.getHeaders().set("Metadata-Flavor", "Google");
HttpResponse response = request.execute();
try {
byte[] output = ByteStreams.toByteArray(response.getContent());
serviceAccountId = StringUtils.newStringUtf8(output).trim();
return new IAMCryptoSigner(requestFactory, jsonFactory, serviceAccountId);
} finally {
response.disconnect();
}
}
}