Created
January 24, 2019 10:37
-
-
Save sanekesv/02e234e7bc0b9cf3835696ead6d9b3c6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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