Loading cadc-cert-gen/README.md 0 → 100644 +11 −0 Original line number Diff line number Diff line # cadc-cert-gen Simple tool to enable someone to use their cadc-cdp-server based CDP service as an internal CA to provide local user certificates. Known Issues: - several hard-coded settings and behaviours make this unusable until some refactoring is done - shares knowledge of back-end DB implementation used in cadc-cdp-server to query the RDBMS table to find expiring certificates - only looks for expiring certificates that match a hard-coded CADC internal CA distinguished name pattern cadc-cert-gen/build.gradle 0 → 100644 +29 −0 Original line number Diff line number Diff line plugins { id 'java' id 'maven' id 'maven-publish' id 'application' } repositories { jcenter() mavenLocal() } sourceCompatibility = 1.7 group = 'org.opencadc' version = '1.0' mainClassName = 'ca.nrc.cadc.cert.Main' dependencies { compile 'log4j:log4j:1.+' compile 'org.opencadc:cadc-log:1.+' compile 'org.opencadc:cadc-util:1.+' compile 'org.opencadc:cadc-cdp:1.+' runtime 'net.sourceforge.jtds:jtds:1.+' } cadc-cert-gen/src/main/java/ca/nrc/cadc/cert/AbstractCertGenAction.java 0 → 100644 +149 −0 Original line number Diff line number Diff line /* ************************************************************************ **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** * * (c) 2015. (c) 2015. * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 * All rights reserved Tous droits reserves * * NRC disclaims any warranties Le CNRC denie toute garantie * expressed, implied, or statu- enoncee, implicite ou legale, * tory, of any kind with respect de quelque nature que se soit, * to the software, including concernant le logiciel, y com- * without limitation any war- pris sans restriction toute * ranty of merchantability or garantie de valeur marchande * fitness for a particular pur- ou de pertinence pour un usage * pose. NRC shall not be liable particulier. Le CNRC ne * in any event for any damages, pourra en aucun cas etre tenu * whether direct or indirect, responsable de tout dommage, * special or general, consequen- direct ou indirect, particul- * tial or incidental, arising ier ou general, accessoire ou * from the use of the software. fortuit, resultant de l'utili- * sation du logiciel. * * * @author adriand * * @version $Revision: $ * * **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** ************************************************************************ */ package ca.nrc.cadc.cert; import java.io.File; import java.io.IOException; import java.net.URI; import java.security.PrivilegedAction; import java.security.Security; import org.apache.log4j.Logger; import org.bouncycastle.jce.provider.BouncyCastleProvider; import ca.nrc.cadc.util.ArgumentMap; import ca.nrc.cadc.util.StringUtil; public abstract class AbstractCertGenAction implements PrivilegedAction<Object> { static final File SERVOPS_PEM_FILE = new File(System.getProperty("user.home") + "/.pub/proxy.pem"); private static Logger LOGGER = Logger .getLogger(AbstractCertGenAction.class); public static final URI CRED_SERVICE_ID = URI.create("ivo://cadc.nrc.ca/cred"); protected String server = "SYBASE"; // default server protected String database = "archive"; // default database protected int expiring; protected String userid; public boolean init(final ArgumentMap argMap) throws IOException { String expiringString = argMap.getValue(Main.ARG_EXPIRING); String userIDString = argMap.getValue(Main.ARG_USERID); if (expiringString == null && userIDString == null) { LOGGER.error("One of " + Main.ARG_EXPIRING + " or " + Main.ARG_USERID + " must be provided."); return false; } if (expiringString != null && userIDString != null) { LOGGER.error("Only one of " + Main.ARG_EXPIRING + " or " + Main.ARG_USERID + " must be provided."); return false; } if (expiringString != null) { expiring = parseExpire(argMap); } else { userid = userIDString; } if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } return true; } private int parseExpire(ArgumentMap argMap) { int expire = 0; String expireStr = argMap.getValue(Main.ARG_EXPIRING); if (StringUtil.hasText(expireStr)) { try { expire = Integer.parseInt(expireStr); } catch (NumberFormatException e) { LOGGER.debug(Main.ARG_EXPIRING + " must be an integer"); LOGGER.debug("Using the default value instead"); } } if (expire == 0) expire = Main.DEFAULT_EXPIRE; return expire; } @Override public String run() { try { runCommand(); return null; } catch (Exception e) { LOGGER.debug("run - ERROR \n" + e.getMessage()); throw new RuntimeException("execution error", e); } } protected void msg(String msg) { Main.msg(msg); } protected abstract void runCommand() throws Exception; } cadc-cert-gen/src/main/java/ca/nrc/cadc/cert/CertGenAction.java 0 → 100644 +297 −0 Original line number Diff line number Diff line /* ************************************************************************ **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** * * (c) 2011. (c) 2011. * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 * All rights reserved Tous droits reserves * * NRC disclaims any warranties Le CNRC denie toute garantie * expressed, implied, or statu- enoncee, implicite ou legale, * tory, of any kind with respect de quelque nature que se soit, * to the software, including concernant le logiciel, y com- * without limitation any war- pris sans restriction toute * ranty of merchantability or garantie de valeur marchande * fitness for a particular pur- ou de pertinence pour un usage * pose. NRC shall not be liable particulier. Le CNRC ne * in any event for any damages, pourra en aucun cas etre tenu * whether direct or indirect, responsable de tout dommage, * special or general, consequen- direct ou indirect, particul- * tial or incidental, arising ier ou general, accessoire ou * from the use of the software. fortuit, resultant de l'utili- * sation du logiciel. * * * @author adriand * * @version $Revision: $ * * **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** ************************************************************************ */ package ca.nrc.cadc.cert; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Security; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.List; import java.util.TimeZone; import javax.security.auth.x500.X500Principal; import org.apache.log4j.Logger; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.jce.PKCS10CertificationRequest; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMReader; import org.bouncycastle.x509.X509V3CertificateGenerator; import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; import org.springframework.jdbc.core.JdbcTemplate; import ca.nrc.cadc.auth.AuthenticationUtil; import ca.nrc.cadc.auth.HttpPrincipal; import ca.nrc.cadc.auth.SSLUtil; import ca.nrc.cadc.auth.X509CertificateChain; import ca.nrc.cadc.cred.CertUtil; import ca.nrc.cadc.cred.client.CredClient; import ca.nrc.cadc.net.ResourceNotFoundException; import ca.nrc.cadc.util.ArgumentMap; public class CertGenAction extends DbCertGenAction { private static final Logger LOGGER = Logger.getLogger(CertGenAction.class); private int lifetime = 365; // 365 days // CADC specific fields of the DN public static final String CADC_DN = "ou=cadc,o=hia,c=ca"; // Certificate of the signing authority X509CertificateChain signer; boolean dryRun = true; @Override public boolean init(final ArgumentMap argMap) throws IOException { if (!super.init(argMap)) return false; if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } String signingKeyStr = argMap.getValue(Main.ARG_SIGNED_CERT); if (signingKeyStr == null) { LOGGER.error(Main.ARG_SIGNED_CERT + " argument missing"); return false; } File signingKeyFile = new File(signingKeyStr); try { this.signer = SSLUtil.readPemCertificateAndKey(signingKeyFile); } catch (Exception ex) { throw new RuntimeException("failed to read " + Main.ARG_SIGNED_CERT + " " + signingKeyStr, ex); } dryRun = argMap.isSet(Main.ARG_DRYRUN); return super.init(argMap); } @Override protected void runCommand() throws Exception { LOGGER.debug("Entering generateCertificate"); boolean result = true; if (userid != null) { // create a cert for a single user HttpPrincipal useridPrincipal = new HttpPrincipal(userid); X500Principal userDN = super.getCADCUserDN(useridPrincipal); LOGGER.debug("About to create certificate for user " + userid + " with DN " + userDN.toString()); generateCertificate(userDN); msg("New user DN: " + userDN.toString()); } else { // renew certs for all users who's are about to expire int count = 0; X500Principal[] userDNs = getExpiringCADC(super.expiring); if (dryRun) { for (X500Principal userDN : userDNs) { msg("expiring: " + userDN.getName()); } count = (userDNs == null ? 0 : userDNs.length); msg("Found " + count + " certificates that will expire " + "within " + super.expiring + " days."); } else { for (X500Principal userDN : userDNs) { try { generateCertificate(userDN); } catch (Exception e) { } count++; } msg("Renewed " + count + " certificates"); } } } /** * Generates a certificate signed with subject's credentials (CADC private * key) and persists them. * * @param userDN The X500Principal to generate for. * @return X509CertificateChain generated and signed certificate chain * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws NoSuchProviderException * @throws SignatureException * @throws IllegalStateException * @throws IOException * @throws CertificateException * @throws CertificateNotYetValidException * @throws CertificateExpiredException */ private void generateCertificate(final X500Principal userDN) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, IllegalStateException, CertificateException, IOException, ResourceNotFoundException { if (!AuthenticationUtil.canonizeDistinguishedName( userDN.getName()).contains(CADC_DN)) { throw new IllegalArgumentException( "Wrong o, ou, or c fields in user DN: " + userDN); } LOGGER.debug("Generate private key & CSR"); CredClient client = new CredClient(CRED_SERVICE_ID); try { client.deleteResource(userDN); // remove old CSR } catch (ResourceNotFoundException ignore) { } client.createResoure(userDN); // generate a new CSR with current specs String encodedCSR = client.getEncodedCSR(userDN); if (encodedCSR == null) { // shouldn't happen throw new RuntimeException("No corresponding CSR found on the server"); } PEMReader reader = new PEMReader(new StringReader(encodedCSR)); PKCS10CertificationRequest csr = (PKCS10CertificationRequest) reader .readObject(); X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); //certGen.setIssuerDN(caCert.getSubjectX500Principal()); certGen.setIssuerDN(signer.getPrincipal()); certGen.setSubjectDN(userDN); // set validity GregorianCalendar date = new GregorianCalendar(TimeZone.getTimeZone("GMT")); date.add(Calendar.MINUTE, -5); // Allow for a five minute clock // skew here. certGen.setNotBefore(date.getTime()); // If hours = 0, then cert lifetime is set to that of user cert date.add(Calendar.MINUTE, 5); date.add(Calendar.HOUR, lifetime * 24); certGen.setNotAfter(date.getTime()); certGen.setPublicKey(csr.getPublicKey()); certGen.setSignatureAlgorithm(CertUtil.DEFAULT_SIGNATURE_ALGORITHM); //certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, // new AuthorityKeyIdentifierStructure(caCert)); certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(signer.getChain()[0])); // no extensions, at least for now LOGGER.debug("Generate certificate"); //X509Certificate cert = certGen.generate(signingKey, "BC"); X509Certificate cert = certGen.generate(signer.getPrivateKey(), "BC"); // build chain with all but the last cert (assumed to be a CA) // if signer is the CA, this creates a [1] and for loop does nothing X509Certificate[] chain = new X509Certificate[signer.getChain().length]; chain[0] = cert; System.arraycopy(signer.getChain(), 0, chain, 1, signer.getChain().length - 1); LOGGER.debug("Persisting certificate for " + userDN .getName() + " chain length: " + chain.length); client.putSignedCert(chain); String dn = userDN.getName(); String noWhitespaceDN = dn.replaceAll("\\s",""); msg("Generated certificate for " + noWhitespaceDN); } private X500Principal[] getExpiringCADC(int expire) { // @formatter:off String query = "select canon_dn" + " from " + " archive.dbo.x509_certificates " + " where " + " canon_dn like 'cn=%&____,ou=cadc,o=hia,c=ca' escape '&' " + " and datediff(dd, current_date(), exp_date) < ? "; // @formatter:on JdbcTemplate jdbc = new JdbcTemplate(ds); @SuppressWarnings("unchecked") List<String> rsList = (List<String>) jdbc.queryForList(query, new Object[]{ expire}, String.class); X500Principal[] result = new X500Principal[rsList.size()]; Iterator<String> it = rsList.iterator(); for (int i = 0; i < result.length; i++) { result[i] = new X500Principal(it.next()); } return result; } } cadc-cert-gen/src/main/java/ca/nrc/cadc/cert/DbCertGenAction.java 0 → 100644 +133 −0 Original line number Diff line number Diff line /* ************************************************************************ **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** * * (c) 2011. (c) 2011. * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 * All rights reserved Tous droits reserves * * NRC disclaims any warranties Le CNRC denie toute garantie * expressed, implied, or statu- enoncee, implicite ou legale, * tory, of any kind with respect de quelque nature que se soit, * to the software, including concernant le logiciel, y com- * without limitation any war- pris sans restriction toute * ranty of merchantability or garantie de valeur marchande * fitness for a particular pur- ou de pertinence pour un usage * pose. NRC shall not be liable particulier. Le CNRC ne * in any event for any damages, pourra en aucun cas etre tenu * whether direct or indirect, responsable de tout dommage, * special or general, consequen- direct ou indirect, particul- * tial or incidental, arising ier ou general, accessoire ou * from the use of the software. fortuit, resultant de l'utili- * sation du logiciel. * * * @author adriand * * @version $Revision: $ * * **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** ************************************************************************ */ package ca.nrc.cadc.cert; import java.io.IOException; import javax.security.auth.x500.X500Principal; import javax.sql.DataSource; import org.apache.log4j.Logger; import org.springframework.jdbc.core.JdbcTemplate; import ca.nrc.cadc.auth.HttpPrincipal; import ca.nrc.cadc.db.ConnectionConfig; import ca.nrc.cadc.db.DBConfig; import ca.nrc.cadc.db.DBUtil; import ca.nrc.cadc.util.ArgumentMap; /** * Represents a AbstractCertGenAction that needs DB connections */ public abstract class DbCertGenAction extends AbstractCertGenAction { private static Logger LOGGER = Logger .getLogger(DbCertGenAction.class); // datasource to the database protected DataSource ds; protected static final String TMP_TABLE = "#tmptable"; public static final String GENERATE_DN_Q = "select dbo.genDN(?)"; @Override public boolean init(final ArgumentMap argMap) throws IOException { if (!super.init(argMap)) { return false; } initDbConnection(argMap); return true; } /** * Returns DN of a cadc user * * @param userId The HTTP Principal to get the DN for. * @return X500Principal, or null. */ protected X500Principal getCADCUserDN(HttpPrincipal userId) { final JdbcTemplate jdbc = new JdbcTemplate(ds); @SuppressWarnings("unchecked") final String userDN = (String) jdbc.queryForObject(GENERATE_DN_Q, new Object[]{ userId.getName()}, String.class); return (userDN == null) ? null : new X500Principal(userDN); } private void initDbConnection(ArgumentMap argMap) throws IOException { if (argMap.getValue(Main.ARG_DB) != null) { database = argMap.getValue(Main.ARG_DB); if (database.length() < 2) { throw new IllegalArgumentException("Argument " + Main.ARG_DB + "(" + argMap.getValue(Main.ARG_DB) + ") invalid"); } } if (argMap.getValue(Main.ARG_SERVER) != null) { server = argMap.getValue(Main.ARG_SERVER); if (server.length() < 2) { throw new IllegalArgumentException("Argument " + Main.ARG_SERVER + "(" + argMap.getValue(Main.ARG_SERVER) + ") invalid"); } } final DBConfig dbrc = new DBConfig(); LOGGER.debug("dbrc=" + dbrc); final ConnectionConfig conCfg = dbrc.getConnectionConfig(server, database); LOGGER.info("Database name: " + database); LOGGER.info("Database server: " + server); LOGGER.debug("conCfg=" + conCfg); ds = DBUtil.getDataSource(conCfg); LOGGER.debug("ds=" + ds); } } Loading
cadc-cert-gen/README.md 0 → 100644 +11 −0 Original line number Diff line number Diff line # cadc-cert-gen Simple tool to enable someone to use their cadc-cdp-server based CDP service as an internal CA to provide local user certificates. Known Issues: - several hard-coded settings and behaviours make this unusable until some refactoring is done - shares knowledge of back-end DB implementation used in cadc-cdp-server to query the RDBMS table to find expiring certificates - only looks for expiring certificates that match a hard-coded CADC internal CA distinguished name pattern
cadc-cert-gen/build.gradle 0 → 100644 +29 −0 Original line number Diff line number Diff line plugins { id 'java' id 'maven' id 'maven-publish' id 'application' } repositories { jcenter() mavenLocal() } sourceCompatibility = 1.7 group = 'org.opencadc' version = '1.0' mainClassName = 'ca.nrc.cadc.cert.Main' dependencies { compile 'log4j:log4j:1.+' compile 'org.opencadc:cadc-log:1.+' compile 'org.opencadc:cadc-util:1.+' compile 'org.opencadc:cadc-cdp:1.+' runtime 'net.sourceforge.jtds:jtds:1.+' }
cadc-cert-gen/src/main/java/ca/nrc/cadc/cert/AbstractCertGenAction.java 0 → 100644 +149 −0 Original line number Diff line number Diff line /* ************************************************************************ **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** * * (c) 2015. (c) 2015. * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 * All rights reserved Tous droits reserves * * NRC disclaims any warranties Le CNRC denie toute garantie * expressed, implied, or statu- enoncee, implicite ou legale, * tory, of any kind with respect de quelque nature que se soit, * to the software, including concernant le logiciel, y com- * without limitation any war- pris sans restriction toute * ranty of merchantability or garantie de valeur marchande * fitness for a particular pur- ou de pertinence pour un usage * pose. NRC shall not be liable particulier. Le CNRC ne * in any event for any damages, pourra en aucun cas etre tenu * whether direct or indirect, responsable de tout dommage, * special or general, consequen- direct ou indirect, particul- * tial or incidental, arising ier ou general, accessoire ou * from the use of the software. fortuit, resultant de l'utili- * sation du logiciel. * * * @author adriand * * @version $Revision: $ * * **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** ************************************************************************ */ package ca.nrc.cadc.cert; import java.io.File; import java.io.IOException; import java.net.URI; import java.security.PrivilegedAction; import java.security.Security; import org.apache.log4j.Logger; import org.bouncycastle.jce.provider.BouncyCastleProvider; import ca.nrc.cadc.util.ArgumentMap; import ca.nrc.cadc.util.StringUtil; public abstract class AbstractCertGenAction implements PrivilegedAction<Object> { static final File SERVOPS_PEM_FILE = new File(System.getProperty("user.home") + "/.pub/proxy.pem"); private static Logger LOGGER = Logger .getLogger(AbstractCertGenAction.class); public static final URI CRED_SERVICE_ID = URI.create("ivo://cadc.nrc.ca/cred"); protected String server = "SYBASE"; // default server protected String database = "archive"; // default database protected int expiring; protected String userid; public boolean init(final ArgumentMap argMap) throws IOException { String expiringString = argMap.getValue(Main.ARG_EXPIRING); String userIDString = argMap.getValue(Main.ARG_USERID); if (expiringString == null && userIDString == null) { LOGGER.error("One of " + Main.ARG_EXPIRING + " or " + Main.ARG_USERID + " must be provided."); return false; } if (expiringString != null && userIDString != null) { LOGGER.error("Only one of " + Main.ARG_EXPIRING + " or " + Main.ARG_USERID + " must be provided."); return false; } if (expiringString != null) { expiring = parseExpire(argMap); } else { userid = userIDString; } if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } return true; } private int parseExpire(ArgumentMap argMap) { int expire = 0; String expireStr = argMap.getValue(Main.ARG_EXPIRING); if (StringUtil.hasText(expireStr)) { try { expire = Integer.parseInt(expireStr); } catch (NumberFormatException e) { LOGGER.debug(Main.ARG_EXPIRING + " must be an integer"); LOGGER.debug("Using the default value instead"); } } if (expire == 0) expire = Main.DEFAULT_EXPIRE; return expire; } @Override public String run() { try { runCommand(); return null; } catch (Exception e) { LOGGER.debug("run - ERROR \n" + e.getMessage()); throw new RuntimeException("execution error", e); } } protected void msg(String msg) { Main.msg(msg); } protected abstract void runCommand() throws Exception; }
cadc-cert-gen/src/main/java/ca/nrc/cadc/cert/CertGenAction.java 0 → 100644 +297 −0 Original line number Diff line number Diff line /* ************************************************************************ **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** * * (c) 2011. (c) 2011. * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 * All rights reserved Tous droits reserves * * NRC disclaims any warranties Le CNRC denie toute garantie * expressed, implied, or statu- enoncee, implicite ou legale, * tory, of any kind with respect de quelque nature que se soit, * to the software, including concernant le logiciel, y com- * without limitation any war- pris sans restriction toute * ranty of merchantability or garantie de valeur marchande * fitness for a particular pur- ou de pertinence pour un usage * pose. NRC shall not be liable particulier. Le CNRC ne * in any event for any damages, pourra en aucun cas etre tenu * whether direct or indirect, responsable de tout dommage, * special or general, consequen- direct ou indirect, particul- * tial or incidental, arising ier ou general, accessoire ou * from the use of the software. fortuit, resultant de l'utili- * sation du logiciel. * * * @author adriand * * @version $Revision: $ * * **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** ************************************************************************ */ package ca.nrc.cadc.cert; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Security; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.List; import java.util.TimeZone; import javax.security.auth.x500.X500Principal; import org.apache.log4j.Logger; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.jce.PKCS10CertificationRequest; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMReader; import org.bouncycastle.x509.X509V3CertificateGenerator; import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; import org.springframework.jdbc.core.JdbcTemplate; import ca.nrc.cadc.auth.AuthenticationUtil; import ca.nrc.cadc.auth.HttpPrincipal; import ca.nrc.cadc.auth.SSLUtil; import ca.nrc.cadc.auth.X509CertificateChain; import ca.nrc.cadc.cred.CertUtil; import ca.nrc.cadc.cred.client.CredClient; import ca.nrc.cadc.net.ResourceNotFoundException; import ca.nrc.cadc.util.ArgumentMap; public class CertGenAction extends DbCertGenAction { private static final Logger LOGGER = Logger.getLogger(CertGenAction.class); private int lifetime = 365; // 365 days // CADC specific fields of the DN public static final String CADC_DN = "ou=cadc,o=hia,c=ca"; // Certificate of the signing authority X509CertificateChain signer; boolean dryRun = true; @Override public boolean init(final ArgumentMap argMap) throws IOException { if (!super.init(argMap)) return false; if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } String signingKeyStr = argMap.getValue(Main.ARG_SIGNED_CERT); if (signingKeyStr == null) { LOGGER.error(Main.ARG_SIGNED_CERT + " argument missing"); return false; } File signingKeyFile = new File(signingKeyStr); try { this.signer = SSLUtil.readPemCertificateAndKey(signingKeyFile); } catch (Exception ex) { throw new RuntimeException("failed to read " + Main.ARG_SIGNED_CERT + " " + signingKeyStr, ex); } dryRun = argMap.isSet(Main.ARG_DRYRUN); return super.init(argMap); } @Override protected void runCommand() throws Exception { LOGGER.debug("Entering generateCertificate"); boolean result = true; if (userid != null) { // create a cert for a single user HttpPrincipal useridPrincipal = new HttpPrincipal(userid); X500Principal userDN = super.getCADCUserDN(useridPrincipal); LOGGER.debug("About to create certificate for user " + userid + " with DN " + userDN.toString()); generateCertificate(userDN); msg("New user DN: " + userDN.toString()); } else { // renew certs for all users who's are about to expire int count = 0; X500Principal[] userDNs = getExpiringCADC(super.expiring); if (dryRun) { for (X500Principal userDN : userDNs) { msg("expiring: " + userDN.getName()); } count = (userDNs == null ? 0 : userDNs.length); msg("Found " + count + " certificates that will expire " + "within " + super.expiring + " days."); } else { for (X500Principal userDN : userDNs) { try { generateCertificate(userDN); } catch (Exception e) { } count++; } msg("Renewed " + count + " certificates"); } } } /** * Generates a certificate signed with subject's credentials (CADC private * key) and persists them. * * @param userDN The X500Principal to generate for. * @return X509CertificateChain generated and signed certificate chain * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws NoSuchProviderException * @throws SignatureException * @throws IllegalStateException * @throws IOException * @throws CertificateException * @throws CertificateNotYetValidException * @throws CertificateExpiredException */ private void generateCertificate(final X500Principal userDN) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, IllegalStateException, CertificateException, IOException, ResourceNotFoundException { if (!AuthenticationUtil.canonizeDistinguishedName( userDN.getName()).contains(CADC_DN)) { throw new IllegalArgumentException( "Wrong o, ou, or c fields in user DN: " + userDN); } LOGGER.debug("Generate private key & CSR"); CredClient client = new CredClient(CRED_SERVICE_ID); try { client.deleteResource(userDN); // remove old CSR } catch (ResourceNotFoundException ignore) { } client.createResoure(userDN); // generate a new CSR with current specs String encodedCSR = client.getEncodedCSR(userDN); if (encodedCSR == null) { // shouldn't happen throw new RuntimeException("No corresponding CSR found on the server"); } PEMReader reader = new PEMReader(new StringReader(encodedCSR)); PKCS10CertificationRequest csr = (PKCS10CertificationRequest) reader .readObject(); X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); //certGen.setIssuerDN(caCert.getSubjectX500Principal()); certGen.setIssuerDN(signer.getPrincipal()); certGen.setSubjectDN(userDN); // set validity GregorianCalendar date = new GregorianCalendar(TimeZone.getTimeZone("GMT")); date.add(Calendar.MINUTE, -5); // Allow for a five minute clock // skew here. certGen.setNotBefore(date.getTime()); // If hours = 0, then cert lifetime is set to that of user cert date.add(Calendar.MINUTE, 5); date.add(Calendar.HOUR, lifetime * 24); certGen.setNotAfter(date.getTime()); certGen.setPublicKey(csr.getPublicKey()); certGen.setSignatureAlgorithm(CertUtil.DEFAULT_SIGNATURE_ALGORITHM); //certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, // new AuthorityKeyIdentifierStructure(caCert)); certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(signer.getChain()[0])); // no extensions, at least for now LOGGER.debug("Generate certificate"); //X509Certificate cert = certGen.generate(signingKey, "BC"); X509Certificate cert = certGen.generate(signer.getPrivateKey(), "BC"); // build chain with all but the last cert (assumed to be a CA) // if signer is the CA, this creates a [1] and for loop does nothing X509Certificate[] chain = new X509Certificate[signer.getChain().length]; chain[0] = cert; System.arraycopy(signer.getChain(), 0, chain, 1, signer.getChain().length - 1); LOGGER.debug("Persisting certificate for " + userDN .getName() + " chain length: " + chain.length); client.putSignedCert(chain); String dn = userDN.getName(); String noWhitespaceDN = dn.replaceAll("\\s",""); msg("Generated certificate for " + noWhitespaceDN); } private X500Principal[] getExpiringCADC(int expire) { // @formatter:off String query = "select canon_dn" + " from " + " archive.dbo.x509_certificates " + " where " + " canon_dn like 'cn=%&____,ou=cadc,o=hia,c=ca' escape '&' " + " and datediff(dd, current_date(), exp_date) < ? "; // @formatter:on JdbcTemplate jdbc = new JdbcTemplate(ds); @SuppressWarnings("unchecked") List<String> rsList = (List<String>) jdbc.queryForList(query, new Object[]{ expire}, String.class); X500Principal[] result = new X500Principal[rsList.size()]; Iterator<String> it = rsList.iterator(); for (int i = 0; i < result.length; i++) { result[i] = new X500Principal(it.next()); } return result; } }
cadc-cert-gen/src/main/java/ca/nrc/cadc/cert/DbCertGenAction.java 0 → 100644 +133 −0 Original line number Diff line number Diff line /* ************************************************************************ **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** * * (c) 2011. (c) 2011. * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 * All rights reserved Tous droits reserves * * NRC disclaims any warranties Le CNRC denie toute garantie * expressed, implied, or statu- enoncee, implicite ou legale, * tory, of any kind with respect de quelque nature que se soit, * to the software, including concernant le logiciel, y com- * without limitation any war- pris sans restriction toute * ranty of merchantability or garantie de valeur marchande * fitness for a particular pur- ou de pertinence pour un usage * pose. NRC shall not be liable particulier. Le CNRC ne * in any event for any damages, pourra en aucun cas etre tenu * whether direct or indirect, responsable de tout dommage, * special or general, consequen- direct ou indirect, particul- * tial or incidental, arising ier ou general, accessoire ou * from the use of the software. fortuit, resultant de l'utili- * sation du logiciel. * * * @author adriand * * @version $Revision: $ * * **** C A N A D I A N A S T R O N O M Y D A T A C E N T R E ***** ************************************************************************ */ package ca.nrc.cadc.cert; import java.io.IOException; import javax.security.auth.x500.X500Principal; import javax.sql.DataSource; import org.apache.log4j.Logger; import org.springframework.jdbc.core.JdbcTemplate; import ca.nrc.cadc.auth.HttpPrincipal; import ca.nrc.cadc.db.ConnectionConfig; import ca.nrc.cadc.db.DBConfig; import ca.nrc.cadc.db.DBUtil; import ca.nrc.cadc.util.ArgumentMap; /** * Represents a AbstractCertGenAction that needs DB connections */ public abstract class DbCertGenAction extends AbstractCertGenAction { private static Logger LOGGER = Logger .getLogger(DbCertGenAction.class); // datasource to the database protected DataSource ds; protected static final String TMP_TABLE = "#tmptable"; public static final String GENERATE_DN_Q = "select dbo.genDN(?)"; @Override public boolean init(final ArgumentMap argMap) throws IOException { if (!super.init(argMap)) { return false; } initDbConnection(argMap); return true; } /** * Returns DN of a cadc user * * @param userId The HTTP Principal to get the DN for. * @return X500Principal, or null. */ protected X500Principal getCADCUserDN(HttpPrincipal userId) { final JdbcTemplate jdbc = new JdbcTemplate(ds); @SuppressWarnings("unchecked") final String userDN = (String) jdbc.queryForObject(GENERATE_DN_Q, new Object[]{ userId.getName()}, String.class); return (userDN == null) ? null : new X500Principal(userDN); } private void initDbConnection(ArgumentMap argMap) throws IOException { if (argMap.getValue(Main.ARG_DB) != null) { database = argMap.getValue(Main.ARG_DB); if (database.length() < 2) { throw new IllegalArgumentException("Argument " + Main.ARG_DB + "(" + argMap.getValue(Main.ARG_DB) + ") invalid"); } } if (argMap.getValue(Main.ARG_SERVER) != null) { server = argMap.getValue(Main.ARG_SERVER); if (server.length() < 2) { throw new IllegalArgumentException("Argument " + Main.ARG_SERVER + "(" + argMap.getValue(Main.ARG_SERVER) + ") invalid"); } } final DBConfig dbrc = new DBConfig(); LOGGER.debug("dbrc=" + dbrc); final ConnectionConfig conCfg = dbrc.getConnectionConfig(server, database); LOGGER.info("Database name: " + database); LOGGER.info("Database server: " + server); LOGGER.debug("conCfg=" + conCfg); ds = DBUtil.getDataSource(conCfg); LOGGER.debug("ds=" + ds); } }