Newer
Older
Patrick Dowler
committed
/*
Patrick Dowler
committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
************************************************************************
******************* CANADIAN ASTRONOMY DATA CENTRE *******************
************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
*
* (c) 2011. (c) 2011.
* Government of Canada Gouvernement du Canada
* National Research Council Conseil national de recherches
* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
* All rights reserved Tous droits réservés
*
* NRC disclaims any warranties, Le CNRC dénie toute garantie
* expressed, implied, or énoncée, implicite ou légale,
* statutory, of any kind with de quelque nature que ce
* respect to the software, soit, concernant le logiciel,
* including without limitation y compris sans restriction
* any warranty of merchantability toute garantie de valeur
* or fitness for a particular marchande ou de pertinence
* purpose. NRC shall not be pour un usage particulier.
* liable in any event for any Le CNRC ne pourra en aucun cas
* damages, whether direct or être tenu responsable de tout
* indirect, special or general, dommage, direct ou indirect,
* consequential or incidental, particulier ou général,
* arising from the use of the accessoire ou fortuit, résultant
* software. Neither the name de l'utilisation du logiciel. Ni
* of the National Research le nom du Conseil National de
* Council of Canada nor the Recherches du Canada ni les noms
* names of its contributors may de ses participants ne peuvent
* be used to endorse or promote être utilisés pour approuver ou
* products derived from this promouvoir les produits dérivés
* software without specific prior de ce logiciel sans autorisation
* written permission. préalable et particulière
* par écrit.
*
* This file is part of the Ce fichier fait partie du projet
* OpenCADC project. OpenCADC.
*
* OpenCADC is free software: OpenCADC est un logiciel libre ;
* you can redistribute it and/or vous pouvez le redistribuer ou le
* modify it under the terms of modifier suivant les termes de
* the GNU Affero General Public la “GNU Affero General Public
* License as published by the License” telle que publiée
* Free Software Foundation, par la Free Software Foundation
* either version 3 of the : soit la version 3 de cette
* License, or (at your option) licence, soit (à votre gré)
* any later version. toute version ultérieure.
*
* OpenCADC is distributed in the OpenCADC est distribué
* hope that it will be useful, dans l’espoir qu’il vous
* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE
* without even the implied GARANTIE : sans même la garantie
* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ
* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF
* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence
* General Public License for Générale Publique GNU Affero
* more details. pour plus de détails.
*
* You should have received Vous devriez avoir reçu une
* a copy of the GNU Affero copie de la Licence Générale
* General Public License along Publique GNU Affero avec
* with OpenCADC. If not, see OpenCADC ; si ce n’est
* <http://www.gnu.org/licenses/>. pas le cas, consultez :
* <http://www.gnu.org/licenses/>.
*
* $Revision: 5 $
*
************************************************************************
*/
Patrick Dowler
committed
Patrick Dowler
committed
package ca.nrc.cadc.cred.server;
Patrick Dowler
committed
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.sql.Types;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.security.auth.x500.X500Principal;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import ca.nrc.cadc.auth.AuthenticationUtil;
import ca.nrc.cadc.auth.SSLUtil;
import ca.nrc.cadc.auth.X509CertificateChain;
import ca.nrc.cadc.db.DBUtil;
import ca.nrc.cadc.profiler.Profiler;
import ca.nrc.cadc.vosi.avail.CheckDataSource;
import ca.nrc.cadc.vosi.avail.CheckResource;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import javax.naming.NamingException;
import org.springframework.jdbc.core.JdbcTemplate;
/**
Patrick Dowler
committed
* Class to persist certificates in a relational database table. This class has
* been only tested with Sybase ASE 15 so far.
Patrick Dowler
committed
*
* @author pdowler
*
*/
public class CertificateDAO
{
Patrick Dowler
committed
private static final Logger logger = Logger.getLogger(CertificateDAO.class);
Patrick Dowler
committed
Patrick Dowler
committed
private final CertificateSchema config;
Patrick Dowler
committed
Patrick Dowler
committed
public static class CertificateSchema
Patrick Dowler
committed
{
Patrick Dowler
committed
private final String dataSourceName;
Patrick Dowler
committed
public static String DEFAULT_CERT_TABLE = "x509_certificates";
Patrick Dowler
committed
private final String certTable;
Patrick Dowler
committed
/**
* Backwards compatible constructor with default table name.
*
* @param dataSourceName
* @param catalog
* @param schema
*/
Patrick Dowler
committed
public CertificateSchema(String dataSourceName, String catalog, String schema)
Patrick Dowler
committed
{
this(dataSourceName, catalog, schema, DEFAULT_CERT_TABLE);
}
/**
* Constructor for certificate table description. The catalog and schema are optional
* (null values are allowed).
*
* @param dataSourceName JNDI DataSource name
* @param catalog optional catalog (database) name
* @param schema optional schema name
* @param table certificate table name (required)
*/
public CertificateSchema(String dataSourceName, String catalog, String schema, String table)
Patrick Dowler
committed
{
Patrick Dowler
committed
this.dataSourceName = dataSourceName;
Patrick Dowler
committed
if (table == null)
throw new IllegalArgumentException("table name cannot be null");
StringBuilder sb = new StringBuilder();
if (catalog != null)
sb.append(catalog).append(".");
if (schema != null)
sb.append(schema);
if (sb.length() > 0)
sb.append("."); // yeah: double dot if catalog!= null and schema==null
sb.append(table);
this.certTable = sb.toString();
Patrick Dowler
committed
}
Patrick Dowler
committed
public String getTable()
{
return certTable;
}
public DataSource getDataSource()
Patrick Dowler
committed
{
Patrick Dowler
committed
try
{
logger.debug("lookup datasource: " + dataSourceName);
Patrick Dowler
committed
return DBUtil.getDataSource(dataSourceName);
}
catch(NamingException ex)
{
throw new RuntimeException("CONFIG: failed to find DataSource " + dataSourceName);
}
Patrick Dowler
committed
}
Patrick Dowler
committed
Patrick Dowler
committed
}
Patrick Dowler
committed
public CertificateDAO(CertificateSchema config)
Patrick Dowler
committed
{
Patrick Dowler
committed
this.config = config;
Patrick Dowler
committed
}
Patrick Dowler
committed
Patrick Dowler
committed
public CheckResource getCheckResource()
{
Patrick Dowler
committed
String sql = "select top 1 hash_dn from " + config.getTable();
return new CheckDataSource(config.getDataSource(), sql);
Patrick Dowler
committed
}
public void put(X509CertificateChain chain)
{
Profiler profiler = new Profiler(this.getClass());
String hashKey = chain.getHashKey();
String canonDn = AuthenticationUtil.canonizeDistinguishedName(chain.getPrincipal().getName());
Date expDate = chain.getExpiryDate();
String certChainStr = chain.certificateString();
byte[] bytesPrivateKey = chain.getPrivateKey().getEncoded();
//TODO just for testing - padded with zeros
byte[] testBytesPrivateKey = Arrays.copyOf(bytesPrivateKey, bytesPrivateKey.length+1);
testBytesPrivateKey[testBytesPrivateKey.length-1]=1;
String csr = chain.getCsrString();
Patrick Dowler
committed
JdbcTemplate jdbc = new JdbcTemplate(config.getDataSource());
Patrick Dowler
committed
if (recordExists(hashKey))
{
Patrick Dowler
committed
String sql = "update " + config.getTable()
Patrick Dowler
committed
+ " set canon_dn = ?, exp_date = ?, cert_chain = ?, private_key = ?, csr = ? where hash_dn=?";
Object[] args = new Object[] { canonDn, expDate, certChainStr, testBytesPrivateKey, csr, hashKey };
int[] argTypes = new int[] { Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR, Types.VARBINARY, Types.VARCHAR, Types.VARCHAR };
jdbc.update(sql, args, argTypes);
}
else
{
Patrick Dowler
committed
String sql = "insert into " + config.getTable()
Patrick Dowler
committed
+ " (canon_dn, exp_date, cert_chain, private_key, csr, hash_dn) values (?,?,?,?,?,?)";
Object[] args = new Object[] { canonDn, expDate, certChainStr, testBytesPrivateKey, csr, hashKey };
int[] argTypes =
new int[] { Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR, Types.VARBINARY, Types.VARCHAR, Types.VARCHAR };
jdbc.update(sql, args, argTypes);
}
profiler.checkpoint("put");
}
public X509CertificateChain get(X500Principal principal)
{
if (principal == null) return null;
String canonizedDn = AuthenticationUtil.canonizeDistinguishedName(principal.getName());
X500Principal p = new X500Principal(canonizedDn);
String hashKey = Integer.toString(p.hashCode());
return get(hashKey);
}
public X509CertificateChain get(String hashKey)
{
Profiler profiler = new Profiler(this.getClass());
X509CertificateChain x509CertificateChain = null;
Patrick Dowler
committed
String query = "select canon_dn, exp_date, cert_chain, private_key, csr from " + config.getTable() + " where hash_dn = ? ";
Patrick Dowler
committed
try
{
Patrick Dowler
committed
JdbcTemplate jdbc = new JdbcTemplate(config.getDataSource());
Patrick Dowler
committed
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
Map<String, Object> map = jdbc.queryForMap(query, new String[] { hashKey });
String canonDn = (String) map.get("canon_dn");
Date expDate = (Date) map.get("exp_date");
String certChainStr = (String) map.get("cert_chain");
byte[] bytesPrivateKey = (byte[]) map.get("private_key");
// Sybase trims the trailing 0's of a varbinary. To compensate we add 0's to the
// privateKey byte array. Extra bytes in the private key array are ignored
// when the key is built so the added 0's are only used when needed.
// ad 20/07/2011
bytesPrivateKey = Arrays.copyOf(bytesPrivateKey, bytesPrivateKey.length+10);
String csrStr = (String) map.get("csr");
PrivateKey privateKey = SSLUtil.readPrivateKey(bytesPrivateKey);
X500Principal principal = new X500Principal(canonDn);
if (certChainStr != null)
{
byte[] bytesCertChain = certChainStr.getBytes();
X509Certificate[] certs = SSLUtil.readCertificateChain(bytesCertChain);
x509CertificateChain = new X509CertificateChain(Arrays.asList(certs));
}
else
{
x509CertificateChain = new X509CertificateChain(principal, privateKey, csrStr);
}
x509CertificateChain.setCsrString(csrStr);
x509CertificateChain.setExpiryDate(expDate);
x509CertificateChain.setHashKey(hashKey);
x509CertificateChain.setKey(privateKey);
x509CertificateChain.setPrincipal(principal);
}
catch (EmptyResultDataAccessException e)
{
// Record not exists.
return null;
}
catch(InvalidKeySpecException ex)
{
throw new RuntimeException("BUG: failed to read private key", ex);
}
catch(NoSuchAlgorithmException ex)
{
throw new RuntimeException("BUG: failed to read private key", ex);
}
catch(CertificateException ex)
{
throw new RuntimeException("BUG: failed to read certficate chain", ex);
}
catch(IOException ex)
{
throw new RuntimeException("BUG: failed to read certificate chain", ex);
}
profiler.checkpoint("get");
return x509CertificateChain;
}
/* (non-Javadoc)
* @see ca.nrc.cadc.accesscontrol.dao.CertificateDAO#delete(java.lang.String)
*/
public void delete(String hashKey)
{
Profiler profiler = new Profiler(this.getClass());
Patrick Dowler
committed
String sql = "delete from " + config.getTable() + " where hash_dn = ? ";
JdbcTemplate jdbc = new JdbcTemplate(config.getDataSource());
Patrick Dowler
committed
jdbc.update(sql, new String[] { hashKey });
profiler.checkpoint("delete");
}
private boolean recordExists(String hashKey)
{
RowMapper rowMapper = new SingleColumnRowMapper(String.class);
Patrick Dowler
committed
String query = "select canon_dn from " + config.getTable() + " where hash_dn = ? ";
JdbcTemplate jdbc = new JdbcTemplate(config.getDataSource());
Patrick Dowler
committed
List<String> dnList = jdbc.query(query, new String[] { hashKey }, rowMapper);
return (dnList != null && dnList.size() == 1);
}
public List<String> getAllHashKeys()
{
Profiler profiler = new Profiler(this.getClass());
Patrick Dowler
committed
String query = "select hash_dn from " + config.getTable();
Patrick Dowler
committed
RowMapper rowMapper = new SingleColumnRowMapper(String.class);
Patrick Dowler
committed
JdbcTemplate jdbc = new JdbcTemplate(config.getDataSource());
Patrick Dowler
committed
List<String> hashKeyList = jdbc.query(query, rowMapper);
profiler.checkpoint("getAllHashKeys");
return hashKeyList;
}
}