Add keystore management and related JUNIT test case

This commit is contained in:
Kelven Yang 2011-04-19 16:19:53 -07:00
parent 98baa7392e
commit 6ed18b5583
7 changed files with 500 additions and 0 deletions

View File

@ -0,0 +1,26 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.keystore;
import com.cloud.utils.db.GenericDao;
public interface KeystoreDao extends GenericDao<KeystoreVO, Long> {
KeystoreVO findByName(String name);
void save(String name, String certificate, String key, String domainSuffix);
}

View File

@ -0,0 +1,77 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.keystore;
import java.sql.PreparedStatement;
import javax.ejb.Local;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.SearchCriteria.Op;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.exception.CloudRuntimeException;
@Local(value={KeystoreDao.class})
public class KeystoreDaoImpl extends GenericDaoBase<KeystoreVO, Long> implements KeystoreDao {
protected final SearchBuilder<KeystoreVO> FindByNameSearch;
public KeystoreDaoImpl() {
FindByNameSearch = createSearchBuilder();
FindByNameSearch.and("name", FindByNameSearch.entity().getName(), Op.EQ);
FindByNameSearch.done();
}
@Override
public KeystoreVO findByName(String name) {
assert(name != null);
SearchCriteria<KeystoreVO> sc = FindByNameSearch.create();
sc.setParameters("name", name);
return findOneBy(sc);
}
@Override
@DB
public void save(String name, String certificate, String key, String domainSuffix) {
Transaction txn = Transaction.currentTxn();
try {
txn.start();
String sql = "INSERT INTO keystore (`name`, `certificate`, `key`, `domain_suffix`) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE `certificate`=?, `key`=?, `domain_suffix`=?";
PreparedStatement pstmt = txn.prepareAutoCloseStatement(sql);
pstmt.setString(1, name);
pstmt.setString(2, certificate);
pstmt.setString(3, key);
pstmt.setString(4, domainSuffix);
pstmt.setString(5, certificate);
pstmt.setString(6, key);
pstmt.setString(7, domainSuffix);
pstmt.executeUpdate();
txn.commit();
} catch(Exception e) {
txn.rollback();
throw new CloudRuntimeException("Unable to save certificate under name " + name + " due to exception", e);
}
}
}

View File

@ -0,0 +1,26 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.keystore;
import com.cloud.utils.component.Manager;
public interface KeystoreManager extends Manager {
void saveCertificate(String name, String certificate, String key, String domainSuffix);
byte[] getKeystoreBits(String name, String aliasForCertificateInStore, String storePassword);
}

View File

@ -0,0 +1,114 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.keystore;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ejb.Local;
import javax.naming.ConfigurationException;
import org.apache.log4j.Logger;
import com.cloud.utils.component.Inject;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.security.CertificateHelper;
@Local(value=KeystoreManager.class)
public class KeystoreManagerImpl implements KeystoreManager {
private static final Logger s_logger = Logger.getLogger(KeystoreManagerImpl.class);
private String _name;
@Inject private KeystoreDao _ksDao;
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
_name = name;
return true;
}
@Override
public boolean start() {
return true;
}
@Override
public boolean stop() {
return true;
}
@Override
public String getName() {
return _name;
}
@Override
public void saveCertificate(String name, String certificate, String key, String domainSuffix) {
if(name == null || name.isEmpty() ||
certificate == null || certificate.isEmpty() ||
key == null || key.isEmpty() ||
domainSuffix == null || domainSuffix.isEmpty())
throw new CloudRuntimeException("invalid parameter in saveCerticate");
_ksDao.save(name, certificate, key, domainSuffix);
}
@Override
public byte[] getKeystoreBits(String name, String aliasForCertificateInStore, String storePassword) {
assert(name != null);
assert(aliasForCertificateInStore != null);
assert(storePassword != null);
KeystoreVO ksVo = _ksDao.findByName(name);
if(ksVo == null)
throw new CloudRuntimeException("Unable to find keystore " + name);
try {
return CertificateHelper.buildAndSaveKeystore(aliasForCertificateInStore, ksVo.getCertificate(), getKeyContent(ksVo.getKey()), storePassword);
} catch(KeyStoreException e) {
s_logger.warn("Unable to build keystore for " + name + " due to KeyStoreException");
} catch(CertificateException e) {
s_logger.warn("Unable to build keystore for " + name + " due to CertificateException");
} catch(NoSuchAlgorithmException e) {
s_logger.warn("Unable to build keystore for " + name + " due to NoSuchAlgorithmException");
} catch(InvalidKeySpecException e) {
s_logger.warn("Unable to build keystore for " + name + " due to InvalidKeySpecException");
} catch(IOException e) {
s_logger.warn("Unable to build keystore for " + name + " due to IOException");
}
return null;
}
private static String getKeyContent(String key) {
Pattern regex = Pattern.compile("(^[\\-]+[^\\-]+[\\-]+[\\n]?)([^\\-]+)([\\-]+[^\\-]+[\\-]+$)");
Matcher m = regex.matcher(key);
if(m.find())
return m.group(2);
return key;
}
}

