blob: faedf0d2e5e4c66d150730efe2b52ed08d3a731f [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.idea.templates;
import com.android.ide.common.signing.KeystoreHelper;
import com.android.ide.common.signing.KeytoolException;
import com.android.prefs.AndroidLocation;
import com.android.tools.idea.gradle.parser.BuildFileKey;
import com.android.tools.idea.gradle.parser.GradleBuildFile;
import com.android.tools.idea.gradle.parser.GradleSettingsFile;
import com.android.tools.idea.gradle.parser.NamedObject;
import com.android.utils.ILogger;
import com.android.utils.StdLogger;
import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.android.model.impl.JpsAndroidModuleProperties;
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.util.List;
/**
* Functions for dealing with singing configurations and keystore files.
*/
public class KeystoreUtils {
/**
* Get the debug keystore path.
*
* @return the keystore file.
* @throws Exception if the keystore file could not be obtained.
*/
@NotNull
public static File getDebugKeystore(@NotNull AndroidFacet facet) throws Exception {
File gradleDebugKeystore = getGradleDebugKeystore(facet);
if (gradleDebugKeystore != null) {
return gradleDebugKeystore;
}
JpsAndroidModuleProperties state = facet.getConfiguration().getState();
if (state != null && !Strings.isNullOrEmpty(state.CUSTOM_DEBUG_KEYSTORE_PATH)) {
return new File(state.CUSTOM_DEBUG_KEYSTORE_PATH);
}
return getOrCreateDefaultDebugKeystore();
}
public static File getOrCreateDefaultDebugKeystore() throws Exception {
try {
File debugLocation = new File(KeystoreHelper.defaultDebugKeystoreLocation());
if (!debugLocation.exists()) {
ILogger logger = new StdLogger(StdLogger.Level.ERROR);
// Default values taken from http://developer.android.com/tools/publishing/app-signing.html
// Keystore name: "debug.keystore"
// Keystore password: "android"
// Keystore alias: "androiddebugkey"
// Key password: "android"
KeystoreHelper.createDebugStore(null, debugLocation, "android", "android", "AndroidDebugKey",logger);
}
if (!debugLocation.exists()) {
throw new AndroidLocation.AndroidLocationException("Could not create debug keystore");
}
return debugLocation;
}
catch (AndroidLocation.AndroidLocationException e) {
throw new Exception("Failed to get debug keystore path", e);
}
}
/**
* Gets a custom debug keystore defined in the build.gradle file for this module
*
* @return null if there is no custom debug keystore configured, or if the project is not a Gradle project.
*/
@Nullable
private static File getGradleDebugKeystore(@NotNull AndroidFacet facet) {
GradleSettingsFile gradleSettingsFile = GradleSettingsFile.get(facet.getModule().getProject());
if (gradleSettingsFile == null) {
return null;
}
String modulePath = GradleSettingsFile.getModuleGradlePath(facet.getModule());
if (modulePath == null) {
return null;
}
GradleBuildFile moduleBuildFile = gradleSettingsFile.getModuleBuildFile(modulePath);
if (moduleBuildFile == null) {
return null;
}
@SuppressWarnings("unchecked") List<NamedObject> signingConfigs =
(List<NamedObject>)moduleBuildFile.getValue(BuildFileKey.SIGNING_CONFIGS);
if (signingConfigs == null) {
return null;
}
for (NamedObject namedObject : signingConfigs) {
if (!"debug".equals(namedObject.getName())) {
continue;
}
File debugKey = (File)namedObject.getValue(BuildFileKey.STORE_FILE);
if (debugKey == null) {
continue;
}
VirtualFile moduleFile = facet.getModule().getModuleFile();
if (moduleFile == null) {
continue;
}
// NOTE: debugKey.getParent() is the current working directory.
return new File(moduleFile.getParent().getPath(), debugKey.getPath());
}
return null;
}
/**
* Get the SHA1 hash of the first signing certificate inside a keystore, encoded as base16 (each byte separated by ':').
*
* @param keystoreFile the keystore file. Must be readable.
* @throws Exception when the sha1 couldn't be computed for any reason.
*/
public static String sha1(File keystoreFile) throws Exception {
Certificate signingCert;
try {
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(keystoreFile), "android".toCharArray());
String keyAlias = keyStore.aliases().nextElement();
signingCert = keyStore.getCertificate(keyAlias);
}
catch (Exception e) {
throw new Exception("Could not extract certificate from file.", e);
}
// Produce SHA1 fingerprint.
try {
byte[] certBytes = MessageDigest.getInstance("SHA1").digest(signingCert.getEncoded());
// Add a separator every 2 characters (i.e. every byte from hash)
return BaseEncoding.base16().withSeparator(":", 2).encode(certBytes);
}
catch (Exception e) {
throw new Exception("Could not compute SHA1 hash from certificate", e);
}
}
}