Skip to content

Instantly share code, notes, and snippets.

@sanekesv
Created January 24, 2019 10:37
Show Gist options
  • Save sanekesv/02e234e7bc0b9cf3835696ead6d9b3c6 to your computer and use it in GitHub Desktop.
Save sanekesv/02e234e7bc0b9cf3835696ead6d9b3c6 to your computer and use it in GitHub Desktop.
package zags.electronic_sign;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.security.GeneralSecurityException;
import java.security.InvalidParameterException;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.*;
import java.util.*;
import java.util.stream.Collectors;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.*;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.util.Store;
import org.bouncycastle.util.encoders.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import security.service.CertificateService;
import security.service.KeystoreConstants;
import ru.cg.webbpm.modules.core.app_info.api.property.StringProperty;
import static org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
/**
* @author Alexander Ermolaev
*/
@Component(value = "detachedSignCertificatService")
public class CertificateServiceDetachedSignImpl implements CertificateService {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Override
public String verifySignature(String data, String signature) throws IOException {
LOGGER.info("Validating signers and certificates");
if (signature == null || signature.isEmpty()) {
throw new AuthenticationCredentialsNotFoundException("Digital signature was not provided");
}
if (Security.getProvider("BC") == null)
Security.addProvider(new BouncyCastleProvider());
try {
InputStream is = new ByteArrayInputStream(signature.getBytes());
CMSSignedData cmsSignedData = new CMSSignedData(new CMSProcessableByteArray(data.getBytes()), is);
Store store = cmsSignedData.getCertificates();
SignerInformationStore signerInformationStore = cmsSignedData.getSignerInfos();
Collection<SignerInformation> signers = signerInformationStore.getSigners();
X509Certificate cert = null;
List<Certificate> certList = new ArrayList<>();
for (SignerInformation signerInfo : signers) {
//verify each certificate of signature
Collection certHolderCollection = store.getMatches(signerInfo.getSID());
for (Object certHolder : certHolderCollection) {
cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate((X509CertificateHolder) certHolder);
JcaSimpleSignerInfoVerifierBuilder signerInfoVerifierBuilder = new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC");
if (!doVerify(cmsSignedData, cert, signerInfo, signerInfoVerifierBuilder))
throw new BadCredentialsException("Not valid digital signature or certificate");
logCertificate(cert, "Verified certificate");
certList.add(cert);
}
}
//extracting data from certificate
Object content = cmsSignedData.getSignedContent().getContent();
String signedContent = new String((byte[]) content);
if (!compareSignedDataAndLogin(cert, signedContent))
throw new BadCredentialsException("No valid signed data");
verifyCertificatesChain(certList);
return signedContent;
}
catch (CMSException | OperatorCreationException | GeneralSecurityException e) {
LOGGER.error(e.getMessage());
throw new BadCredentialsException("Not valid digital signature or certificate");
}
catch (IOException e) {
LOGGER.error(e.getMessage());
throw e;
}
}
@Override
public String verifySignature(String signature) throws IOException {
return null;
}
// certificates and signatures verification
private boolean doVerify(CMSSignedData cmsSignedData, X509Certificate cert, SignerInformation signerInfo, JcaSimpleSignerInfoVerifierBuilder signerInfoVerifierBuilder)
throws CertificateException, CMSException, OperatorCreationException {
SignerInformationVerifier verifier = signerInfoVerifierBuilder.build(cert);
//certificates validity check
cert.checkValidity();
return signerInfo.verify(verifier) && cmsSignedData.verifySignatures(sid -> verifier);
}
// verify certificate chain using trusted anchors from local keystore
// method throws exception if chain is not valid
private void verifyCertificatesChain(List<Certificate> certList) throws GeneralSecurityException, IOException {
LOGGER.info("Validating certificates chain");
CertPath certPath = CertificateFactory.getInstance("X.509", "BC")
.generateCertPath(certList);
// Load the keystore file
FileInputStream inputStream = new FileInputStream(getSystemProperty(KeystoreConstants.KEYSTORE_LOCATION.propertyName()));
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
String password = getSystemProperty(KeystoreConstants.KEYSTORE_PASSWORD.propertyName());
if (password == null) {
throw new InvalidParameterException("System property " + KeystoreConstants.KEYSTORE_PASSWORD.propertyName() + " is not defined");
}
keystore.load(inputStream, password.toCharArray());
// This class retrieves the most-trusted CAs from the keystore
PKIXParameters params = new PKIXParameters(keystore);
// Get the set of trust anchors, which contain the most-trusted CA certificates
Set<TrustAnchor> trustAnchors = new HashSet<>();
Iterator trustAnchorIterator = params.getTrustAnchors().iterator();
while (trustAnchorIterator.hasNext()) {
TrustAnchor trustAnchor = (TrustAnchor) trustAnchorIterator.next();
X509Certificate trustedCert = trustAnchor.getTrustedCert();
trustAnchors.add(new TrustAnchor(trustedCert, null));
logCertificate(trustedCert, "Added trusted certificate");
}
CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX", "BC");
PKIXParameters param = new PKIXParameters(trustAnchors);
param.setRevocationEnabled(false);
param.setDate(new Date());
certPathValidator.validate(certPath, param);
}
private String getSystemProperty(String name) {
StringProperty property = new StringProperty(name, false, null);
return property.value();
}
//email and signed data comparison
private boolean compareSignedDataAndLogin(X509Certificate cert, String signedContent) {
if (isBlank(signedContent)) {
return false;
}
Map<String, String> certificateFields = Arrays.stream(
cert
.getSubjectDN()
.getName()
.split(",")
)
.map(s -> s.split("="))
.filter(s -> s.length == 2)
.collect(Collectors.toMap(s -> s[0], s -> s[1]));
//E - field which holds email address
String email = certificateFields.get("E");
if (isBlank(email)) {
return false;
}
//remove domain postfix and compare
return email.split("@")[0].equals(signedContent);
}
private boolean isBlank(String value) {
return value == null || value.isEmpty();
}
private static void logCertificate(X509Certificate cert, String title) {
LOGGER.info(
new StringBuilder()
.append(title + ": ")
.append("serial number = " + cert.getSerialNumber())
.append("; subject DN = " + cert.getSubjectDN())
.append("; issuer DN = " + cert.getIssuerDN())
.append("; algorithm name =" + cert.getSigAlgName())
.toString());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment