org.beigesoft.ajetty.crypto.CryptoService Maven / Gradle / Ivy
Show all versions of a-jetty-base Show documentation
/*
BSD 2-Clause License
Copyright (c) 2019, Beigesoft™
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.beigesoft.ajetty.crypto;
import java.math.BigInteger;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.BufferedInputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.security.SecureRandom;
import java.util.Date;
import java.util.Calendar;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Locale;
import java.util.ResourceBundle;
import java.security.Security;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.cert.X509v1CertificateBuilder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS12PfxPdu;
import org.bouncycastle.pkcs.PKCS12PfxPduBuilder;
import org.bouncycastle.pkcs.PKCS12SafeBag;
import org.bouncycastle.pkcs.PKCS12SafeBagBuilder;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.pkcs.jcajce.JcaPKCS12SafeBagBuilder;
import org.bouncycastle.pkcs.jcajce.JcePKCS12MacCalculatorBuilder;
import org.bouncycastle.pkcs.jcajce.JcePKCSPBEOutputEncryptorBuilder;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.operator.OutputEncryptor;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* It serves A-Jetty with encryption features.
* It's based on Bounce Castle example.
*/
public class CryptoService implements ICryptoService {
/**
* I18N.
**/
private ResourceBundle messages;
/**
* Only constructor.
**/
public CryptoService() {
try {
this.messages = ResourceBundle.getBundle("MessagesCrypto");
} catch (Exception e) {
try {
Locale locale = new Locale("en", "US");
this.messages = ResourceBundle.getBundle("MessagesCrypto", locale);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
/**
* Check if password strong.
* It implements logic:
* At least 15 letters and digits!
* 60% of them must be different!
* At least 50% of them must be letters!
* At least 3 of them must be digits!
* No containing qwerty, 12345, admin, user etc!
* @param pPassword Password
* @return NULL if strong, otherwise message.
**/
@Override
public final String isPasswordStrong(final char[] pPassword) {
if (pPassword == null || pPassword.length < 15) {
return getMsg("Password15");
}
String passw = new String(pPassword).toLowerCase();
if (passw.contains("qwert") || passw.contains("qwaszx")
|| passw.contains("qweasd") || passw.contains("qazwsx")
|| passw.contains("wsxedc") || passw.contains("wqsaxz")
|| passw.contains("ewqdsa") || passw.contains("zaqxsw")
|| passw.contains("xswzaq") || passw.contains("qscwdv")
|| passw.contains("csqvdw") || passw.contains("zaxqsc")
|| passw.contains("qscax") || passw.contains("csqxa")
|| passw.contains("trewq") || passw.contains("asdfg")
|| passw.contains("zxcvb") || passw.contains("bvcxz")
|| passw.contains("gfdsa")) {
return getMsg("noQwerty");
} else if (passw.contains("raccooneatstone")
|| passw.contains("nraccooteaeston")) {
return getMsg("noDemoPassw");
} else if (passw.contains("2345") || passw.contains("admin")
|| passw.contains("user") || passw.contains("5432")
|| passw.contains("5678") || passw.contains("9876")
|| passw.contains("password")) {
return getMsg("noAdmin12345");
}
HashSet chars = new HashSet();
ArrayList digits = new ArrayList();
ArrayList letters = new ArrayList();
for (char ch : pPassword) {
if (!Character.isLetterOrDigit(ch)) {
return getMsg("letterOrDig");
}
if (Character.isDigit(ch)) {
digits.add(ch);
} else {
letters.add(ch);
}
chars.add(ch);
}
double allLn = pPassword.length;
double lettersLn = letters.size();
double distinctLn = chars.size();
if (lettersLn / allLn < 0.49999999999) {
return getMsg("lettersAtLeast50pr");
}
if (distinctLn / allLn < 0.59999999999) {
return getMsg("distinct60pr");
}
if (digits.size() < 3) {
return getMsg("atLeast3digits");
}
return null;
}
/**
* Generates RSA pair for HTTPS and file exchange,
* then makes certificates for them,
* then creates Key Store and save them into it.
* Keystore name is ajettykeystore.[pAjettyIn]
* Validity period is 10 years since now.
* It uses standard aliases prefixes:
*
* - AJettyRoot[pAjettyIn] - root certificate alias.
* But all self-signed CA is version 3, so it's omitted.
* - AJettyCA[pAjettyIn] - self -signed CA certificate alias V3.
* - AJettyHttps[pAjettyIn] - HTTPS certificate/private key alias
* - AJettyFileExch[pAjettyIn] - File exchanger certificate/private
* key alias
*
*
* @param pFilePath path, if null - use current
* @param pAjettyIn A-Jetty instance number.
* @param pPassw password
* @throws Exception an Exception
*/
@Override
public final void createKeyStoreWithCredentials(final String pFilePath,
final int pAjettyIn, final char[] pPassw) throws Exception {
File pks12File;
if (pFilePath == null) {
pks12File = new File("ajettykeystore." + pAjettyIn);
} else {
pks12File = new File(pFilePath + File.separator
+ "ajettykeystore." + pAjettyIn);
}
if (pks12File.exists()) {
throw new Exception("File already exist - " + pks12File.getPath());
}
// generate key pairs:
KeyPairGenerator kpGenRsa = KeyPairGenerator.getInstance("RSA", "BC");
kpGenRsa.initialize(2048, new SecureRandom());
KeyPair kpHttps = kpGenRsa.generateKeyPair();
kpGenRsa.initialize(2048, new SecureRandom());
KeyPair kpFileExch = kpGenRsa.generateKeyPair();
// generate certificates:
Calendar cal = Calendar.getInstance();
Date start = cal.getTime();
cal.add(Calendar.YEAR, 10);
Date end = cal.getTime();
// CA:
kpGenRsa.initialize(2048, new SecureRandom());
KeyPair kpCa = kpGenRsa.generateKeyPair();
String x500dn = "CN=A-Jetty" + pAjettyIn
+ " CA, OU=A-Jetty" + pAjettyIn + " CA, O=A-Jetty"
+ pAjettyIn + " CA, C=RU";
X509Certificate caCert = buildCaCertSelfSign(kpCa, x500dn, start, end);
// HTTPS:
x500dn = "CN=localhost, OU=A-Jetty" + pAjettyIn + " HTTPS, O=A-Jetty"
+ pAjettyIn + " HTTPS, C=RU";
X509Certificate httpsCert = buildLocalhostHttpsCert(kpHttps.getPublic(),
kpCa.getPrivate(), caCert, 2, x500dn, start, end);
// File exchanger:
x500dn = "CN=A-Jetty" + pAjettyIn
+ " File Exchanger, OU=A-Jetty" + pAjettyIn + " File Exchanger, O=A-Jetty"
+ pAjettyIn + " File Exchanger, C=RU";
X509Certificate fileExchCert = buildEndEntityCert(kpFileExch.getPublic(),
kpCa.getPrivate(), caCert, 3, x500dn, start, end);
// save to keystore:
JcePKCSPBEOutputEncryptorBuilder jcePcEb =
new JcePKCSPBEOutputEncryptorBuilder(NISTObjectIdentifiers.id_aes256_CBC);
jcePcEb.setProvider("BC");
OutputEncryptor encOut = jcePcEb.build(pPassw);
PKCS12SafeBagBuilder caCrtBagBld = new JcaPKCS12SafeBagBuilder(caCert);
caCrtBagBld.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute,
new DERBMPString("AJettyCa" + pAjettyIn));
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
PKCS12SafeBagBuilder httpsCrBgBr = new JcaPKCS12SafeBagBuilder(httpsCert);
httpsCrBgBr.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute,
new DERBMPString("AJettyHttps" + pAjettyIn));
SubjectKeyIdentifier skiHttps = extUtils
.createSubjectKeyIdentifier(httpsCert.getPublicKey());
httpsCrBgBr.addBagAttribute(PKCS12SafeBag.localKeyIdAttribute, skiHttps);
PKCS12SafeBagBuilder httpsKbb =
new JcaPKCS12SafeBagBuilder(kpHttps.getPrivate(), encOut);
httpsKbb.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute,
new DERBMPString("AJettyHttps" + pAjettyIn));
httpsKbb.addBagAttribute(PKCS12SafeBag.localKeyIdAttribute, skiHttps);
PKCS12SafeBagBuilder fileExchCrBgBr =
new JcaPKCS12SafeBagBuilder(fileExchCert);
fileExchCrBgBr.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute,
new DERBMPString("AJettyFileExch" + pAjettyIn));
SubjectKeyIdentifier skiFileExch = extUtils
.createSubjectKeyIdentifier(fileExchCert.getPublicKey());
fileExchCrBgBr
.addBagAttribute(PKCS12SafeBag.localKeyIdAttribute, skiFileExch);
PKCS12SafeBagBuilder fileExchKbb =
new JcaPKCS12SafeBagBuilder(kpFileExch.getPrivate(), encOut);
fileExchKbb.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute,
new DERBMPString("AJettyFileExch" + pAjettyIn));
fileExchKbb.addBagAttribute(PKCS12SafeBag.localKeyIdAttribute, skiFileExch);
PKCS12PfxPduBuilder builder = new PKCS12PfxPduBuilder();
builder.addData(httpsKbb.build());
builder.addData(fileExchKbb.build());
builder.addEncryptedData(new JcePKCSPBEOutputEncryptorBuilder(
PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC).setProvider("BC")
.build(pPassw), new PKCS12SafeBag[] {httpsCrBgBr.build(),
fileExchCrBgBr.build(), caCrtBagBld.build()});
JcePKCS12MacCalculatorBuilder jmcb = new JcePKCS12MacCalculatorBuilder();
jmcb.setProvider("BC");
PKCS12PfxPdu pfx = builder.build(jmcb, pPassw);
FileOutputStream pfxOut = null;
try {
pfxOut = new FileOutputStream(pks12File);
// make sure we don't include indefinite length encoding
pfxOut.write(pfx.getEncoded(ASN1Encoding.DL));
pfxOut.flush();
} finally {
if (pfxOut != null) {
pfxOut.close();
}
}
}
/**
* Calculate SHA1 for given file.
* @param pFile file
* @return SHA1 bytes array
* @throws Exception an Exception
*/
@Override
public final byte[] calculateSha1(final File pFile) throws Exception {
BufferedInputStream bis = null;
Digest sha1 = new SHA1Digest();
try {
bis = new BufferedInputStream(new FileInputStream(pFile));
byte[] buffer = new byte[1024];
while (bis.read(buffer) >= 0) {
sha1.update(buffer, 0, buffer.length);
}
} finally {
if (bis != null) {
try {
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
byte[] digest = new byte[sha1.getDigestSize()];
sha1.doFinal(digest, 0);
return digest;
}
/**
* Initialize (cryptop-rovider).
* @throws Exception an Exception
*/
@Override
public final void init() throws Exception {
Security.removeProvider(getProviderName());
Security.addProvider(new BouncyCastleProvider());
}
/**
* Get crypto-provider name.
* @return crypto-provider name
**/
@Override
public final String getProviderName() {
return "BC";
}
/**
* To override odd behavior standard I18N.
* @param pKey key
* @return i18n message
**/
public final String getMsg(final String pKey) {
try {
return this.messages.getString(pKey);
} catch (Exception e) {
return "[" + pKey + "]";
}
}
/**
* Build A-Jetty Root V1 certificate.
* @param pKpRoot Root key pair
* @param pX500dn X.500 distinguished name.
* @param pStart date from.
* @param pEnd date to.
* @throws Exception an Exception
* @return root certificate
*/
public final X509Certificate buildRootCert(final KeyPair pKpRoot,
final String pX500dn, final Date pStart, final Date pEnd) throws Exception {
X509v1CertificateBuilder certBldr = new JcaX509v1CertificateBuilder(
new X500Name(pX500dn), BigInteger.valueOf(1), //#1
pStart, pEnd, new X500Name(pX500dn), pKpRoot.getPublic());
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
.setProvider("BC").build(pKpRoot.getPrivate());
return new JcaX509CertificateConverter().setProvider("BC")
.getCertificate(certBldr.build(signer));
}
/**
* Build A-Jetty CA intermediate V3 certificate
* to use for creating (signing) end entities certificates.
* @param pCaPk CA PK
* @param pRootSk root private key
* @param pRootCert root certificate
* @param pX500dn X.500 distinguished name.
* @param pStart date from.
* @param pEnd date to.
* @throws Exception an Exception
* @return CA certificate
*/
public final X509Certificate buildCaCert(final PublicKey pCaPk,
final PrivateKey pRootSk, final X509Certificate pRootCert,
final String pX500dn, final Date pStart,
final Date pEnd) throws Exception {
X509v3CertificateBuilder certBldr = new JcaX509v3CertificateBuilder(
pRootCert.getSubjectX500Principal(), BigInteger.valueOf(2), //#2
pStart, pEnd, new X500Principal(pX500dn), pCaPk);
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
certBldr.addExtension(Extension.authorityKeyIdentifier, false, extUtils
.createAuthorityKeyIdentifier(pRootCert)).addExtension(Extension
.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(pCaPk))
.addExtension(Extension.basicConstraints, true,
new BasicConstraints(0))
.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage
.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign));
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
.setProvider("BC").build(pRootSk);
return new JcaX509CertificateConverter().setProvider("BC")
.getCertificate(certBldr.build(signer));
}
/**
* Build A-Jetty self signing CA intermediate V3 certificate
* to use for creating (signing) end entities certificates.
* @param pKpCa CA key pair
* @param pX500dn X.500 distinguished name.
* @param pStart date from.
* @param pEnd date to.
* @throws Exception an Exception
* @return CA certificate
*/
public final X509Certificate buildCaCertSelfSign(final KeyPair pKpCa,
final String pX500dn, final Date pStart, final Date pEnd) throws Exception {
X509v3CertificateBuilder certBldr = new JcaX509v3CertificateBuilder(
new X500Principal(pX500dn), BigInteger.valueOf(1), //#1
pStart, pEnd, new X500Principal(pX500dn), pKpCa.getPublic());
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
certBldr.addExtension(Extension
.subjectKeyIdentifier, false, extUtils
.createSubjectKeyIdentifier(pKpCa.getPublic()));
certBldr.addExtension(Extension.basicConstraints, true,
new BasicConstraints(0));
certBldr.addExtension(Extension.keyUsage, true,
new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign));
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
.setProvider("BC").build(pKpCa.getPrivate());
return new JcaX509CertificateConverter().setProvider("BC")
.getCertificate(certBldr.build(signer));
}
/**
* Build end entity V3 certificate.
* @param pEntityPk entity PK
* @param pCaSk CA private key
* @param pCaCert CA certificate
* @param pSn serial number
* @param pX500dn X.500 distinguished name.
* @param pStart date from.
* @param pEnd date to.
* @throws Exception an Exception
* @return end user certificate
*/
public final X509Certificate buildEndEntityCert(final PublicKey pEntityPk,
final PrivateKey pCaSk, final X509Certificate pCaCert, final int pSn,
final String pX500dn, final Date pStart,
final Date pEnd) throws Exception {
X509v3CertificateBuilder certBldr = new JcaX509v3CertificateBuilder(
pCaCert.getSubjectX500Principal(), BigInteger.valueOf(pSn),
pStart, pEnd, new X500Principal(pX500dn), pEntityPk);
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
certBldr.addExtension(Extension.authorityKeyIdentifier, false, extUtils
.createAuthorityKeyIdentifier(pCaCert)).addExtension(Extension
.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(
pEntityPk)).addExtension(Extension.basicConstraints, true,
new BasicConstraints(false)).addExtension(Extension.keyUsage, true,
new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
.setProvider("BC").build(pCaSk);
return new JcaX509CertificateConverter().setProvider("BC")
.getCertificate(certBldr.build(signer));
}
/**
* Build localhost HTTPS V3 certificate.
* @param pEntityPk entity PK
* @param pCaSk CA private key
* @param pCaCert CA certificate
* @param pSn serial number
* @param pX500dn X.500 distinguished name.
* @param pStart date from.
* @param pEnd date to.
* @throws Exception an Exception
* @return end user certificate
*/
public final X509Certificate buildLocalhostHttpsCert(
final PublicKey pEntityPk, final PrivateKey pCaSk,
final X509Certificate pCaCert, final int pSn,
final String pX500dn, final Date pStart,
final Date pEnd) throws Exception {
X509v3CertificateBuilder certBldr = new JcaX509v3CertificateBuilder(
pCaCert.getSubjectX500Principal(), BigInteger.valueOf(pSn),
pStart, pEnd, new X500Principal(pX500dn), pEntityPk);
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
certBldr.addExtension(Extension.authorityKeyIdentifier, false, extUtils
.createAuthorityKeyIdentifier(pCaCert));
certBldr.addExtension(Extension.subjectKeyIdentifier, false, extUtils
.createSubjectKeyIdentifier(pEntityPk));
certBldr.addExtension(Extension.basicConstraints, true,
new BasicConstraints(false));
certBldr.addExtension(Extension.extendedKeyUsage, false,
new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth));
GeneralName dns = new GeneralName(GeneralName.dNSName, "localhost");
GeneralName ip = new GeneralName(GeneralName.iPAddress, "127.0.0.1");
GeneralNames dnsIp = new GeneralNames(new GeneralName[] {dns, ip});
certBldr.addExtension(Extension.subjectAlternativeName, false, dnsIp);
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
.setProvider("BC").build(pCaSk);
return new JcaX509CertificateConverter().setProvider("BC")
.getCertificate(certBldr.build(signer));
}
//Simple getters and setters:
/**
* Getter for messages.
* @return ResourceBundle
**/
public final ResourceBundle getMessages() {
return this.messages;
}
/**
* Setter for messages.
* @param pMessages reference
**/
public final void setMessages(final ResourceBundle pMessages) {
this.messages = pMessages;
}
}