View File

@ -0,0 +1,90 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.keystore;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="keystore")
public class KeystoreVO {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private Long id;
@Column(name="name")
private String name;
@Column(name="certificate",length=65535)
private String certificate;
@Column(name="key",length=65535)
private String key;
@Column(name="domain_suffix")
private String domainSuffix;
public KeystoreVO() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCertificate() {
return certificate;
}
public void setCertificate(String certificate) {
this.certificate = certificate;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getDomainSuffix() {
return domainSuffix;
}
public void setDomainSuffix(String domainSuffix) {
this.domainSuffix = domainSuffix;
}
}

View File

@ -0,0 +1,157 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.keystore;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import junit.framework.TestCase;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import com.cloud.configuration.DefaultInterceptorLibrary;
import com.cloud.utils.component.ComponentLocator;
import com.cloud.utils.component.MockComponentLocator;
import com.cloud.utils.security.CertificateHelper;
public class KeystoreTest extends TestCase {
private final static Logger s_logger = Logger.getLogger(KeystoreTest.class);
private String keyContent =
"MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALV5vGlkiWwoZX4hTRplPXP8qtST\n" +
"hwZhko8noeY5vf8ECwmd+vrCTw/JvnOtkx/8oYNbg/SeUt1EfOsk6gqJdBblGFBZRMcUJlIpqE9z\n" +
"uv68U9G8Gfi/qvRSY336hibw0J5bZ4vn1QqmyHDB+Czea9AjFUV7AEVG15+vED7why+/AgMBAAEC\n" +
"gYBmFBPnNKYYMKDmUdUNA+WNWJK/ADzzWe8WlzR6TACTcbLDthl289WFC/YVG42mcHRpbxDKiEQU\n" +
"MnIR0rHTO34Qb/2HcuyweStU2gqR6omxBvMnFpJr90nD1HcOMJzeLHsphau0/EmKKey+gk4PyieD\n" +
"KqTM7LTjjHv8xPM4n+WAAQJBAOMNCeFKlJ4kMokWhU74B5/w/NGyT1BHUN0VmilHSiJC8JqS4BiI\n" +
"ZpAeET3VmilO6QTGh2XVhEDGteu3uZR6ipUCQQDMnRzMgQ/50LFeIQo4IBtwlEouczMlPQF4c21R\n" +
"1d720moxILVPT0NJZTQUDDmmgbL+B7CgtcCR2NlP5sKPZVADAkEAh4Xq1cy8dMBKYcVNgNtPQcqI\n" +
"PWpfKR3ISI5yXB0vRNAL6Vet5zbTcUZhKDVtNSbis3UEsGYH8NorEC2z2cpjGQJANhJi9Ow6c5Mh\n" +
"/DURBUn+1l5pyCKrZnDbvaALSLATLvjmFTuGjoHszy2OeKnOZmEqExWnKKE/VYuPyhy6V7i3TwJA\n" +
"f8skDgtPK0OsBCa6IljPaHoWBjPc4kFkSTSS1d56hUcWSikTmiuKdLyBb85AADSZYsvHWrte4opN\n" +
"dhNukMJuRA==\n";
private String certContent =
"-----BEGIN CERTIFICATE-----\n" +
"MIIE3jCCA8agAwIBAgIFAqv56tIwDQYJKoZIhvcNAQEFBQAwgcoxCzAJBgNVBAYT\n" +
"AlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYD\n" +
"VQQKExFHb0RhZGR5LmNvbSwgSW5jLjEzMDEGA1UECxMqaHR0cDovL2NlcnRpZmlj\n" +
"YXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MTAwLgYDVQQDEydHbyBEYWRkeSBT\n" +
"ZWN1cmUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxETAPBgNVBAUTCDA3OTY5Mjg3\n" +
"MB4XDTA5MDIxMTA0NTc1NloXDTEyMDIwNzA1MTEyM1owWTEZMBcGA1UECgwQKi5y\n" +
"ZWFsaG9zdGlwLmNvbTEhMB8GA1UECwwYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVk\n" +
"MRkwFwYDVQQDDBAqLnJlYWxob3N0aXAuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN\n" +
"ADCBiQKBgQC1ebxpZIlsKGV+IU0aZT1z/KrUk4cGYZKPJ6HmOb3/BAsJnfr6wk8P\n" +
"yb5zrZMf/KGDW4P0nlLdRHzrJOoKiXQW5RhQWUTHFCZSKahPc7r+vFPRvBn4v6r0\n" +
"UmN9+oYm8NCeW2eL59UKpshwwfgs3mvQIxVFewBFRtefrxA+8IcvvwIDAQABo4IB\n" +
"vTCCAbkwDwYDVR0TAQH/BAUwAwEBADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB\n" +
"BQUHAwIwDgYDVR0PAQH/BAQDAgWgMDIGA1UdHwQrMCkwJ6AloCOGIWh0dHA6Ly9j\n" +
"cmwuZ29kYWRkeS5jb20vZ2RzMS0yLmNybDBTBgNVHSAETDBKMEgGC2CGSAGG/W0B\n" +
"BxcBMDkwNwYIKwYBBQUHAgEWK2h0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j\n" +
"b20vcmVwb3NpdG9yeS8wgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYYaHR0\n" +
"cDovL29jc3AuZ29kYWRkeS5jb20vMEoGCCsGAQUFBzAChj5odHRwOi8vY2VydGlm\n" +
"aWNhdGVzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvZ2RfaW50ZXJtZWRpYXRlLmNy\n" +
"dDAfBgNVHSMEGDAWgBT9rGEyk2xF1uLuhV+auud2mWjM5zArBgNVHREEJDAighAq\n" +
"LnJlYWxob3N0aXAuY29tgg5yZWFsaG9zdGlwLmNvbTAdBgNVHQ4EFgQUHxwmdK5w\n" +
"9/YVeZ/3fHyi6nQfzoYwDQYJKoZIhvcNAQEFBQADggEBABv/XinvId6oWXJtmku+\n" +
"7m90JhSVH0ycoIGjgdaIkcExQGP08MCilbUsPcbhLheSFdgn/cR4e1MP083lacoj\n" +
"OGauY7b8f/cuquGkT49Ns14awPlEzRjjycQEjjLxFEuL5CFWa2t2gKRE1dSfhDQ+\n" +
"fJ6GBCs1XgZLuhkKS8fPf+YmG2ZjHzYDjYoSx7paDXgEm+kbYIZdCK51lA0BUAjP\n" +
"9ZMGhsu/PpAbh5U/DtcIqxY0xeqD4TeGsBzXg6uLhv+jKHDtXg5fYPe+z0n5DCEL\n" +
"k0fLF4+i/pt9hVCz0QrZ28RUhXf825+EOL0Gw+Uzt+7RV2cCaJrlu4cDrDom2FRy\n" +
"E8I=\n" +
"-----END CERTIFICATE-----\n";
@Override
@Before
public void setUp() {
MockComponentLocator locator = new MockComponentLocator("management-server");
locator.addDao("keystoreDao", KeystoreDaoImpl.class);
locator.addManager("KeystoreManager", KeystoreManagerImpl.class);
locator.makeActive(new DefaultInterceptorLibrary());
}
@Override
@After
public void tearDown() throws Exception {
}
public void testKeystoreSave() throws Exception {
KeystoreVO ksVo;
ComponentLocator locator = ComponentLocator.getCurrentLocator();
KeystoreDao ksDao = locator.getDao(KeystoreDao.class);
ksDao.save("CPVMCertificate", "CPVMCertificate", "KeyForCertificate", "realhostip.com");
ksVo = ksDao.findByName("CPVMCertificate");
assertTrue(ksVo != null);
assertTrue(ksVo.getCertificate().equals("CPVMCertificate"));
assertTrue(ksVo.getKey().equals("KeyForCertificate"));
assertTrue(ksVo.getDomainSuffix().equals("realhostip.com"));
ksDao.save("CPVMCertificate", "CPVMCertificate Again", "KeyForCertificate Again", "again.realhostip.com");
ksVo = ksDao.findByName("CPVMCertificate");
assertTrue(ksVo != null);
assertTrue(ksVo.getCertificate().equals("CPVMCertificate Again"));
assertTrue(ksVo.getKey().equals("KeyForCertificate Again"));
assertTrue(ksVo.getDomainSuffix().equals("again.realhostip.com"));
ksDao.expunge(ksVo.getId());
}
public void testStripeKey() throws Exception {
Pattern regex = Pattern.compile("(^[\\-]+[^\\-]+[\\-]+[\\n]?)([^\\-]+)([\\-]+[^\\-]+[\\-]+$)");
Matcher m = regex.matcher("-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAm4bLUORp9oM65GV9XrPrbs+K563DjUR1M8mP1HaE+Y4lX5pk\nvQjC/xoEqSs5pxDDWXAkoexvxij8A4AWcsKU1Q+ep2E+GcytBoz8XINGvgb8cQNn\n/4PlVWKp7j5SDDNCfleYvmiRn8k6P4mxVJOHKzwb/IwQcKghyqAF1w==\n-----END RSA PRIVATE KEY-----");
if(m.find()) {
String content = m.group(2);
assertTrue(content.startsWith("MIIEpAIBAAKCAQE"));
assertTrue(content.endsWith("KghyqAF1w==\n"));
} else {
assertTrue(false);
}
}
public void testKeystoreManager() throws Exception {
ComponentLocator locator = ComponentLocator.getCurrentLocator();
KeystoreManagerImpl ksMgr = ComponentLocator.inject(KeystoreManagerImpl.class);
assertTrue(ksMgr.configure("TaskManager", new HashMap<String, Object>()));
assertTrue(ksMgr.start());
ksMgr.saveCertificate("CPVMCertificate", certContent, keyContent, "realhostip.com");
byte[] ksBits = ksMgr.getKeystoreBits("CPVMCertificate", "realhostip", "vmops.com");
assertTrue(ksBits != null);
try {
KeyStore ks = CertificateHelper.loadKeystore(ksBits, "vmops.com");
assertTrue(ks != null);
} catch(Exception e) {
assertTrue(false);
}
KeystoreDao ksDao = locator.getDao(KeystoreDao.class);
KeystoreVO ksVo = ksDao.findByName("CPVMCertificate");
ksDao.expunge(ksVo.getId());
}
}

View File

@ -1494,4 +1494,14 @@ CREATE TABLE `cloud`.`cmd_exec_log` (
CONSTRAINT `fk_cmd_exec_log_ref__inst_id` FOREIGN KEY (`instance_id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE CONSTRAINT `fk_cmd_exec_log_ref__inst_id` FOREIGN KEY (`instance_id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `cloud`.`keystore` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(64) NOT NULL COMMENT 'unique name for the certifiation',
`certificate` text NOT NULL COMMENT 'the actual certificate being stored in the db',
`key` text NOT NULL COMMENT 'private key associated wih the certificate',
`domain_suffix` varchar(256) NOT NULL COMMENT 'DNS domain suffix associated with the certificate',
PRIMARY KEY (`id`),
UNIQUE(name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET foreign_key_checks = 1; SET foreign_key_checks = 1;