/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php * * 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.ide.eclipse.adt.internal.wizards.export; import com.android.ide.eclipse.adt.internal.project.ProjectHelper; import com.android.ide.eclipse.adt.internal.wizards.export.ExportWizard.ExportWizardPage; import org.eclipse.core.resources.IProject; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.forms.widgets.FormText; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.security.KeyStore; import java.security.KeyStore.PrivateKeyEntry; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.UnrecoverableEntryException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Calendar; /** * Final page of the wizard that checks the key and ask for the ouput location. */ final class KeyCheckPage extends ExportWizardPage { private static final int REQUIRED_YEARS = 25; private static final String VALIDITY_WARNING = "
Make sure the certificate is valid for the planned lifetime of the product.
" + "If the certificate expires, you will be forced to sign your application with " + "a different one.
" + "Applications cannot be upgraded if their certificate changes from " + "one version to another, forcing a full uninstall/install, which will make " + "the user lose his/her data.
" + "Google Play(Android Market) currently requires certificates to be valid " + "until 2033.
"; private final ExportWizard mWizard; private PrivateKey mPrivateKey; private X509Certificate mCertificate; private Text mDestination; private boolean mFatalSigningError; private FormText mDetailText; private ScrolledComposite mScrolledComposite; private String mKeyDetails; private String mDestinationDetails; protected KeyCheckPage(ExportWizard wizard, String pageName) { super(pageName); mWizard = wizard; setTitle("Destination and key/certificate checks"); setDescription(""); // TODO } @Override public void createControl(Composite parent) { setErrorMessage(null); setMessage(null); // build the ui. Composite composite = new Composite(parent, SWT.NULL); composite.setLayoutData(new GridData(GridData.FILL_BOTH)); GridLayout gl = new GridLayout(3, false); gl.verticalSpacing *= 3; composite.setLayout(gl); GridData gd; new Label(composite, SWT.NONE).setText("Destination APK file:"); mDestination = new Text(composite, SWT.BORDER); mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); mDestination.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { onDestinationChange(false /*forceDetailUpdate*/); } }); final Button browseButton = new Button(composite, SWT.PUSH); browseButton.setText("Browse..."); browseButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.SAVE); fileDialog.setText("Destination file name"); // get a default apk name based on the project String filename = ProjectHelper.getApkFilename(mWizard.getProject(), null /*config*/); fileDialog.setFileName(filename); String saveLocation = fileDialog.open(); if (saveLocation != null) { mDestination.setText(saveLocation); } } }); mScrolledComposite = new ScrolledComposite(composite, SWT.V_SCROLL); mScrolledComposite.setLayoutData(gd = new GridData(GridData.FILL_BOTH)); gd.horizontalSpan = 3; mScrolledComposite.setExpandHorizontal(true); mScrolledComposite.setExpandVertical(true); mDetailText = new FormText(mScrolledComposite, SWT.NONE); mScrolledComposite.setContent(mDetailText); mScrolledComposite.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { updateScrolling(); } }); setControl(composite); } @Override void onShow() { // fill the texts with information loaded from the project. if ((mProjectDataChanged & DATA_PROJECT) != 0) { // reset the destination from the content of the project IProject project = mWizard.getProject(); String destination = ProjectHelper.loadStringProperty(project, ExportWizard.PROPERTY_DESTINATION); if (destination != null) { mDestination.setText(destination); } } // if anything change we basically reload the data. if (mProjectDataChanged != 0) { mFatalSigningError = false; // reset the wizard with no key/cert to make it not finishable, unless a valid // key/cert is found. mWizard.setSigningInfo(null, null); mPrivateKey = null; mCertificate = null; mKeyDetails = null; if (mWizard.getKeystoreCreationMode() || mWizard.getKeyCreationMode()) { int validity = mWizard.getValidity(); StringBuilder sb = new StringBuilder( String.format("Certificate expires in %d years.
", validity)); if (validity < REQUIRED_YEARS) { sb.append(VALIDITY_WARNING); } mKeyDetails = sb.toString(); } else { try { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); FileInputStream fis = new FileInputStream(mWizard.getKeystore()); keyStore.load(fis, mWizard.getKeystorePassword().toCharArray()); fis.close(); PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry( mWizard.getKeyAlias(), new KeyStore.PasswordProtection( mWizard.getKeyPassword().toCharArray())); if (entry != null) { mPrivateKey = entry.getPrivateKey(); mCertificate = (X509Certificate)entry.getCertificate(); } else { setErrorMessage("Unable to find key."); setPageComplete(false); } } catch (FileNotFoundException e) { // this was checked at the first previous step and will not happen here, unless // the file was removed during the export wizard execution. onException(e); } catch (KeyStoreException e) { onException(e); } catch (NoSuchAlgorithmException e) { onException(e); } catch (UnrecoverableEntryException e) { onException(e); } catch (CertificateException e) { onException(e); } catch (IOException e) { onException(e); } if (mPrivateKey != null && mCertificate != null) { Calendar expirationCalendar = Calendar.getInstance(); expirationCalendar.setTime(mCertificate.getNotAfter()); Calendar today = Calendar.getInstance(); if (expirationCalendar.before(today)) { mKeyDetails = String.format( "Certificate expired on %s
", mCertificate.getNotAfter().toString()); // fatal error = nothing can make the page complete. mFatalSigningError = true; setErrorMessage("Certificate is expired."); setPageComplete(false); } else { // valid, key/cert: put it in the wizard so that it can be finished mWizard.setSigningInfo(mPrivateKey, mCertificate); StringBuilder sb = new StringBuilder(String.format( "Certificate expires on %s.
", mCertificate.getNotAfter().toString())); int expirationYear = expirationCalendar.get(Calendar.YEAR); int thisYear = today.get(Calendar.YEAR); if (thisYear + REQUIRED_YEARS < expirationYear) { // do nothing } else { if (expirationYear == thisYear) { sb.append("The certificate expires this year.
"); } else { int count = expirationYear-thisYear; sb.append(String.format( "The Certificate expires in %1$s %2$s.
", count, count == 1 ? "year" : "years")); } sb.append(VALIDITY_WARNING); } // show certificate fingerprints String sha1 = mWizard.getCertSha1Fingerprint(); String md5 = mWizard.getCertMd5Fingerprint(); sb.append("" /*blank line*/); sb.append("Certificate fingerprints:
"); sb.append(String.format("