/*
* Copyright (C) 2012 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.motorolamobility.studio.android.certmanager.packaging.sign;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.X509Certificate;
import javax.security.auth.x500.X500Principal;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X500Name;
import com.motorola.studio.android.common.log.StudioLogger;
import com.motorolamobility.studio.android.certmanager.CertificateManagerActivator;
import com.motorolamobility.studio.android.certmanager.exception.KeyStoreManagerException;
import com.motorolamobility.studio.android.certmanager.ui.model.IKeyStoreEntry;
/**
* This class implements the signature block file from jar mechanism used in packaging
*/
public class SignatureBlockFile
{
/**
* the signature file
*/
private SignatureFile signatureFile;
/**
* A certificate from keystore
*/
private IKeyStoreEntry keystoreEntry;
/**
* The password of the certificate.
*/
private String keyEntryPassword;
/**
* Default Constructor
*
* @param signatureFile the signature file
* @param alias the certificate alias
*/
public SignatureBlockFile(SignatureFile signatureFile, IKeyStoreEntry keystoreEntry,
String keyEntryPassword)
{
this.keyEntryPassword = keyEntryPassword;
this.keystoreEntry = keystoreEntry;
this.signatureFile = signatureFile;
}
/**
* To string method override
*
* @return the signature block file name with relative path from root. Frequently META-INF/alias.RSA or .DSA
*/
@Override
public String toString()
{
String result = new String();
try
{
result =
new StringBuilder(CertificateManagerActivator.METAFILES_DIR)
.append(CertificateManagerActivator.JAR_SEPARATOR)
.append(ISignConstants.SIGNATURE_FILE_NAME).append(".")
.append(getBlockAlgorithm()).toString();
}
catch (UnrecoverableKeyException e)
{
StudioLogger.error("Could not generate signature block file name.");
}
catch (KeyStoreException e)
{
StudioLogger.error("Could not generate signature block file name.");
}
catch (NoSuchAlgorithmException e)
{
StudioLogger.error("Could not generate signature block file name.");
}
catch (KeyStoreManagerException e)
{
StudioLogger.error("Could not generate signature block file name.");
}
return result;
}
/**
* Gets the block file algorithm
*
* @return the signature block file algorithm to be used
* @throws KeyStoreManagerException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
* @throws UnrecoverableKeyException
* @throws
*/
private String getBlockAlgorithm() throws UnrecoverableKeyException, KeyStoreException,
NoSuchAlgorithmException, KeyStoreManagerException
{
return keystoreEntry.getKey(this.keyEntryPassword).getAlgorithm();
}
/**
* Writes this file to an output stream
*
* @param outputStream the output stream to write the file
* @throws IOException if an I/O error occurs during the signing process
* @throws SignException if a processing error occurs during the signing process
* @throws KeyStoreManagerException
* @throws KeyStoreException
* @throws UnrecoverableKeyException
* @throws NoSuchAlgorithmException
*/
public void write(OutputStream outputStream) throws IOException, SignException,
UnrecoverableKeyException, KeyStoreException, KeyStoreManagerException,
NoSuchAlgorithmException
{
// get certificate from entry
X509Certificate[] certChain =
{
keystoreEntry.getX509Certificate()
};
if (certChain.length > 0)
{
//get some info from certificate as issuer, serial and algorithm
X500Principal issuer = certChain[0].getIssuerX500Principal();
String serial = certChain[0].getSerialNumber().toString();
String blockalgorithm = getBlockAlgorithm();
String digestAlgotithm = null;
// determine the algorithm to be used to cipher the block file
if (blockalgorithm.equalsIgnoreCase(ISignConstants.DSA))
{
digestAlgotithm = ISignConstants.SHA1;
}
else if (blockalgorithm.equalsIgnoreCase(ISignConstants.RSA))
{
digestAlgotithm = ISignConstants.MD5;
}
else
{
StudioLogger.error(SignatureBlockFile.class,
"Signing block algorithm not supported. Key algorithm must be DSA or RSA");
throw new SignException("Signing block algorithm not supported");
}
String signatureAlgorithm =
digestAlgotithm + ISignConstants.ALGORITHM_CONNECTOR + blockalgorithm;
AlgorithmId digestAlg;
try
{
digestAlg = AlgorithmId.get(digestAlgotithm);
AlgorithmId privateKeyAlg = AlgorithmId.get(blockalgorithm);
// write the certificate with signature file and cipher it
Signature sig = Signature.getInstance(signatureAlgorithm);
sig.initSign(keystoreEntry.getPrivateKey(this.keyEntryPassword));
PKCS7 block = null;
ByteArrayOutputStream baos = null;
ByteArrayOutputStream signature = null;
try
{
baos = new ByteArrayOutputStream();
signatureFile.write(baos);
ContentInfo contentInfo = new ContentInfo(ContentInfo.DATA_OID, null);
sig.update(baos.toByteArray());
signature = new ByteArrayOutputStream();
signature.write(sig.sign());
SignerInfo si =
new SignerInfo(new X500Name(issuer.getName()), new BigInteger(serial),
digestAlg, privateKeyAlg, signature.toByteArray());
AlgorithmId[] algs =
{
digestAlg
};
SignerInfo[] infos =
{
si
};
block = new PKCS7(algs, contentInfo, certChain, infos);
}
catch (IOException e)
{
StudioLogger.error(SignatureBlockFile.class,
"I/O error creating signature block file: " + e.getMessage());
throw new SignException("I/O error creating signature block file", e);
}
finally
{
if (baos != null)
{
baos.close();
}
if (signature != null)
{
signature.close();
}
}
// I/O exceptions below are thrown unmodified
block.encodeSignedData(outputStream);
}
catch (NoSuchAlgorithmException nsae)
{
StudioLogger.error(SignatureBlockFile.class, "Signing algorithm not supported: "
+ nsae.getMessage());
throw new SignException("Signing algorithm not supported", nsae);
}
catch (InvalidKeyException ike)
{
StudioLogger.error(SignatureBlockFile.class,
"Invalid key when creating signature block file: " + ike.getMessage());
throw new SignException("Invalid key when creating signature block file", ike);
}
catch (SignatureException se)
{
StudioLogger.error(SignatureBlockFile.class,
"Signature error when creating signature block file: " + se.getMessage());
throw new SignException("Signature error creating signature block file", se);
}
}
StudioLogger.info(SignatureBlockFile.class, "Created signature block file");
}
}