Java tutorial
/* * Copyright (c) 2014 Berner Fachhochschule, Switzerland. * Bern University of Applied Sciences, Engineering and Information Technology, * Research Institute for Security in the Information Society, E-Voting Group, * Biel, Switzerland. * * Project UniCert. * * Distributable under GPL license. * See terms of license at gnu.org. */ package ch.bfh.unicert.issuer; import ch.bfh.uniboard.clientlib.BoardErrorException; import ch.bfh.uniboard.clientlib.PostException; import ch.bfh.uniboard.clientlib.PostHelper; import ch.bfh.unicert.issuer.cryptography.CryptographicSetup; import ch.bfh.unicert.issuer.cryptography.DiscreteLogSetup; import ch.bfh.unicert.issuer.cryptography.RsaSetup; import ch.bfh.unicert.issuer.exceptions.CertificateCreationException; import ch.bfh.unicert.issuer.util.CertificateHelper; import ch.bfh.unicert.issuer.util.ConfigurationHelper; import ch.bfh.unicert.issuer.util.ConfigurationHelperImpl; import ch.bfh.unicert.issuer.util.ExtensionOID; import ch.bfh.unicrypt.crypto.proofsystem.challengegenerator.classes.FiatShamirSigmaChallengeGenerator; import ch.bfh.unicrypt.crypto.proofsystem.classes.PreimageProofSystem; import ch.bfh.unicrypt.crypto.schemes.signature.classes.RSASignatureScheme; import ch.bfh.unicrypt.helper.Alphabet; import ch.bfh.unicrypt.helper.converter.classes.ConvertMethod; import ch.bfh.unicrypt.helper.converter.classes.biginteger.FiniteByteArrayToBigInteger; import ch.bfh.unicrypt.helper.converter.classes.bytearray.BigIntegerToByteArray; import ch.bfh.unicrypt.helper.converter.classes.bytearray.ByteArrayToByteArray; import ch.bfh.unicrypt.helper.converter.classes.bytearray.StringToByteArray; import ch.bfh.unicrypt.helper.hash.HashAlgorithm; import ch.bfh.unicrypt.helper.hash.HashMethod; import ch.bfh.unicrypt.math.algebra.concatenative.classes.StringElement; import ch.bfh.unicrypt.math.algebra.concatenative.classes.StringMonoid; import ch.bfh.unicrypt.math.algebra.dualistic.classes.ZMod; import ch.bfh.unicrypt.math.algebra.general.classes.Triple; import ch.bfh.unicrypt.math.algebra.general.interfaces.Element; import ch.bfh.unicrypt.math.function.classes.GeneratorFunction; import ch.bfh.unicrypt.math.function.interfaces.Function; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.nio.ByteOrder; import java.nio.charset.Charset; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PublicKey; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.interfaces.DSAPublicKey; import java.security.interfaces.RSAPrivateCrtKey; import java.security.interfaces.RSAPublicKey; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import javax.ejb.Startup; import javax.ejb.Stateless; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DERBitString; import org.bouncycastle.asn1.DERBoolean; import org.bouncycastle.asn1.DERInteger; import org.bouncycastle.asn1.DERNull; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.DEROutputStream; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.DigestInfo; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x509.TBSCertificateStructure; import org.bouncycastle.asn1.x509.Time; import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator; import org.bouncycastle.asn1.x509.X509CertificateStructure; import org.bouncycastle.asn1.x509.X509Extension; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.asn1.x509.X509Name; import org.bouncycastle.asn1.x509.X509ObjectIdentifiers; import org.bouncycastle.crypto.AsymmetricBlockCipher; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.digests.SHA1Digest; import org.bouncycastle.crypto.encodings.PKCS1Encoding; import org.bouncycastle.crypto.engines.RSAEngine; import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; import org.bouncycastle.jce.PrincipalUtil; import org.bouncycastle.jce.provider.X509CertificateObject; import sun.security.provider.DSAPublicKeyImpl; import sun.security.rsa.RSAPublicKeyImpl; /** * Implements the certificate issuence service used by the authentication component of the unicert webclient. Upon * request constructs a certificate for the requestor and returns it * * @author Eric Dubuis <eric.dubuis@bfh.ch> * @author Reto Koenig <reto.koenig@bfh.ch> * @author Philmon von Bergen <philemon.vonbergen@bfh.ch> */ @Stateless @Startup public class CertificateIssuerBean implements CertificateIssuer { private static final int MIN_RSA_SIZE = 1010; private static final int MIN_DLOG_SIZE = 1010; private static final String ATTRIBUTE_NAME_PUBLICKEY = "publickey"; private static final String ATTRIBUTE_NAME_SIG = "signature"; private static final String ATTRIBUTE_NAME_BOARD_SIG = "boardSignature"; private static final String GROUPED = "group"; private static final String SECTIONED = "section"; protected static final HashMethod HASH_METHOD = HashMethod.getInstance(HashAlgorithm.SHA256, ConvertMethod.getInstance(BigIntegerToByteArray.getInstance(ByteOrder.BIG_ENDIAN), ByteArrayToByteArray.getInstance(false), StringToByteArray.getInstance(Charset.forName("UTF-8"))), HashMethod.Mode.RECURSIVE); private static final Logger logger = Logger.getLogger(CertificateIssuerBean.class.getName()); @Override public Certificate createCertificate(CryptographicSetup cs, IdentityData idData, String applicationIdentifier, String[] roles) throws CertificateCreationException { PublicKey pk = null; logger.log(Level.INFO, "Certificate requested for {0}", idData.getCommonName()); //Check if role is realistic if (roles == null) { throw new CertificateCreationException("200 Unknow role"); } //Check validity of crypto setup if (cs instanceof RsaSetup) { RsaSetup setup = (RsaSetup) cs; if (setup.getN().bitLength() < (setup.getSize() - (setup.getSize() * 5 / 1000) - 1) || setup.getN().bitLength() < MIN_RSA_SIZE) { logger.log(Level.SEVERE, "Illegal cryptographic setup: minimal size not respected"); throw new CertificateCreationException( "221 Illegal cryptographic setup: minimal size not respected. N was " + setup.getN().bitLength() + " long, but " + (setup.getSize() - (setup.getSize() * 5 / 1000) - 1) + " or " + MIN_RSA_SIZE + " is required."); } logger.log(Level.INFO, "Valid RSA setup"); //verify signature ZMod rsaGroup = ZMod.getInstance(setup.getN()); RSASignatureScheme rsa = RSASignatureScheme.getInstance(StringMonoid.getInstance(Alphabet.UNICODE_BMP), rsaGroup, HashMethod.getInstance(HashAlgorithm.SHA256, ConvertMethod.getInstance(BigIntegerToByteArray.getInstance(ByteOrder.BIG_ENDIAN), StringToByteArray.getInstance(Charset.forName("UTF-8"))))); Element publicKey = rsaGroup.getElement(setup.getPublicKey()); Element signature = rsaGroup.getElement(setup.getSignature()); Element message = rsa.getMessageSpace().getElement(setup.getSignatureOtherInput()); if (!rsa.verify(publicKey, message, signature).getValue()) { logger.log(Level.SEVERE, "Signature invalid"); throw new CertificateCreationException("222 Signature invalid"); } logger.log(Level.INFO, "Signature valid"); pk = createRSAPublicKey(setup.getPublicKey(), setup.getN()); } else if (cs instanceof DiscreteLogSetup) { DiscreteLogSetup setup = (DiscreteLogSetup) cs; if (setup.getP().getValue().bitLength() < MIN_DLOG_SIZE || !setup.getGenerator().isGenerator()) { logger.log(Level.SEVERE, "Illegal cryptographic setup: minimal size not respected"); throw new CertificateCreationException( "221 Illegal cryptographic setup: minimal size not respected"); } logger.log(Level.INFO, "Valid DLOG setup"); Function func = GeneratorFunction.getInstance(setup.getGenerator()); StringElement s = StringMonoid.getInstance(Alphabet.UNICODE_BMP).getElement(setup.getProofOtherInput()); HashMethod hashMethod = HashMethod .getInstance(HashAlgorithm.SHA256, ConvertMethod.getInstance(BigIntegerToByteArray.getInstance(ByteOrder.BIG_ENDIAN), StringToByteArray.getInstance(Charset.forName("UTF-8"))), HashMethod.Mode.RECURSIVE); FiniteByteArrayToBigInteger byteArrayConverter = FiniteByteArrayToBigInteger .getInstance(HashAlgorithm.SHA256.getHashLength()); FiatShamirSigmaChallengeGenerator scg = FiatShamirSigmaChallengeGenerator.getInstance(setup.getG_q(), setup.getG_q(), setup.getZ_q(), s, hashMethod, byteArrayConverter); PreimageProofSystem pips = PreimageProofSystem.getInstance(scg, func); //verify proof Triple proof = Triple.getInstance(setup.getG_q().getElement(setup.getProofCommitment()), setup.getZ_q().getElement(setup.getProofChallenge()), setup.getZ_q().getElement(setup.getProofResponse())); if (!pips.verify(proof, setup.getPublicKey())) { logger.log(Level.SEVERE, "Proof incorrect"); throw new CertificateCreationException("223 Proof incorrect"); } logger.log(Level.INFO, "Proof correct"); pk = createDSAPublicKey(setup.getPublicKey().getBigInteger(), setup.getP().getValue(), setup.getZ_q().getOrder(), setup.getGenerator().getBigInteger()); } Calendar expiry = getExpiryDate(getConfigurationHelper().getValidityYears()); Certificate cert = createClientCertificate(idData, getConfigurationHelper().getIssuerCertificate(), createIssuerCipherParams(getConfigurationHelper().getPrivateRSAKey()), pk, expiry, applicationIdentifier, roles); logger.log(Level.INFO, "Certificate created"); //post message on UniBoard if corresponding JNDI parameter is defined if (getConfigurationHelper().getUniBoardServiceURL() != null) { postOnUniBoard(cert, getConfigurationHelper().getUniBoardWsdlURL(), getConfigurationHelper().getUniBoardServiceURL(), getConfigurationHelper().getUniBoardSection(), (RSAPublicKey) getConfigurationHelper().getIssuerCertificate().getPublicKey(), getConfigurationHelper().getPrivateRSAKey()); } return cert; } /** * Helper method managing the publication of certificate on a UniBoard instance * * @param cert the certificate to publish * @param endpointUrl the url to access UniBoard * @param section the section on UniBoard where the post must be published * @param publicKey the public key that must be sent with the post * @param privateKey the private key used to create post signature * @throws CertificateCreationException if an error occured during publication */ protected void postOnUniBoard(Certificate cert, String wsdlUrl, String endpointUrl, String section, RSAPublicKey publicKey, RSAPrivateCrtKey privateKey) throws CertificateCreationException { try { PostHelper ph = new PostHelper(publicKey, privateKey, getConfigurationHelper().getUniBoardCertificate().getPublicKey(), wsdlUrl, endpointUrl); ph.post(cert.toJSON(), section, "certificate"); } catch (PostException | ch.bfh.uniboard.clientlib.signaturehelper.SignatureException | BoardErrorException | UnsupportedEncodingException ex) { logger.log(Level.SEVERE, "230 UniBoard rejected post: {0}", ex); throw new CertificateCreationException("230 UniBoard rejected post: " + ex); } logger.log(Level.INFO, "Certificate posted on UniBoard"); } /** * Creates the DSA public key of the requestor. * * @param publicKey the value for the public key * @return the DSA object representing the DSA public key * @throws CertificateCreationException if there is an error */ private DSAPublicKey createDSAPublicKey(BigInteger publicKey, BigInteger p, BigInteger q, BigInteger g) throws CertificateCreationException { DSAPublicKey dpk; try { dpk = new DSAPublicKeyImpl(publicKey, p, q, g); } catch (InvalidKeyException ex) { logger.log(Level.SEVERE, "Could not instantiate DSA public key: {0}", new Object[] { ex.getMessage() }); throw new CertificateCreationException("224 Could not instantiate DSA public key"); } return dpk; } /** * Creates an RSA public key of the requestor * * @param publicKey the value of the public key * @param n the modulus * @return the object representing an RSA public key * @throws CertificateCreationException if there is an error */ private RSAPublicKey createRSAPublicKey(BigInteger publicKey, BigInteger n) throws CertificateCreationException { RSAPublicKey rpk; try { rpk = new RSAPublicKeyImpl(n, publicKey); } catch (InvalidKeyException ex) { logger.log(Level.SEVERE, "Could not instantiate RSA public key: {0}", new Object[] { ex.getMessage() }); throw new CertificateCreationException("224 Could not instantiate RSA public key"); } return rpk; } /** * Returns the calendar representing the expiration date. By default, 2 years after now is set * * @return a calendar */ private Calendar getExpiryDate(Integer validityInYears) { //Default is 2 if (validityInYears == null) { validityInYears = 2; } Calendar expiry = Calendar.getInstance(); expiry.add(Calendar.YEAR, validityInYears); return expiry; } /** * Given a private RSA key creates an object containing all relevant cipher parameters. * * @param rsaPrivKey a RSA private key * @return the cipher parameters * @throws CertificateCreationException if there is an error */ private RSAPrivateCrtKeyParameters createIssuerCipherParams(RSAPrivateCrtKey rsaPrivKey) throws CertificateCreationException { RSAPrivateCrtKeyParameters cipherParams; try { cipherParams = new RSAPrivateCrtKeyParameters(rsaPrivKey.getModulus(), rsaPrivKey.getPublicExponent(), rsaPrivKey.getPrivateExponent(), rsaPrivKey.getPrimeP(), rsaPrivKey.getPrimeQ(), rsaPrivKey.getPrimeExponentP(), rsaPrivKey.getPrimeExponentQ(), rsaPrivKey.getCrtCoefficient()); } catch (Exception ex) { logger.log(Level.SEVERE, "Could not get issuer cipher parameters: {0}", new Object[] { ex.getMessage() }); throw new CertificateCreationException("200 Could not get issuer cipher parameters"); } return cipherParams; } /** * Actually creates the requestor certificate. * * @param id requestor identity data * @param caCert certificate of the certification authority * @param cipherParams issuer private key parameters used for signing * @param pk public key of the requestor to certify * @param expiry the expiry date * @param applicationIdentifier the application identifier for which te certificate is issued * @param role role for which the certificate is issued * @return the certificate object containing the X509 certificate * @throws CertificateCreationException if an error occurs */ private Certificate createClientCertificate(IdentityData id, X509Certificate caCert, CipherParameters cipherParams, PublicKey pk, Calendar expiry, String applicationIdentifier, String[] roles) throws CertificateCreationException { X509Certificate clientCert; Hashtable extension = new Hashtable(); extension.put(new DERObjectIdentifier(ExtensionOID.APPLICATION_IDENTIFIER.getOID()), new X509Extension(DERBoolean.FALSE, CertificateHelper.stringToDER(applicationIdentifier))); String completeRole = ""; for (String role : roles) { completeRole += role + ", "; } completeRole = completeRole.substring(0, completeRole.length() - 2); extension.put(new DERObjectIdentifier(ExtensionOID.ROLE.getOID()), new X509Extension(DERBoolean.FALSE, CertificateHelper.stringToDER(completeRole))); extension.put(new DERObjectIdentifier(ExtensionOID.IDENTITY_PROVIDER.getOID()), new X509Extension(DERBoolean.FALSE, CertificateHelper.stringToDER(id.getIdentityProvider()))); Map<String, String> extensionMap = new HashMap(); if (id.getOtherValues() != null) { for (Entry<ExtensionOID, String> entry : id.getOtherValues().entrySet()) { extension.put(new DERObjectIdentifier(entry.getKey().getOID()), new X509Extension(DERBoolean.FALSE, CertificateHelper.stringToDER(entry.getValue()))); extensionMap.put(entry.getKey().getName(), entry.getValue()); } } try { String x509NameString = ""; x509NameString += "CN=" + id.getCommonName(); if (id.getSurname() != null && !id.getSurname().equals("")) { x509NameString += ", SURNAME=" + id.getSurname(); } if (id.getGivenName() != null && !id.getGivenName().equals("")) { x509NameString += ", GIVENNAME=" + id.getGivenName(); } if (id.getUniqueIdentifier() != null && !id.getUniqueIdentifier().equals("")) { x509NameString += ", UID=" + id.getUniqueIdentifier(); } if (id.getOrganisation() != null && !id.getOrganisation().equals("")) { x509NameString += ", O=" + id.getOrganisation(); } if (id.getOrganisationUnit() != null && !id.getOrganisationUnit().equals("")) { x509NameString += ", OU=" + id.getOrganisationUnit(); } if (id.getCountryName() != null && !id.getCountryName().equals("")) { x509NameString += ", C=" + id.getCountryName(); } if (id.getState() != null && !id.getState().equals("")) { x509NameString += ", ST=" + id.getState(); } if (id.getLocality() != null && !id.getLocality().equals("")) { x509NameString += ", L=" + id.getLocality(); } X509Name x509Name = new X509Name(x509NameString); V3TBSCertificateGenerator certGen = new V3TBSCertificateGenerator(); certGen.setSerialNumber(new DERInteger(BigInteger.valueOf(System.currentTimeMillis()))); certGen.setIssuer(PrincipalUtil.getSubjectX509Principal(caCert)); certGen.setSubject(x509Name); certGen.setExtensions(new X509Extensions(extension)); DERObjectIdentifier sigOID = new DERObjectIdentifier("1.2.840.113549.1.1.5"); AlgorithmIdentifier sigAlgId = new AlgorithmIdentifier(sigOID, new DERNull()); certGen.setSignature(sigAlgId); certGen.setSubjectPublicKeyInfo(new SubjectPublicKeyInfo( (ASN1Sequence) new ASN1InputStream(new ByteArrayInputStream(pk.getEncoded())).readObject())); certGen.setStartDate(new Time(new Date(System.currentTimeMillis()))); certGen.setEndDate(new Time(expiry.getTime())); TBSCertificateStructure tbsCert = certGen.generateTBSCertificate(); //Sign certificate SHA1Digest digester = new SHA1Digest(); AsymmetricBlockCipher rsa = new PKCS1Encoding(new RSAEngine()); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); DEROutputStream dOut = new DEROutputStream(bOut); dOut.writeObject(tbsCert); byte[] signature; byte[] certBlock = bOut.toByteArray(); // first create digest digester.update(certBlock, 0, certBlock.length); byte[] hash = new byte[digester.getDigestSize()]; digester.doFinal(hash, 0); // then sign it rsa.init(true, cipherParams); DigestInfo dInfo = new DigestInfo(new AlgorithmIdentifier(X509ObjectIdentifiers.id_SHA1, null), hash); byte[] digest = dInfo.getEncoded(ASN1Encodable.DER); signature = rsa.processBlock(digest, 0, digest.length); ASN1EncodableVector v = new ASN1EncodableVector(); v.add(tbsCert); v.add(sigAlgId); v.add(new DERBitString(signature)); // Create CRT data structure clientCert = new X509CertificateObject(new X509CertificateStructure(new DERSequence(v))); clientCert.verify(caCert.getPublicKey()); } catch (IOException | CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException | InvalidCipherTextException | SignatureException e) { logger.log(Level.SEVERE, "Could not create client certificate: {0}", new Object[] { e.getMessage() }); throw new CertificateCreationException("230 Could not create client certificate"); } return new Certificate(clientCert, id.getCommonName(), id.getUniqueIdentifier(), id.getOrganisation(), id.getOrganisationUnit(), id.getCountryName(), id.getState(), id.getLocality(), id.getSurname(), id.getGivenName(), applicationIdentifier, roles, id.getIdentityProvider(), extensionMap); } /** * Returns a configuration helper. A subclass may override this method for testing purposes. * * @return a configuration helper */ protected ConfigurationHelper getConfigurationHelper() { return ConfigurationHelperImpl.getInstance(); } }