/* * oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. * * Copyright (c) 2014, Gluu */ package org.xdi.oxauth.model.crypto; import org.apache.commons.codec.binary.Base64; import org.apache.log4j.Logger; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.cert.CertIOException; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import org.xdi.oxauth.model.crypto.signature.SignatureAlgorithm; import org.xdi.oxauth.model.crypto.signature.SignatureAlgorithmFamily; import org.xdi.oxauth.model.jwk.Use; import org.xdi.oxauth.model.util.Base64Util; import org.xdi.oxauth.model.util.Util; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.math.BigInteger; import java.security.*; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.ECGenParameterSpec; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.UUID; import static org.xdi.oxauth.model.jwk.JWKParameter.*; /** * @author Javier Rojas Blum * @author Yuriy Movchan * @version September 30, 2016 */ public class OxAuthCryptoProvider extends AbstractCryptoProvider { private static final Logger LOG = Logger.getLogger(OxAuthCryptoProvider.class); private KeyStore keyStore; private String keyStoreFile; private String keyStoreSecret; private String dnName; public OxAuthCryptoProvider() throws Exception { this(null, null, null); } public OxAuthCryptoProvider(String keyStoreFile, String keyStoreSecret, String dnName) throws Exception { if (!Util.isNullOrEmpty(keyStoreFile) && !Util.isNullOrEmpty(keyStoreSecret) /* && !Util.isNullOrEmpty(dnName) */) { this.keyStoreFile = keyStoreFile; this.keyStoreSecret = keyStoreSecret; this.dnName = dnName; keyStore = KeyStore.getInstance("JKS"); try { File f = new File(keyStoreFile); if (!f.exists()) { keyStore.load(null, keyStoreSecret.toCharArray()); FileOutputStream fos = new FileOutputStream(keyStoreFile); keyStore.store(fos, keyStoreSecret.toCharArray()); fos.close(); } final InputStream is = new FileInputStream(keyStoreFile); keyStore.load(is, keyStoreSecret.toCharArray()); } catch (Exception e) { LOG.error(e.getMessage(), e); } } } @Override public JSONObject generateKey(SignatureAlgorithm signatureAlgorithm, Long expirationTime) throws Exception { KeyPairGenerator keyGen = null; if (signatureAlgorithm == null) { throw new RuntimeException("The signature algorithm parameter cannot be null"); } else if (SignatureAlgorithmFamily.RSA.equals(signatureAlgorithm.getFamily())) { keyGen = KeyPairGenerator.getInstance(signatureAlgorithm.getFamily(), "BC"); keyGen.initialize(2048, new SecureRandom()); } else if (SignatureAlgorithmFamily.EC.equals(signatureAlgorithm.getFamily())) { ECGenParameterSpec eccgen = new ECGenParameterSpec(signatureAlgorithm.getCurve().getAlias()); keyGen = KeyPairGenerator.getInstance(signatureAlgorithm.getFamily(), "BC"); keyGen.initialize(eccgen, new SecureRandom()); } else { throw new RuntimeException("The provided signature algorithm parameter is not supported"); } // Generate the key KeyPair keyPair = keyGen.generateKeyPair(); java.security.PrivateKey pk = keyPair.getPrivate(); // Java API requires a certificate chain X509Certificate cert = generateV3Certificate(keyPair, dnName, signatureAlgorithm.getAlgorithm(), expirationTime); X509Certificate[] chain = new X509Certificate[1]; chain[0] = cert; String alias = UUID.randomUUID().toString(); keyStore.setKeyEntry(alias, pk, keyStoreSecret.toCharArray(), chain); FileOutputStream stream = new FileOutputStream(keyStoreFile); keyStore.store(stream, keyStoreSecret.toCharArray()); PublicKey publicKey = keyPair.getPublic(); JSONObject jsonObject = new JSONObject(); jsonObject.put(KEY_TYPE, signatureAlgorithm.getFamily()); jsonObject.put(KEY_ID, alias); jsonObject.put(KEY_USE, Use.SIGNATURE); jsonObject.put(ALGORITHM, signatureAlgorithm.getName()); jsonObject.put(EXPIRATION_TIME, expirationTime); if (publicKey instanceof RSAPublicKey) { RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey; jsonObject.put(MODULUS, Base64Util.base64urlencodeUnsignedBigInt(rsaPublicKey.getModulus())); jsonObject.put(EXPONENT, Base64Util.base64urlencodeUnsignedBigInt(rsaPublicKey.getPublicExponent())); } else if (publicKey instanceof ECPublicKey) { ECPublicKey ecPublicKey = (ECPublicKey) publicKey; jsonObject.put(CURVE, signatureAlgorithm.getCurve()); jsonObject.put(X, Base64Util.base64urlencodeUnsignedBigInt(ecPublicKey.getW().getAffineX())); jsonObject.put(Y, Base64Util.base64urlencodeUnsignedBigInt(ecPublicKey.getW().getAffineY())); } JSONArray x5c = new JSONArray(); x5c.put(Base64.encodeBase64String(cert.getEncoded())); jsonObject.put(CERTIFICATE_CHAIN, x5c); return jsonObject; } @Override public String sign(String signingInput, String alias, String sharedSecret, SignatureAlgorithm signatureAlgorithm) throws Exception { if (signatureAlgorithm == SignatureAlgorithm.NONE) { return ""; } else if (SignatureAlgorithmFamily.HMAC.equals(signatureAlgorithm.getFamily())) { SecretKey secretKey = new SecretKeySpec(sharedSecret.getBytes(Util.UTF8_STRING_ENCODING), signatureAlgorithm.getAlgorithm()); Mac mac = Mac.getInstance(signatureAlgorithm.getAlgorithm()); mac.init(secretKey); byte[] sig = mac.doFinal(signingInput.getBytes()); return Base64Util.base64urlencode(sig); } else { // EC or RSA PrivateKey privateKey = getPrivateKey(alias); Signature signature = Signature.getInstance(signatureAlgorithm.getAlgorithm(), "BC"); //Signature signature = Signature.getInstance(signatureAlgorithm.getAlgorithm()); signature.initSign(privateKey); signature.update(signingInput.getBytes()); return Base64Util.base64urlencode(signature.sign()); } } @Override public boolean verifySignature(String signingInput, String encodedSignature, String alias, JSONObject jwks, String sharedSecret, SignatureAlgorithm signatureAlgorithm) throws Exception { boolean verified = false; if (signatureAlgorithm == SignatureAlgorithm.NONE) { return Util.isNullOrEmpty(encodedSignature); } else if (SignatureAlgorithmFamily.HMAC.equals(signatureAlgorithm.getFamily())) { String expectedSignature = sign(signingInput, null, sharedSecret, signatureAlgorithm); return expectedSignature.equals(encodedSignature); } else { // EC or RSA PublicKey publicKey = null; try { if (jwks == null) { publicKey = getPublicKey(alias); } else { publicKey = getPublicKey(alias, jwks); } if (publicKey == null) { return false; } byte[] signature = Base64Util.base64urldecode(encodedSignature); Signature verifier = Signature.getInstance(signatureAlgorithm.getAlgorithm(), "BC"); //Signature verifier = Signature.getInstance(signatureAlgorithm.getAlgorithm()); verifier.initVerify(publicKey); verifier.update(signingInput.getBytes()); verified = verifier.verify(signature); } catch (NoSuchAlgorithmException e) { LOG.error(e.getMessage(), e); verified = false; } catch (SignatureException e) { LOG.error(e.getMessage(), e); verified = false; } catch (InvalidKeyException e) { LOG.error(e.getMessage(), e); verified = false; } catch (Exception e) { LOG.error(e.getMessage(), e); verified = false; } } return verified; } private String getJWKSValue(JSONObject jwks, String node) throws JSONException { try { return jwks.getString(node); } catch (Exception ex) { JSONObject publicKey = jwks.getJSONObject(PUBLIC_KEY); return publicKey.getString(node); } } @Override public boolean deleteKey(String alias) throws Exception { keyStore.deleteEntry(alias); FileOutputStream stream = new FileOutputStream(keyStoreFile); keyStore.store(stream, keyStoreSecret.toCharArray()); return true; } public PublicKey getPublicKey(String alias) { PublicKey publicKey = null; try { if (Util.isNullOrEmpty(alias)) { return null; } java.security.cert.Certificate certificate = keyStore.getCertificate(alias); if (certificate == null) { return null; } publicKey = certificate.getPublicKey(); } catch (KeyStoreException e) { e.printStackTrace(); } return publicKey; } public PrivateKey getPrivateKey(String alias) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException { if (Util.isNullOrEmpty(alias)) { return null; } Key key = keyStore.getKey(alias, keyStoreSecret.toCharArray()); if (key == null) { return null; } PrivateKey privateKey = (PrivateKey) key; return privateKey; } public X509Certificate generateV3Certificate(KeyPair keyPair, String issuer, String signatureAlgorithm, Long expirationTime) throws CertIOException, OperatorCreationException, CertificateException { PrivateKey privateKey = keyPair.getPrivate(); PublicKey publicKey = keyPair.getPublic(); // Signers name X500Name issuerName = new X500Name(issuer); // Subjects name - the same as we are self signed. X500Name subjectName = new X500Name(issuer); // Serial BigInteger serial = new BigInteger(256, new SecureRandom()); // Not before Date notBefore = new Date(System.currentTimeMillis() - 10000); Date notAfter = new Date(expirationTime); // Create the certificate - version 3 JcaX509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(issuerName, serial, notBefore, notAfter, subjectName, publicKey); ASN1EncodableVector purposes = new ASN1EncodableVector(); purposes.add(KeyPurposeId.id_kp_serverAuth); purposes.add(KeyPurposeId.id_kp_clientAuth); purposes.add(KeyPurposeId.anyExtendedKeyUsage); ASN1ObjectIdentifier extendedKeyUsage = new ASN1ObjectIdentifier("2.5.29.37").intern(); builder.addExtension(extendedKeyUsage, false, new DERSequence(purposes)); ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).setProvider("BC").build(privateKey); X509CertificateHolder holder = builder.build(signer); X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(holder); return cert; } public List<String> getKeyAliases() throws KeyStoreException { return Collections.list(this.keyStore.aliases()); } public SignatureAlgorithm getSignatureAlgorithm(String alias) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException { Certificate[] chain = keyStore.getCertificateChain(alias); if ((chain == null) || chain.length == 0) { return null; } X509Certificate cert = (X509Certificate) chain[0]; String sighAlgName = cert.getSigAlgName(); for (SignatureAlgorithm sa : SignatureAlgorithm.values()) { if (sighAlgName.equalsIgnoreCase(sa.getAlgorithm())) { return sa; } } return null; } }