blob: 1f11724a2f883e6ffb90e9261b4fd7cab1644d4f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Varun Raval and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* Varun Raval - initial API and implementation
*******************************************************************************/
package org.eclipse.ease.sign;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.ease.Activator;
import org.eclipse.ease.ICodeParser;
import org.eclipse.ease.Logger;
import org.eclipse.ease.service.ScriptType;
import org.eclipse.ease.tools.ResourceTools;
public class VerifySignature {
private final SignatureInfo fSignatureInfo;
/**
* Use this method to get constructor when signature is attached to script file.
*
* @param scriptType
* provide {@link ScriptType} instance of stream for script
* @param inputStream
* provide stream of script to verify
* @return instance of {@link VerifySignature} when signature is present and can be properly loaded or null when signature is not present
* @throws ScriptSignatureException
* when one or more parameters are not provided or signature format is improper
*/
public static VerifySignature getInstance(final ScriptType scriptType, final InputStream inputStream) throws ScriptSignatureException {
return getInstance(scriptType, inputStream, null);
}
/**
* Use this method to get constructor when script contents and signature are separate. Use only when it is guaranteed that input stream of signature is for
* corresponding input stream of file.
*
* @param scriptType
* provide {@link ScriptType} instance of stream for script
* @param inputStream
* provide stream of script to verify
* @param signatureInputStream
* provide stream where signature is stored
* @return instance of {@link VerifySignature} when signature can be properly loaded or null when signature is not present
* @throws ScriptSignatureException
* when one or more parameters are not provided or signature format is improper
*/
public static VerifySignature getInstance(final ScriptType scriptType, final InputStream inputStream, final InputStream signatureInputStream)
throws ScriptSignatureException {
if ((scriptType == null) || (inputStream == null))
throw new ScriptSignatureException("One or more parameters are not provided");
final ICodeParser iCodeParser = scriptType.getCodeParser();
if (iCodeParser == null) {
return null;
}
if (signatureInputStream == null) {
final SignatureInfo signatureInfo = iCodeParser.getSignatureInfo(inputStream);
if (signatureInfo != null) {
if ((signatureInfo.getSignature() == null) || (signatureInfo.getProvider() == null) || (signatureInfo.getMessageDigestAlgo() == null)
|| (signatureInfo.getCertificateChain() == null) || (signatureInfo.getContentOnly() == null))
throw new ScriptSignatureException("Error while parsing script. Try again.");
return new VerifySignature(signatureInfo);
} else
return null;
} else {
final SignatureInfo signatureInfo = iCodeParser.getSignatureInfo(signatureInputStream);
if (signatureInfo != null) {
if ((signatureInfo.getSignature() == null) || (signatureInfo.getProvider() == null) || (signatureInfo.getMessageDigestAlgo() == null)
|| (signatureInfo.getCertificateChain() == null))
throw new ScriptSignatureException("Error while parsing script. Try again.");
final BufferedInputStream bInput = new BufferedInputStream(inputStream);
final StringBuffer sBuf = new StringBuffer();
int cur;
try {
while ((cur = bInput.read()) >= 0)
sBuf.append((char) cur);
signatureInfo.setContentOnly(sBuf.toString());
return new VerifySignature(signatureInfo);
} catch (final IOException e) {
Logger.error(Activator.PLUGIN_ID, e.getMessage(), e);
throw new ScriptSignatureException("An IO error occurred while reading file.", e);
} finally {
try {
if (bInput != null)
bInput.close();
} catch (final IOException e) {
Logger.error(Activator.PLUGIN_ID, e.getMessage(), e);
}
}
} else
return null;
}
}
/**
*
* @param signatureInfo
* provide {@link SignatureInfo} containing the signature and contents of file on which signature was applied
*/
private VerifySignature(SignatureInfo signatureInfo) {
fSignatureInfo = signatureInfo;
}
/**
* Converts byte array to corresponding certificate.
*
* @param bytesCert
* provide certificate in bytes to convert it to {@link Certificate}
* @return an instance of {@link Certificate}
* @throws ScriptSignatureException
* when there is an error while retrieving certificate
*/
private Certificate getCertificate(byte[] bytesCert) throws ScriptSignatureException {
CertificateFactory certificateFactory;
try {
certificateFactory = CertificateFactory.getInstance("X.509");
return certificateFactory.generateCertificate(new ByteArrayInputStream(bytesCert));
} catch (final CertificateException e) {
throw new ScriptSignatureException("Error while retrieving certificate.", e);
}
}
/**
* Converts certificate chain in form of string array to list.
*
* @return {@link List} of {@link Certificate}
* @throws ScriptSignatureException
* when there is an error while retrieving certificate
*/
private List<Certificate> getCertificateChain() throws ScriptSignatureException {
final String certChainString[] = fSignatureInfo.getCertificateChain();
final int noOfCert = certChainString.length;
final byte[][] certChainByte = new byte[noOfCert][];
for (int i = 0; i < noOfCert; i++)
certChainByte[i] = SignatureHelper.convertBase64ToBytes(certChainString[i]);
final ArrayList<Certificate> certificateList = new ArrayList<>();
for (final byte cert[] : certChainByte)
certificateList.add(getCertificate(cert));
return certificateList;
}
/**
* Checks whether certificate attached with script is self-signed or not.
*
* @return <code>true</code> if certificate is self-signed or <code>false</code> if certificate is CA signed
* @throws ScriptSignatureException
* when script does not contain signature or there is an error while retrieving certificate
*/
public boolean isSelfSignedCertificate() throws ScriptSignatureException {
if (fSignatureInfo != null) {
final ArrayList<Certificate> certificateList = (ArrayList<Certificate>) getCertificateChain();
final Certificate certificate = certificateList.get(0);
return SignatureHelper.isSelfSignedCertificate(certificate);
}
throw new ScriptSignatureException("Script does not contain signature.");
}
/**
* Checks the validity of certificate. If certificate is CA signed, then it checks the validity of CA with trust-store.
*
* @param trustStoreLocation
* provide location of truststore
* @param trustStorePassword
* provide password for truststore
* @return <code>true</code> if certificate is valid and trusted or <code>false</code> if certificate is invalid or not trusted
* @throws ScriptSignatureException
* when truststore can't be loaded due to one or more certificates can't be loaded from it or appropriate provider can't be found or truststore
* file can't be read or password does not correspond to truststore or truststore does not contain any trusted certificate entry or script does
* not contain signature
*/
public boolean isCertChainValid(InputStream trustStoreLocation, char[] trustStorePassword) throws ScriptSignatureException {
if (((trustStoreLocation == null) && (trustStorePassword != null)) || ((trustStoreLocation != null) && (trustStorePassword == null)))
throw new ScriptSignatureException("Either both or none of the parameters should be null");
if (fSignatureInfo != null) {
InputStream iStream = null;
try {
if ((trustStoreLocation == null) && (trustStorePassword == null)) {
// TODO check following command for windows
iStream = new FileInputStream(System.getProperty("java.home") + "/lib/security/" + "cacerts");
trustStorePassword = "changeit".toCharArray();
} else
iStream = ResourceTools.getInputStream(trustStoreLocation);
final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
final ArrayList<Certificate> certificateList = (ArrayList<Certificate>) getCertificateChain();
final int certLength = certificateList.size();
if (SignatureHelper.isSelfSignedCertificate(certificateList.get(certLength - 1)))
certificateList.remove(certLength - 1);
final CertPath certPath = certificateFactory.generateCertPath(certificateList);
final CertPathValidator validator = CertPathValidator.getInstance("PKIX");
final KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(iStream, trustStorePassword);
final PKIXParameters params = new PKIXParameters(keystore);
params.setRevocationEnabled(true);
// If certificate does not contain OSCP or CRL responder than that certificate will be considered invalid
Security.setProperty("ocsp.enable", "true");
System.setProperty("com.sun.net.ssl.checkRevocation", "true");
System.setProperty("com.sun.security.enableCRLDP", "true");
// Validate will throw an exception on invalid chains.
validator.validate(certPath, params);
return true;
} catch (final CertificateException e) {
throw new ScriptSignatureException("One or more certificates can't be loaded.", e);
} catch (final NoSuchAlgorithmException e) {
throw new ScriptSignatureException("Algorithm used for securing truststore can't be found. Chose another Truststore.", e);
} catch (final KeyStoreException e) {
throw new ScriptSignatureException("Truststore can't be loaded.");
} catch (final IOException e) {
if (e.getCause() instanceof UnrecoverableKeyException)
throw new ScriptSignatureException("Invalid Truststore Password.", e);
else if ((e.getCause() instanceof FileNotFoundException) || (e.getCause() instanceof SecurityException))
throw new ScriptSignatureException("File can't be read. Chose another Truststore or try again.", e);
Logger.error(Activator.PLUGIN_ID, Arrays.toString(e.getStackTrace()), e);
throw new ScriptSignatureException("Error loading Truststore. Try again.", e);
} catch (final InvalidAlgorithmParameterException e) {
Logger.error(Activator.PLUGIN_ID, Arrays.toString(e.getStackTrace()), e);
throw new ScriptSignatureException("Can't perform validation.", e);
} catch (final CertPathValidatorException e) {
// if any invalidation occurs, exception will be caught here
throw new ScriptSignatureException(e.getMessage());
} finally {
try {
if (iStream != null)
iStream.close();
} catch (final IOException e) {
Logger.error(Activator.PLUGIN_ID, Arrays.toString(e.getStackTrace()), e);
}
}
}
throw new ScriptSignatureException("Script does not contain signature.");
}
/**
* Checks the validity of certificate. If certificate is CA signed, then it checks the validity of CA with trust-store. It uses default truststore present
* at JRE_PATH/lib/security/cacerts and "changeit" as password. If password has been modified, use {@link #isCertChainValid(InputStream, char[])}.
*
* @return <code>true</code> if certificate is valid and trusted or <code>false</code> if certificate is invalid or not trusted
* @throws ScriptSignatureException
* when one or more certificates can't be loaded from truststore or truststore can't be loaded
*/
public boolean isCertChainValid() throws ScriptSignatureException {
return isCertChainValid(null, null);
}
/**
* Verify given signature with provided public key of provided certificate.
*
* @return <code>true</code> if signature is valid or <code>false</code> if signature is invalid
* @throws ScriptSignatureException
* when script does not contain signature or there is an error while retrieving certificate
*/
public boolean verify() throws ScriptSignatureException {
if (fSignatureInfo != null) {
final byte[] signByte = SignatureHelper.convertBase64ToBytes(fSignatureInfo.getSignature());
final byte[] certByte = SignatureHelper.convertBase64ToBytes(fSignatureInfo.getCertificateChain()[0]);
final Certificate userCert = getCertificate(certByte);
try {
final PublicKey publicKey = userCert.getPublicKey();
final String encryptionAlgo = publicKey.getAlgorithm();
final Signature signature = Signature.getInstance(fSignatureInfo.getMessageDigestAlgo() + "with" + encryptionAlgo,
fSignatureInfo.getProvider());
// initialize signature instance with public key
signature.initVerify(publicKey);
// perform verification
signature.update(fSignatureInfo.getContentOnly().getBytes());
return signature.verify(signByte);
} catch (final SignatureException e) {
Logger.error(Activator.PLUGIN_ID, "Signature object not initialized properly or signature is not readable.", e);
throw new ScriptSignatureException("Signature is not readable.", e);
} catch (final NoSuchAlgorithmException e) {
throw new ScriptSignatureException("Algorithm used by signature is not recognized by provider.", e);
} catch (final InvalidKeyException e) {
throw new ScriptSignatureException("Public key is invalid.", e);
} catch (final NoSuchProviderException e) {
throw new ScriptSignatureException("No such provider is registered in Security Providers' list.", e);
}
}
throw new ScriptSignatureException("Script does not contain signature.");
}
}