blob: aef852a70707fc76ce67d1237f4b71a61be48c8c [file] [log] [blame]
/*
* Copyright (c) 2020 Kentyou.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Kentyou - initial API and implementation
*/
package org.eclipse.sensinact.gateway.security.signature.internal;
import org.bouncycastle.cert.jcajce.JcaCertStoreBuilder;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.eclipse.sensinact.gateway.common.bundle.Mediator;
import org.osgi.framework.Bundle;
import java.io.IOException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Manifest;
/**
* A SignedJarFile is a particular type of JarFile that contains cryptographic
* signature(s). Several signatures can exist in a single SignedJarFile. <br>
* A cryptographic signature of a SignedJarFile is made of several elements
* :<br>
* - the hash value for each existing resources in the manifest file,<br>
* - the SignatureFile, that contains the hash value of the manifest itself,<br>
* - the SignatureBlock, which contains a digital signature of the Signature
* File.
*
* @author pierre parrend
* @see JarFile
*/
public class SignedBundle {
private KeyStoreManager ksm = null;
private final CryptographicUtils cryptoUtils;
protected static final String METADATA_DIR = "/META-INF/";
protected static final String MF_FILE = "MANIFEST.MF";
// is usefull
private Map<String, SignatureFile> signatureFiles = null;
private Map<String, Certificate> certs = null;
private Map<String, SignatureBlock> signatureBlocks = null;
private Bundle bundle;
private Mediator mediator;
/**
* Constructor
*
* @param f
* @param cryptoUtils
* @throws IOException
*/
public SignedBundle(Mediator mediator, final Bundle bundle, final CryptographicUtils cryptoUtils) throws IOException {
this.mediator = mediator;
this.bundle = bundle;
this.cryptoUtils = cryptoUtils;
}
public List<URL> getEntries() {
List<URL> listEntries = Collections.<URL>list(this.bundle.findEntries("/", "*", true));
return listEntries;
}
/**
* A method for setting the KeyStoreManager used for analysing or creating
* this SignedJarFile.
*
* @param ksm
*/
public void setKeyStoreManager(final KeyStoreManager ksm) {
this.ksm = ksm;
}
/**
* @param string
* @return
*/
public URL getEntry(String path) {
URL entry = this.bundle.getEntry(path);
return entry;
}
/**
* @return
*/
public Manifest getManifest() {
Manifest manifest = new Manifest();
try {
manifest.read(this.getEntry(METADATA_DIR + MF_FILE).openStream());
} catch (IOException e) {
e.printStackTrace();
}
return manifest;
}
/**
* A method for retriving the signature Files of a jar File
*
* @return HashMap
*/
private Map<String, SignatureFile> getSignatureFiles() throws IOException {
if (signatureFiles == null) {
signatureFiles = new HashMap<String, SignatureFile>();
SignatureFile signedFile = null;
String signerName = "";
Enumeration<URL> entries = this.bundle.findEntries(METADATA_DIR, "*.SF", true);
URL jEntry = null;
while (entries.hasMoreElements()) {
jEntry = entries.nextElement();
signedFile = new SignatureFile(this.mediator, jEntry);
String path = jEntry.getPath();
int index = path.lastIndexOf('/');
String signer = path.substring(index + 1, path.lastIndexOf('.'));
signatureFiles.put(signer, signedFile);
}
} else {
if (this.mediator.isDebugLoggable()) {
this.mediator.debug("Better performance, data found (signatureFiles)");
}
}
return signatureFiles;
}
/**
* A method for retriving the signature Files of a jar File
*
* @return HashMap
*/
public SignatureFile getSignatureFile(final String givenSigner) throws IOException {
final String signer = SignedBundle.getSignerShortName(givenSigner);
if (this.mediator.isDebugLoggable()) {
this.mediator.debug("SignedJarFile.getSignatureFile, signer: " + signer);
}
Map<String, SignatureFile> signatures = this.getSignatureFiles();
SignatureFile file = signatures.get(signer);
if (file == null) {
if (this.mediator.isErrorLoggable()) {
this.mediator.error("SignedJarFile.getSignatureFile: " + "signature file not available.");
}
} else {
if (this.mediator.isDebugLoggable()) {
this.mediator.debug("Better performance, data found (signatureFile)");
}
}
return file;
}
/**
* A method for retrieving Public Key Certificate of a given Signer of a jar
* File
*
* @param signer
* @return Certificate.
*/
public Certificate getCertificate(final String signer) throws IOException, CMSException, GeneralSecurityException {
Certificate cert = null;
if (certs == null) {
certs = new HashMap<String, Certificate>();
}
cert = certs.get(signer);
if (cert == null) {
SignatureBlock block = this.getSignatureBlock(signer);
SignerInformationStore signers = block.getSignerInfos();
//CertStore certStore = block.getCertificatesAndCRLs("Collection","SUN");
JcaCertStoreBuilder builder = new JcaCertStoreBuilder();
builder.addCertificates(block.getCertificates());
builder.addCRLs(block.getCRLs());
builder.setProvider("SUN");
Iterator iter = signers.getSigners().iterator();
if (iter.hasNext()) {
SignerInformation signerInfo = (SignerInformation) iter.next();
try {
/*cert = (X509Certificate)this.cryptoUtils.getCertificate(
signerInfo, certStore);*/
cert = (X509Certificate) this.cryptoUtils.getCertificate(signerInfo, builder.build());
} catch (Exception e) {
e.printStackTrace();
}
certs.put(signer, cert);
}
} else {
if (this.mediator.isDebugLoggable()) {
this.mediator.debug("Better performance, data found (cert)");
}
}
return cert;
}
/**
* A method for retrieving signature from the CMS data of a signed jar.<br>
* the archive id supposed to be signed only once by the signer
*
* @param signer
* @return byte[]
* @throws Exception
*/
public byte[] getSignatureFromBlock(final String signer) throws IOException, CMSException {
final CMSSignedData pkcs7 = this.getSignatureBlockAsCMS(signer);
return SignatureBlock.getSignatureFromSignedData(pkcs7);
}
public SignatureBlock getSignatureBlock(final String signer) throws IOException, CMSException {
SignatureBlock block = null;
if (signatureBlocks == null) {
signatureBlocks = new HashMap<String, SignatureBlock>();
} else {
block = signatureBlocks.get(signer);
}
if (block == null) {
block = SignatureBlock.getInstance(mediator, this, signer);
signatureBlocks.put(signer, block);
} else {
if (this.mediator.isDebugLoggable()) {
this.mediator.debug("Better performance, data found (block)");
}
}
return block;
}
public CMSSignedData getSignatureBlockAsCMS(final String signer) throws IOException, CMSException {
return new CMSSignedData(this.getSignatureBlock(signer).getEncoded());
}
/**
* A method for retrieving the certificates of the different signers of the
* SignedJarFile.
*
* @return List<Certificate>
* @throws NoSuchAlgorithmException
* @throws CMSException
* @throws CertStoreException
* @throws NoSuchProviderException
* @throws IOException
*/
public Map<String, Certificate> getCertificates() throws CMSException, IOException, GeneralSecurityException {
Map<String, Certificate> certs = new HashMap<String, Certificate>();
Set<String> signers = this.getSignatureFiles().keySet();
Iterator<String> iter = signers.iterator();
String signer = "";
Certificate cert = null;
while (iter.hasNext()) {
signer = (String) iter.next();
cert = this.getCertificate(signer);
certs.put(signer, cert);
}
return certs;
}
/**
* A method for retrieving the list of valid certificates Validity of
* certificates should be handled by the KeyStoreManager
*
* @param String
* @return List<Certificate>
*/
private Map<String, Certificate> extractValidCertificates(final String passwd) throws CMSException, IOException, GeneralSecurityException {
final Map<String, Certificate> validCertificates = new HashMap<String, Certificate>();
// getCertificates for the different signers
final Map<String, Certificate> certificates = this.getCertificates();
// for each certificate
final Iterator iter = certificates.entrySet().iterator();
Certificate currentCert = null;
boolean currentCertificateValid = false;
String signer = "";
Map.Entry entry;
while (iter.hasNext()) {
entry = (Map.Entry) iter.next();
signer = (String) entry.getKey();
currentCert = (Certificate) certificates.get(signer);
currentCertificateValid = this.isCertValid(currentCert, passwd);
if (currentCertificateValid) {
validCertificates.put(signer, ((X509Certificate) currentCert));
}
}
return validCertificates;
}
/*
* Beware not to consider a certificate as valid just because not password
* is given
*/
private boolean isCertValid(final Certificate currentCert, final String passwd) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {
boolean certificateValid = false;
if (ksm != null) {
if (passwd == null) {
certificateValid = ksm.isTemporallyOK(currentCert);
} else {
certificateValid = ksm.isValid(currentCert);
}
}
return certificateValid;
}
/**
* A method for retrieving valid certificates of signers for this
* SignedJarFile.
*
* @param passwd
* @return List<Certificate>
* @throws KeyStoreException
* @throws CertStoreException
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws CMSException
* @throws CertificateException
*/
public Map<String, Certificate> getValidCertificates(final String passwd) throws IOException, CMSException, GeneralSecurityException {
Map<String, Certificate> validCertificates = null;
if ("".equals(passwd)) {
validCertificates = new HashMap<String, Certificate>();
} else {
validCertificates = this.extractValidCertificates(passwd);
}
return validCertificates;
}
/**
* A method for checking coherence of a Jar File signature. Verification
* occurs in four steps:<br>
* - checking whether the resources in a signed archive stream are in a
* valid order,<br>
* - checking whether the Signature Block File is valid (that is to say if
* the signature matches with Signature File and given public key of
* signer),<br>
* - checking whether the Signature File is valid (that is to say whether
* given Manifest digest matches real Manifest of the the archive),<br>
* - checking whether manifest is valid against the archive (that is to say
* whether given file digests match real file digests).<br>
* Current restriction is that multiple signer is not supported.
*
* @param cert
* @return boolean
*/
public boolean checkCoherence(final String signer, final Certificate cert, String algo) throws Exception {
return new SignedBundleChecker(mediator).checkCoherence(this, signer, cert, cryptoUtils, algo);
}
public static String getSignerShortName(final String givenSigner) {
String signer = "";
if (givenSigner.length() > 8) {
signer = givenSigner.substring(0, 8);
} else {
signer = givenSigner;
}
return signer;
}
}