Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
package org.dogtagpki.acme.database;

import java.io.FileReader;
import java.security.cert.X509Certificate;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -25,6 +27,7 @@
import org.dogtagpki.acme.ACMENonce;
import org.dogtagpki.acme.ACMEOrder;
import org.dogtagpki.acme.JWK;
import org.mozilla.jss.netscape.security.x509.X509CertImpl;

/**
* @author Endi S. Dewata
Expand All @@ -33,45 +36,31 @@ public class PostgreSQLDatabase extends ACMEDatabase {

public static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(PostgreSQLDatabase.class);

protected Properties statements = new Properties();
protected Properties info;
protected String url;

protected Properties statements;
protected Connection connection;

public void init() throws Exception {

logger.info("Initializing PostgreSQL database");

Properties info = new Properties();
info = new Properties();
for (String name : config.getParameterNames()) {
String value = config.getParameter(name);
info.put(name, value);
}

String url = (String) info.remove("url");
logger.info("Connecting to " + url);
connection = DriverManager.getConnection(url, info);

DatabaseMetaData md = connection.getMetaData();
ResultSet rs = null;

try {
logger.info("Tables:");
rs = md.getTables(null, null, "%", new String[] { "TABLE" });

while (rs.next()) {
String name = rs.getString(3);
logger.info("- " + name);
}

} finally {
if (rs != null) rs.close();
}
url = (String) info.remove("url");

String statementsFilename = info.getProperty(
"statements",
"/usr/share/pki/acme/conf/database/postgresql/statements.conf");
"/usr/share/pki/acme/database/postgresql/statements.conf");

logger.info("Loading statements from " + statementsFilename);

statements = new Properties();
try (FileReader reader = new FileReader(statementsFilename)) {
statements.load(reader);
}
Expand All @@ -82,6 +71,45 @@ public void init() throws Exception {
}
}

/**
* This method will create the initial connection, validate
* the current connection, or reestablish the connection if
* it's closed.
*
* This method should only be called by methods implementing
* ACMEDatabase.
*
* TODO: Use connection pool.
*/
public void connect() throws Exception {

if (connection == null) { // create the initial connection
logger.info("Connecting to " + url);
connection = DriverManager.getConnection(url, info);
return;
}

// validate the current connection
try (Statement st = connection.createStatement();
ResultSet rs = st.executeQuery("SELECT 1")) {

} catch (SQLException e) {

if (connection.isClosed()) { // reestablish the connection
logger.info("Reconnecting to " + url);
connection = DriverManager.getConnection(url, info);
return;
}

logger.error("Unable to access database: " + e.getMessage());

// https://www.postgresql.org/docs/current/errcodes-appendix.html
logger.error("SQL state: " + e.getSQLState());

throw e;
}
}

public void close() throws Exception {
if (connection != null) {
connection.close();
Expand Down Expand Up @@ -123,6 +151,8 @@ public void addNonce(ACMENonce nonce) throws Exception {
String sql = statements.getProperty("addNonce");
logger.info("SQL: " + sql);

connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {

ps.setString(1, value);
Expand All @@ -136,6 +166,8 @@ public void addNonce(ACMENonce nonce) throws Exception {

public ACMENonce removeNonce(String value) throws Exception {

connect();

ACMENonce nonce = getNonce(value);
if (nonce == null) return null;

Expand All @@ -154,6 +186,8 @@ public ACMENonce removeNonce(String value) throws Exception {

public void removeExpiredNonces(Date currentTime) throws Exception {

connect();

Collection<ACMENonce> nonces = getExpiredNonces(currentTime);

for (ACMENonce nonce : nonces) {
Expand Down Expand Up @@ -201,6 +235,7 @@ public ACMEAccount getAccount(String accountID) throws Exception {
logger.info("SQL: " + sql);

ACMEAccount account = new ACMEAccount();
connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, accountID);
Expand Down Expand Up @@ -259,6 +294,8 @@ public void addAccount(ACMEAccount account) throws Exception {
String sql = statements.getProperty("addAccount");
logger.info("SQL: " + sql);

connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {

ps.setString(1, accountID);
Expand All @@ -279,6 +316,8 @@ public void updateAccount(ACMEAccount account) throws Exception {
String sql = statements.getProperty("updateAccount");
logger.info("SQL: " + sql);

connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {

ps.setString(1, account.getStatus());
Expand Down Expand Up @@ -338,6 +377,7 @@ public ACMEOrder getOrder(String orderID) throws Exception {
logger.info("SQL: " + sql);

ACMEOrder order = new ACMEOrder();
connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, orderID);
Expand Down Expand Up @@ -380,6 +420,7 @@ public Collection<ACMEOrder> getOrdersByAuthorizationAndStatus(String authzID, S
logger.info("SQL: " + sql);

Collection<ACMEOrder> orders = new ArrayList<>();
connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, authzID);
Expand Down Expand Up @@ -423,6 +464,7 @@ public ACMEOrder getOrderByCertificate(String certID) throws Exception {
logger.info("SQL: " + sql);

ACMEOrder order = new ACMEOrder();
connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, certID);
Expand Down Expand Up @@ -523,6 +565,8 @@ public void addOrder(ACMEOrder order) throws Exception {
String sql = statements.getProperty("addOrder");
logger.info("SQL: " + sql);

connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {

ps.setString(1, orderID);
Expand Down Expand Up @@ -602,6 +646,8 @@ public void updateOrder(ACMEOrder order) throws Exception {
String sql = statements.getProperty("updateOrder");
logger.info("SQL: " + sql);

connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {

ps.setString(1, order.getStatus());
Expand All @@ -620,6 +666,7 @@ public ACMEAuthorization getAuthorization(String authzID) throws Exception {
logger.info("SQL: " + sql);

ACMEAuthorization authorization = new ACMEAuthorization();
connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, authzID);
Expand Down Expand Up @@ -660,6 +707,7 @@ public ACMEAuthorization getAuthorizationByChallenge(String challengeID) throws
logger.info("SQL: " + sql);

ACMEAuthorization authorization = new ACMEAuthorization();
connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, challengeID);
Expand Down Expand Up @@ -700,6 +748,7 @@ public Collection<ACMEAuthorization> getRevocationAuthorizations(String accountI
logger.info("SQL: " + sql);

Collection<ACMEAuthorization> authorizations = new ArrayList<>();
connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, accountID);
Expand Down Expand Up @@ -781,6 +830,8 @@ public void addAuthorization(ACMEAuthorization authorization) throws Exception {
String sql = statements.getProperty("addAuthorization");
logger.info("SQL: " + sql);

connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {

ps.setString(1, authzID);
Expand Down Expand Up @@ -811,6 +862,8 @@ public void updateAuthorization(ACMEAuthorization authorization) throws Exceptio
String sql = statements.getProperty("updateAuthorization");
logger.info("SQL: " + sql);

connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {

ps.setString(1, authorization.getStatus());
Expand Down Expand Up @@ -867,4 +920,63 @@ public void addAuthorizationChallenges(ACMEAuthorization authorization) throws E
}
}
}

public X509Certificate getCertificate(String certID) throws Exception {

logger.info("Getting certificate " + certID);

String sql = statements.getProperty("getCertificate");
logger.info("SQL: " + sql);

connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, certID);

try (ResultSet rs = ps.executeQuery()) {

if (!rs.next()) {
return null;
}

byte[] bytes = rs.getBytes("data");
return new X509CertImpl(bytes);
}
}
}

public void addCertificate(String certID, X509Certificate cert) throws Exception {

logger.info("Adding certificate " + certID);

String sql = statements.getProperty("addCertificate");
logger.info("SQL: " + sql);

connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {

ps.setString(1, certID);
ps.setBytes(2, cert.getEncoded());

ps.executeUpdate();
}
}

public void removeCertificate(String certID) throws Exception {

logger.info("Deleting certificate " + certID);

String sql = statements.getProperty("removeCertificate");
logger.info("SQL: " + sql);

connect();

try (PreparedStatement ps = connection.prepareStatement(sql)) {

ps.setString(1, certID);

ps.executeUpdate();
}
}
}
158 changes: 158 additions & 0 deletions base/acme/src/main/java/org/dogtagpki/acme/issuer/NSSIssuer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//
// Copyright Red Hat, Inc.
//
// SPDX-License-Identifier: GPL-2.0-or-later
//
package org.dogtagpki.acme.issuer;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigInteger;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.cert.X509Certificate;

import org.apache.commons.codec.binary.Base64;
import org.dogtagpki.acme.database.ACMEDatabase;
import org.dogtagpki.acme.server.ACMEEngine;
import org.dogtagpki.nss.NSSDatabase;
import org.dogtagpki.nss.NSSExtensionGenerator;
import org.mozilla.jss.CryptoManager;
import org.mozilla.jss.netscape.security.pkcs.PKCS10;
import org.mozilla.jss.netscape.security.util.Cert;
import org.mozilla.jss.netscape.security.util.Utils;
import org.mozilla.jss.netscape.security.x509.CertificateExtensions;
import org.mozilla.jss.netscape.security.x509.X509CertImpl;

import com.netscape.cmsutil.password.IPasswordStore;
import com.netscape.cmsutil.password.PlainPasswordFile;

/**
* @author Endi S. Dewata
*/
public class NSSIssuer extends ACMEIssuer {

public static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(NSSIssuer.class);

NSSDatabase nssDatabase;
IPasswordStore passwordStore;

org.mozilla.jss.crypto.X509Certificate issuer;

NSSExtensionGenerator extGenerator;
Integer monthsValid;

public void init() throws Exception {

logger.info("Initializing NSS issuer");

Path instanceDir = Paths.get(System.getProperty("catalina.base"));

String database = config.getParameter("database");
if (database == null) database = "conf/alias";

Path databasePath = instanceDir.resolve(database);
logger.info("- database: " + databasePath);

nssDatabase = new NSSDatabase(databasePath);

String passwords = config.getParameter("passwords");
if (passwords == null) passwords = "conf/password.conf";

Path passwordsPath = instanceDir.resolve(passwords);
logger.info("- passwords: " + passwordsPath);

passwordStore = new PlainPasswordFile();
passwordStore.init(passwordsPath.toString());
nssDatabase.setPasswordStore(passwordStore);

String nickname = config.getParameter("nickname");
if (nickname == null) nickname = "ca_signing";
logger.info("- nickname: " + nickname);

CryptoManager cm = CryptoManager.getInstance();
issuer = cm.findCertByNickname(nickname);

String monthsValid = config.getParameter("monthsValid");
if (monthsValid != null) {
logger.info("- months valid: " + monthsValid);

this.monthsValid = new Integer(monthsValid);
}

String extensions = config.getParameter("extensions");
if (extensions == null) extensions = "/usr/share/pki/acme/issuer/nss/sslserver.conf";
logger.info("- extensions: " + extensions);

Path extPath = instanceDir.resolve(extensions);
extGenerator = new NSSExtensionGenerator();
extGenerator.init(extPath.toString());
}

public String issueCertificate(PKCS10 pkcs10) throws Exception {

logger.info("Issuing certificate");

ACMEEngine engine = ACMEEngine.getInstance();
ACMEDatabase acmeDatabase = engine.getDatabase();

CertificateExtensions extensions = null;
if (extGenerator != null) {
extensions = extGenerator.createExtensions(issuer, pkcs10);
}

X509Certificate cert = nssDatabase.createCertificate(
issuer,
pkcs10,
monthsValid,
extensions);

BigInteger serialNumber = cert.getSerialNumber();
String certID = Base64.encodeBase64URLSafeString(serialNumber.toByteArray());
acmeDatabase.addCertificate(certID, cert);

return certID;
}

public X509Certificate[] getCACertificateChain() throws Exception {

CryptoManager cm = CryptoManager.getInstance();
org.mozilla.jss.crypto.X509Certificate[] caCertChain = cm.buildCertificateChain(issuer);

X509Certificate[] caCertChainImpl = new X509Certificate[caCertChain.length];
for (int i = 0; i < caCertChain.length; i++) {
org.mozilla.jss.crypto.X509Certificate cert = caCertChain[i];
caCertChainImpl[i] = new X509CertImpl(cert.getEncoded());
}

return caCertChainImpl;
}

public String getCertificateChain(String certID) throws Exception {

logger.info("Retrieving certificate");

ACMEEngine engine = ACMEEngine.getInstance();
ACMEDatabase acmeDatabase = engine.getDatabase();

X509Certificate cert = acmeDatabase.getCertificate(certID);
X509Certificate[] certChain = getCACertificateChain();

StringWriter sw = new StringWriter();

try (PrintWriter out = new PrintWriter(sw, true)) {

out.println(Cert.HEADER);
out.print(Utils.base64encodeMultiLine(cert.getEncoded()));
out.println(Cert.FOOTER);

for (X509Certificate caCert : certChain) {
out.println(Cert.HEADER);
out.print(Utils.base64encodeMultiLine(caCert.getEncoded()));
out.println(Cert.FOOTER);
}
}

return sw.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//
// Copyright Red Hat, Inc.
//
// SPDX-License-Identifier: GPL-2.0-or-later
//
package org.dogtagpki.acme.server;

import java.util.Collection;
import java.util.Date;

import org.dogtagpki.acme.ACMEAccount;
import org.dogtagpki.acme.ACMEAuthorization;
import org.dogtagpki.acme.ACMEChallenge;
import org.dogtagpki.acme.ACMEOrder;
import org.dogtagpki.acme.validator.ACMEValidator;

/**
* @author Endi S. Dewata
*/
public class ACMEChallengeProcessor implements Runnable {

public static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(ACMEChallengeProcessor.class);

ACMEAccount account;
ACMEAuthorization authorization;
ACMEChallenge challenge;
ACMEValidator validator;

public ACMEChallengeProcessor(
ACMEAccount account,
ACMEAuthorization authorization,
ACMEChallenge challenge,
ACMEValidator validator) {

this.account = account;
this.authorization = authorization;
this.challenge = challenge;
this.validator = validator;
}

public void run() {
try {
processChallenge();
} catch (Exception e) {
logger.error("Unable to process challenge " + challenge.getID() + ": " + e.getMessage(), e);
}
}

public void processChallenge() throws Exception {

String authzID = authorization.getID();
String challengeID = challenge.getID();

logger.info("Processing challenge " + challengeID);

ACMEEngine engine = ACMEEngine.getInstance();

try {
validator.validateChallenge(authorization, challenge);

} catch (Exception e) {

// RFC 8555 Section 8.2: Retrying Challenges
//
// The server MUST provide information about its retry state to the
// client via the "error" field in the challenge and the Retry-After
// HTTP header field in response to requests to the challenge resource.
// The server MUST add an entry to the "error" field in the challenge
// after each failed validation query. The server SHOULD set the Retry-
// After header field to a time after the server's next validation
// query, since the status of the challenge will not change until that
// time.

logger.info("Challenge " + challengeID + " is invalid");
challenge.setStatus("invalid");
engine.updateAuthorization(account, authorization);
throw e;
}

logger.info("Challenge " + challengeID + " is valid");
challenge.setStatus("valid");
challenge.setValidationTime(new Date());

logger.info("Authorization " + authzID + " is valid");
authorization.setStatus("valid");

engine.updateAuthorization(account, authorization);

logger.info("Checking associated pending orders for validity");
Collection<ACMEOrder> orders =
engine.getOrdersByAuthorizationAndStatus(account, authzID, "pending");

for (ACMEOrder order : orders) {
boolean allAuthorizationsValid = true;

for (String orderAuthzID : order.getAuthzIDs()) {
ACMEAuthorization authz = engine.getAuthorization(account, orderAuthzID);
if (!authz.getStatus().equals("valid")) {
allAuthorizationsValid = false;
break;
}
}

if (allAuthorizationsValid) {
logger.info("Order " + order.getID() + " is ready");
order.setStatus("ready");
engine.updateOrder(account, order);
} else {
logger.info("Order " + order.getID() + " is not ready");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
package org.dogtagpki.acme.server;

import java.net.URI;
import java.util.Collection;
import java.util.Date;

import javax.ws.rs.POST;
import javax.ws.rs.Path;
Expand All @@ -24,7 +22,6 @@
import org.dogtagpki.acme.ACMEChallenge;
import org.dogtagpki.acme.ACMEHeader;
import org.dogtagpki.acme.ACMENonce;
import org.dogtagpki.acme.ACMEOrder;
import org.dogtagpki.acme.JWS;
import org.dogtagpki.acme.validator.ACMEValidator;

Expand Down Expand Up @@ -73,71 +70,46 @@ public Response handlePOST(@PathParam("id") String challengeID, JWS jws) throws
throw new Exception("Unknown challenge: " + challengeID);
}

String type = challenge.getType();
logger.info("Challenge Type: " + type);

ACMEValidator validator = engine.getValidator(type);
if (validator == null) {
// TODO: generate proper exception
throw new Exception("Unsupported challenge type: " + type);
}

String challengeStatus = challenge.getStatus();
if (challengeStatus.equals("pending")) {
challenge.setStatus("processing");
engine.updateAuthorization(account, authorization);

} else if (challengeStatus.equals("processing")) {
// retrying the challenge, ignore

} else {
// TODO: generate proper exception
throw new Exception("Challenge is already " + challengeStatus);
}
String type = challenge.getType();
logger.info("Challenge Type: " + type);

try {
validator.validateChallenge(authorization, challenge);
ACMEValidator validator = engine.getValidator(type);
if (validator == null) {
// TODO: generate proper exception
throw new Exception("Unsupported challenge type: " + type);
}

} catch (Exception e) {
logger.info("Challenge " + challengeID + " is invalid");
challenge.setStatus("invalid");
challenge.setStatus("processing");
engine.updateAuthorization(account, authorization);
throw e;
}

logger.info("Challenge " + challengeID + " is valid");
challenge.setStatus("valid");
challenge.setValidationTime(new Date());

logger.info("Authorization " + authzID + " is valid");
authorization.setStatus("valid");

engine.updateAuthorization(account, authorization);

logger.info("Checking associated pending orders for validity");
Collection<ACMEOrder> orders =
engine.getOrdersByAuthorizationAndStatus(account, authzID, "pending");
ACMEChallengeProcessor processor = new ACMEChallengeProcessor(
account,
authorization,
challenge,
validator);

for (ACMEOrder order : orders) {
boolean allAuthorizationsValid = true;
// TODO: use thread pool
new Thread(processor).start();

for (String orderAuthzID : order.getAuthzIDs()) {
ACMEAuthorization authz = engine.getAuthorization(account, orderAuthzID);
if (authz.getStatus().equals("valid")) {
continue;
} else {
allAuthorizationsValid = false;
break;
}
}
} else if (challengeStatus.equals("processing")) {
// TODO: retry the challenge

// RFC 8555 Section 8.2: Retrying Challenges
//
// Clients can explicitly request a retry by re-sending their response
// to a challenge in a new POST request (with a new nonce, etc.). This
// allows clients to request a retry when the state has changed (e.g.,
// after firewall rules have been updated). Servers SHOULD retry a
// request immediately on receiving such a POST request. In order to
// avoid denial-of-service attacks via client-initiated retries, servers
// SHOULD rate-limit such requests.

if (allAuthorizationsValid) {
logger.info("Order " + order.getID() + " is ready");
order.setStatus("ready");
engine.updateOrder(account, order);
} else {
logger.info("Order " + order.getID() + " is not ready");
}
} else {
// TODO: generate proper exception
throw new Exception("Challenge is already " + challengeStatus);
}

URI challengeURL = uriInfo.getBaseUriBuilder().path("chall").path(challengeID).build();
Expand Down
34 changes: 18 additions & 16 deletions base/acme/src/main/java/org/dogtagpki/acme/server/ACMEEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -284,18 +284,16 @@ public void loadValidatorsConfig(String filename) throws Exception {

File validatorsConfigFile = new File(filename);

if (validatorsConfigFile.exists()) {
logger.info("Loading ACME validators config from " + validatorsConfigFile);
Properties props = new Properties();
try (FileReader reader = new FileReader(validatorsConfigFile)) {
props.load(reader);
}
validatorsConfig = ACMEValidatorsConfig.fromProperties(props);
if (!validatorsConfigFile.exists()) {
validatorsConfigFile = new File("/usr/share/pki/acme/conf/validators.conf");
}

} else {
logger.info("Loading default ACME validators config");
validatorsConfig = new ACMEValidatorsConfig();
logger.info("Loading ACME validators config from " + validatorsConfigFile);
Properties props = new Properties();
try (FileReader reader = new FileReader(validatorsConfigFile)) {
props.load(reader);
}
validatorsConfig = ACMEValidatorsConfig.fromProperties(props);
}

public void initValidators() throws Exception {
Expand Down Expand Up @@ -363,7 +361,7 @@ public void shutdownIssuer() throws Exception {

public void contextInitialized(ServletContextEvent event) {

logger.info("Initializing ACME engine");
logger.info("Starting ACME engine");

String path = event.getServletContext().getContextPath();
if ("".equals(path)) {
Expand Down Expand Up @@ -409,14 +407,16 @@ public void contextInitialized(ServletContextEvent event) {
initIssuer();

} catch (Exception e) {
logger.error("Unable to initialize ACME engine: " + e.getMessage(), e);
throw new RuntimeException("Unable to initialize ACME engine: " + e.getMessage(), e);
logger.error("Unable to start ACME engine: " + e.getMessage(), e);
throw new RuntimeException("Unable to start ACME engine: " + e.getMessage(), e);
}

logger.info("ACME engine started");
}

public void contextDestroyed(ServletContextEvent event) {

logger.info("Shutting down ACME engine");
logger.info("Stopping ACME engine");

if (engineConfigSource != null) {
engineConfigSource.shutdown();
Expand All @@ -429,9 +429,11 @@ public void contextDestroyed(ServletContextEvent event) {
shutdownDatabase();

} catch (Exception e) {
logger.error("Unable to initialize ACME engine: " + e.getMessage(), e);
throw new RuntimeException("Unable to shutdown ACME engine: " + e.getMessage(), e);
logger.error("Unable to stop ACME engine: " + e.getMessage(), e);
throw new RuntimeException("Unable to stop ACME engine: " + e.getMessage(), e);
}

logger.info("ACME engine stopped");
}

public ACMENonce createNonce() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public void validateChallenge(
String hostname = identifier.getValue();
String recordName = "_acme-challenge." + hostname;

// TODO: move retry to ACMEChallengeProcessor.processChallenge()
// TODO: make it configurable

int maxCount = 5;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public void validateChallenge(
String validationPath = "/.well-known/acme-challenge/" + token;
URI validationURL = new URI("http", hostname, validationPath, null);

// TODO: move retry to ACMEChallengeProcessor.processChallenge()
// TODO: make it configurable

int maxCount = 5;
Expand Down
2 changes: 1 addition & 1 deletion base/ca/shared/webapps/ca/agent/ca/ProfileReview.template
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ document.writeln('</FONT>');
} else if (recordSet[i].defListSet[j].defSyntax == 'string_list') {
document.writeln('<textarea cols=40 rows=5 name="' + recordSet[i].defListSet[j].defId + '">' + recordSet[i].defListSet[j].defVal + '</textarea>');
} else if (recordSet[i].defListSet[j].defSyntax == 'integer') {
document.writeln('<input size=6 type=text name="' + recordSet[i].defListSet[j].defId + '" value="' + recordSet[i].defListSet[j].defVal + '">');
document.writeln('<input size=6 type=number name="' + recordSet[i].defListSet[j].defId + '" value="' + recordSet[i].defListSet[j].defVal + '">');
} else if (recordSet[i].defListSet[j].defSyntax == 'image_url') {
document.writeln('<img border=0 src="' + recordSet[i].defListSet[j].defVal + '">');
document.writeln('<input type=hidden name="' + recordSet[i].defListSet[j].defId + '" value="' + recordSet[i].defListSet[j].defVal + '">');
Expand Down
306 changes: 182 additions & 124 deletions base/ca/src/com/netscape/ca/CAService.java

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions base/ca/src/com/netscape/ca/CertificateAuthority.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@
package com.netscape.ca;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.MessageDigest;
Expand Down Expand Up @@ -52,6 +50,7 @@
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;
import org.dogtag.util.cert.CertUtil;
import org.dogtagpki.legacy.ca.CAPolicy;
import org.dogtagpki.legacy.policy.IPolicyProcessor;
import org.dogtagpki.server.ca.CAConfig;
Expand Down Expand Up @@ -2878,9 +2877,7 @@ public ICertificateAuthority createSubCA(
signature.initSign(keypair.getPrivate());
pkcs10.encodeAndSign(
new X500Signer(signature, subjectX500Name));
ByteArrayOutputStream out = new ByteArrayOutputStream();
pkcs10.print(new PrintStream(out));
String pkcs10String = out.toString();
String pkcs10String = CertUtil.toPEM(pkcs10);

// Sign certificate
Locale locale = Locale.getDefault();
Expand Down
2 changes: 1 addition & 1 deletion base/common/examples/java/CAClientExample.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
* $ pki -c Secret.123 client-init
*
* Then import CA admin certificate and key from PKCS #12 file:
* $ pki -c Secret.123 client-cert-import --pkcs12 &lt;file&gt; --pkcs12-password &lt;password&gt;
* $ pki -c Secret.123 pkcs12-import --pkcs12 &lt;file&gt; --password &lt;password&gt;
*
* To compile the program:
* $ javac -cp "/usr/lib/java/jss4.jar:../../lib/*" CAClientExample.java
Expand Down
34 changes: 23 additions & 11 deletions base/common/python/pki/cli/pkcs12.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ def __init__(self):
def print_help(self):
print('Usage: pki pkcs12-import [OPTIONS]')
print()
print(' --pkcs12-file <path> PKCS #12 file containing certificates and '
'keys.')
print(' --pkcs12-password <password> Password for the PKCS #12 file.')
print(' --pkcs12-password-file <path> containing the PKCS #12 password.')
print(' --pkcs12 <path> PKCS #12 file')
print(' --pkcs12-file <path> DEPRECATED: PKCS #12 file')
print(' --password <password> PKCS #12 password')
print(' --pkcs12-password <password> DEPRECATED: PKCS #12 password')
print(' --password-file <path> PKCS #12 password file')
print(' --pkcs12-password-file <path> DEPRECATED: PKCS #12 password file')
print(' --no-trust-flags Do not include trust flags')
print(' --no-user-certs Do not import user certificates')
print(' --no-ca-certs Do not import CA certificates')
Expand All @@ -70,6 +72,7 @@ def execute(self, argv):

try:
opts, _ = getopt.gnu_getopt(argv, 'v', [
'pkcs12=', 'password=', 'password-file=',
'pkcs12-file=', 'pkcs12-password=', 'pkcs12-password-file=',
'no-trust-flags', 'no-user-certs', 'no-ca-certs', 'overwrite',
'verbose', 'debug', 'help'])
Expand All @@ -88,12 +91,21 @@ def execute(self, argv):
overwrite = False

for o, a in opts:
if o == '--pkcs12-file':
if o == '--pkcs12':
pkcs12_file = a

elif o == '--pkcs12-file':
pkcs12_file = a

elif o == '--password':
pkcs12_password = a

elif o == '--pkcs12-password':
pkcs12_password = a

elif o == '--password-file':
password_file = a

elif o == '--pkcs12-password-file':
password_file = a

Expand Down Expand Up @@ -154,13 +166,13 @@ def execute(self, argv):
cmd = ['pkcs12-cert-find']

if pkcs12_file:
cmd.extend(['--pkcs12-file', pkcs12_file])
cmd.extend(['--pkcs12', pkcs12_file])

if pkcs12_password:
cmd.extend(['--pkcs12-password', pkcs12_password])
cmd.extend(['--password', pkcs12_password])

if password_file:
cmd.extend(['--pkcs12-password-file', password_file])
cmd.extend(['--password-file', password_file])

if logger.isEnabledFor(logging.DEBUG):
cmd.extend(['--debug'])
Expand Down Expand Up @@ -300,13 +312,13 @@ def execute(self, argv):
cmd = ['pkcs12-import']

if pkcs12_file:
cmd.extend(['--pkcs12-file', pkcs12_file])
cmd.extend(['--pkcs12', pkcs12_file])

if pkcs12_password:
cmd.extend(['--pkcs12-password', pkcs12_password])
cmd.extend(['--password', pkcs12_password])

if password_file:
cmd.extend(['--pkcs12-password-file', password_file])
cmd.extend(['--password-file', password_file])

if no_trust_flags:
cmd.extend(['--no-trust-flags'])
Expand Down
80 changes: 79 additions & 1 deletion base/common/python/pki/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,17 @@

from __future__ import absolute_import
from __future__ import print_function

import functools
import inspect
import logging
import os
import ssl
import warnings

import requests
from requests import adapters
from requests.adapters import DEFAULT_POOLBLOCK, DEFAULT_POOLSIZE, DEFAULT_RETRIES
try:
from requests.packages.urllib3.exceptions import InsecureRequestWarning
except ImportError:
Expand All @@ -51,6 +56,75 @@ def wrapper(self, *args, **kwargs):
return wrapper


class SSLContextAdapter(adapters.HTTPAdapter):
"""
Custom SSLContext Adapter for requests
"""

def __init__(self, pool_connections=DEFAULT_POOLSIZE,
pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES,
pool_block=DEFAULT_POOLBLOCK, verify=True,
cert_paths=None):
self.verify = verify
self.cafiles = []
self.capaths = []

cert_paths = cert_paths or []

if isinstance(cert_paths, str):
cert_paths = [cert_paths]

for path in cert_paths:
path = path and os.path.expanduser(path)

if os.path.isdir(path):
self.capaths.append(path)
elif os.path.exists(path):
self.cafiles.append(path)
else:
logger.warning("cert_path missing; not used for validation: %s",
path)

# adapters.HTTPAdapter.__init__ calls our init_poolmanager, which needs
# our cafiles/capaths variables we set up above.
super(SSLContextAdapter, self).__init__(pool_connections=pool_connections,
pool_maxsize=pool_maxsize,
max_retries=max_retries,
pool_block=pool_block)

def init_poolmanager(self, connections, maxsize,
block=adapters.DEFAULT_POOLBLOCK, **pool_kwargs):
context = ssl.SSLContext(
ssl.PROTOCOL_TLS # pylint: disable=no-member
)

# Enable post handshake authentication for TLS 1.3
if getattr(context, "post_handshake_auth", None) is not None:
context.post_handshake_auth = True

# Load from the system trust store when possible; per documentation
# this call could silently fail and refuse to configure any
# certificates. In this instance, the user should provide a
# certificate manually.
context.set_default_verify_paths()

# Load any specific certificate paths that have been specified during
# adapter initialization.
for cafile in self.cafiles:
context.load_verify_locations(cafile=cafile)
for capath in self.capaths:
context.load_verify_locations(capath=capath)

if self.verify:
# Enable certificate verification
context.verify_mode = ssl.VerifyMode.CERT_REQUIRED # pylint: disable=no-member

pool_kwargs['ssl_context'] = context
return super().init_poolmanager(
connections, maxsize, block, **pool_kwargs
)


class PKIConnection:
"""
Class to encapsulate the connection between the client and a Dogtag
Expand All @@ -59,7 +133,7 @@ class PKIConnection:

def __init__(self, protocol='http', hostname='localhost', port='8080',
subsystem=None, accept='application/json',
trust_env=None, verify=False):
trust_env=None, verify=True, cert_paths=None):
"""
Set the parameters for a python-requests based connection to a
Dogtag subsystem.
Expand All @@ -81,6 +155,9 @@ def __init__(self, protocol='http', hostname='localhost', port='8080',
:param verify: verify TLS/SSL connections and configure CA certs
(default: no)
:type verify: None, bool, str
:param cert_paths: paths to CA certificates / directories in OpenSSL
format. (default: None)
:type cert_paths: None, str, list
:return: PKIConnection object.
"""

Expand All @@ -101,6 +178,7 @@ def __init__(self, protocol='http', hostname='localhost', port='8080',
self.serverURI = self.rootURI

self.session = requests.Session()
self.session.mount("https://", SSLContextAdapter(verify=verify, cert_paths=cert_paths))
self.session.trust_env = trust_env
self.session.verify = verify

Expand Down
49 changes: 40 additions & 9 deletions base/common/python/pki/nssdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ def add_cert(self, nickname, cert_file, token=None, trust_attributes=None):
finally:
shutil.rmtree(tmpdir)

def add_ca_cert(self, cert_file, trust_attributes=None):
def add_ca_cert(self, cert_file, trust_attributes='CT,C,C'):

# Import CA certificate into internal token with automatically
# assigned nickname.
Expand All @@ -534,8 +534,8 @@ def add_ca_cert(self, cert_file, trust_attributes=None):
cmd.extend(['-C', self.internal_password_file])

cmd.extend([
'client-cert-import',
'--ca-cert', cert_file
'nss-cert-import',
'--cert', cert_file
])

if trust_attributes:
Expand Down Expand Up @@ -1553,10 +1553,10 @@ def import_pkcs7(
cmd.extend(['pkcs7-import'])

if pkcs7_file:
cmd.extend(['--input-file', pkcs7_file])
cmd.extend(['--pkcs7', pkcs7_file])

if trust_attributes:
cmd.extend(['--trust-flags', trust_attributes])
cmd.extend(['--trust', trust_attributes])

logger.debug('Command: %s', ' '.join(map(str, cmd)))

Expand Down Expand Up @@ -1652,8 +1652,8 @@ def import_pkcs12(self, pkcs12_file,

cmd.extend([
'pkcs12-import',
'--pkcs12-file', pkcs12_file,
'--pkcs12-password-file', password_file
'--pkcs12', pkcs12_file,
'--password-file', password_file
])

if no_user_certs:
Expand Down Expand Up @@ -1717,8 +1717,8 @@ def export_pkcs12(self, pkcs12_file,
cmd.extend(['pkcs12-export'])

cmd.extend([
'--pkcs12-file', pkcs12_file,
'--pkcs12-password-file', password_file
'--pkcs12', pkcs12_file,
'--password-file', password_file
])

if cert_encryption:
Expand Down Expand Up @@ -1751,6 +1751,37 @@ def export_pkcs12(self, pkcs12_file,
finally:
shutil.rmtree(tmpdir)

def extract_ca_cert(self, ca_path, nickname):
tmpdir = tempfile.mkdtemp()

try:
p12_file = os.path.join(tmpdir, "sslserver.p12")
password = pki.generate_password()

# Build a chain containing the certificate we're trying to
# export. OpenSSL gets confused if we don't have a key for
# the end certificate: rh-bz#1246371
self.export_pkcs12(p12_file, pkcs12_password=password,
nicknames=[nickname], include_key=False,
include_chain=True)

# This command is similar to the one from server/__init__.py.
# However, to work during the initial startup, we do not
# specify the cacerts option! This ensures we always get
cmd_export_ca = [
'openssl', 'pkcs12',
'-in', p12_file,
'-out', ca_path,
'-nodes', '-nokeys',
'-passin', 'pass:' + password
]

res_ca = subprocess.check_output(cmd_export_ca,
stderr=subprocess.STDOUT).decode('utf-8')
logger.debug('Result of CA cert export: %s', res_ca)
finally:
shutil.rmtree(tmpdir)

@staticmethod
def __generate_key_args(key_type=None, key_size=None, curve=None):
"""
Expand Down
4 changes: 2 additions & 2 deletions base/common/python/pki/pkcs12.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ def show_certs(self):

cmd.extend([
'pkcs12-cert-find',
'--pkcs12-file', self.path,
'--pkcs12-password-file', self.password_file
'--pkcs12', self.path,
'--password-file', self.password_file
])

subprocess.check_call(cmd)
43 changes: 0 additions & 43 deletions base/common/src/com/netscape/certsrv/client/ClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -94,31 +93,6 @@ public ClientConfig(ClientConfig config) {
messageFormat = config.messageFormat;
}

/**
* @deprecated Use getServerURL() instead.
*/
@XmlElement(name="ServerURI")
@Deprecated
public URI getServerURI() {
try {
return serverURL.toURI();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}

/**
* @deprecated Use setServerURL() instead.
*/
@Deprecated
public void setServerURI(String serverUri) throws URISyntaxException {
try {
this.serverURL = new URI(serverUri).toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}

public void setServerURI(URI serverUri) {
try {
this.serverURL = serverUri.toURL();
Expand Down Expand Up @@ -230,23 +204,6 @@ public static class NSSPassword {
public String value;
}

/**
* @deprecated Use getNSSDatabase() instead.
*/
@XmlElement(name="CertDatabase")
@Deprecated
public String getCertDatabase() {
return nssDatabase;
}

/**
* @deprecated Use setNSSDatabase() instead.
*/
@Deprecated
public void setCertDatabase(String certDatabase) {
this.nssDatabase = certDatabase;
}

@XmlElement(name="Token")
public String getTokenName() {
return tokenName;
Expand Down
857 changes: 855 additions & 2 deletions base/common/src/org/dogtagpki/nss/NSSDatabase.java

Large diffs are not rendered by default.

452 changes: 452 additions & 0 deletions base/common/src/org/dogtagpki/nss/NSSExtensionGenerator.java

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions base/java-tools/src/com/netscape/cmstools/PKCS10Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;

import org.dogtag.util.cert.CertUtil;
import org.mozilla.jss.CryptoManager;
import org.mozilla.jss.InitializationValues;
import org.mozilla.jss.asn1.BMPString;
Expand Down Expand Up @@ -341,11 +342,12 @@ public static void main(String args[]) throws Exception {
System.out.println("");
}

certReq.print(System.out);
String pem = CertUtil.toPEM(certReq);
System.out.print(pem);

try (FileOutputStream fos = new FileOutputStream(ofilename);
PrintStream ps = new PrintStream(fos)) {
certReq.print(ps);
ps.print(pem);
}

System.out.println("PKCS10Client: Certificate request written into " + ofilename);
Expand Down
44 changes: 34 additions & 10 deletions base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
import com.netscape.cmstools.tps.TPSCLI;
import com.netscape.cmstools.user.ProxyUserCLI;
import com.netscape.cmsutil.crypto.CryptoUtil;
import com.netscape.cmsutil.password.PlainPasswordFile;

/**
* @author Endi S. Dewata
Expand All @@ -87,15 +88,17 @@ public class MainCLI extends CLI {

public ClientConfig config = new ClientConfig();

NSSDatabase nssdb;

public Collection<Integer> rejectedCertStatuses = new HashSet<Integer>();
public Collection<Integer> ignoredCertStatuses = new HashSet<Integer>();

public boolean ignoreBanner;
public File certDatabase;

String output;

boolean initialized;
boolean optionsParsed;

public MainCLI() throws Exception {
super("pki", "PKI command-line interface");
Expand Down Expand Up @@ -130,8 +133,8 @@ public ClientConfig getConfig() {
return config;
}

public File getNSSDatabase() {
return certDatabase;
public NSSDatabase getNSSDatabase() {
return nssdb;
}

public String getFullModuleName(String moduleName) {
Expand Down Expand Up @@ -422,6 +425,21 @@ public void parseOptions(CommandLine cmd) throws Exception {
config.setNSSPasswords(nssPasswords);
}

PlainPasswordFile passwordStore = new PlainPasswordFile();

if (nssPassword != null) {
String token = tokenName;
if (token == null) token = "internal";
passwordStore.putPassword(token, nssPassword);

} else if (nssPasswordConfig != null) {
passwordStore.init(nssPasswordConfig);
}

logger.info("NSS database: " + config.getNSSDatabase());
nssdb = new NSSDatabase(config.getNSSDatabase());
nssdb.setPasswordStore(passwordStore);

// store user name
config.setUsername(username);

Expand All @@ -445,12 +463,11 @@ public void parseOptions(CommandLine cmd) throws Exception {

ignoreBanner = cmd.hasOption("ignore-banner");

this.certDatabase = new File(config.getNSSDatabase());
logger.info("NSS database: " + this.certDatabase.getAbsolutePath());

String messageFormat = cmd.getOptionValue("message-format");
config.setMessageFormat(messageFormat);
logger.info("Message format: " + messageFormat);

optionsParsed = true;
}

public void convertCertStatusList(String list, Collection<Integer> statuses) throws Exception {
Expand All @@ -476,15 +493,22 @@ public void init() throws Exception {
return;
}

NSSDatabase nssdb = new NSSDatabase(certDatabase);
if (!optionsParsed) {
throw new Exception("Unable to call MainCLI.init() without first calling MainCLI.parseOptions()");
}

if (!nssdb.exists()) {
// Create a default NSS database without password
nssdb.create();
// Create the NSS DB with the specified password, if one has been
// specified.
if (config.getNSSPassword() != null) {
nssdb.create(config.getNSSPassword());
} else {
nssdb.create();
}
}

logger.info("Initializing NSS");
CryptoManager.initialize(certDatabase.getAbsolutePath());
CryptoManager.initialize(nssdb.getPath().toString());

CryptoManager manager;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,8 @@
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.dogtagpki.cli.CommandCLI;
import org.mozilla.jss.CryptoManager;
import org.mozilla.jss.crypto.X509Certificate;
import org.dogtagpki.nss.NSSDatabase;
import org.mozilla.jss.netscape.security.pkcs.PKCS7;
import org.mozilla.jss.netscape.security.util.Cert;

import com.netscape.certsrv.ca.CACertClient;
import com.netscape.certsrv.ca.CAClient;
Expand Down Expand Up @@ -109,6 +107,7 @@ public void execute(CommandLine cmd) throws Exception {
MainCLI mainCLI = (MainCLI) getRoot();
mainCLI.init();

ClientConfig clientConfig = mainCLI.getConfig();
String nickname = null;

// Get nickname from command argument if specified.
Expand All @@ -120,7 +119,7 @@ public void execute(CommandLine cmd) throws Exception {
// This code is used to provide backward compatibility.
// TODO: deprecate/remove this code in 10.3.
if (nickname == null) {
nickname = mainCLI.config.getCertNickname();
nickname = clientConfig.getCertNickname();
}

// nickname is not required to import PKCS #12 file
Expand All @@ -135,17 +134,19 @@ public void execute(CommandLine cmd) throws Exception {
String serialNumber = cmd.getOptionValue("serial");
String trustAttributes = cmd.getOptionValue("trust");

NSSDatabase nssdb = mainCLI.getNSSDatabase();
File nssdbPasswordFile = null;

if (mainCLI.config.getNSSPassword() != null) {
String password = clientConfig.getNSSPassword();
if (password != null) {

// store NSS database password in a temporary file

nssdbPasswordFile = File.createTempFile("pki-client-cert-import-", ".nssdb-pwd");
nssdbPasswordFile.deleteOnExit();

try (PrintWriter out = new PrintWriter(new FileWriter(nssdbPasswordFile))) {
out.print(mainCLI.config.getNSSPassword());
out.print(password);
}
}

Expand All @@ -154,15 +155,15 @@ public void execute(CommandLine cmd) throws Exception {

logger.info("Importing certificate from " + certPath);

if (nickname == null) {
throw new Exception("Missing certificate nickname");
}

if (trustAttributes == null)
trustAttributes = "u,u,u";

importCert(
mainCLI.certDatabase,
nssdbPasswordFile,
certPath,
nickname,
trustAttributes);
nssdb.addPEMCertificate(nickname, certPath, trustAttributes);
System.out.println("Imported certificate \"" + nickname + "\"");

} else if (caCertPath != null) {

Expand All @@ -171,17 +172,20 @@ public void execute(CommandLine cmd) throws Exception {
if (trustAttributes == null)
trustAttributes = "CT,C,C";

importCACert(
mainCLI.certDatabase,
nssdbPasswordFile,
caCertPath,
nickname,
trustAttributes);
if (nickname != null) {
// import a single CA certificate with the provided nickname
nssdb.addPEMCertificate(nickname, caCertPath, trustAttributes);
System.out.println("Imported certificate \"" + nickname + "\"");
return;
}

org.mozilla.jss.crypto.X509Certificate cert = nssdb.addPEMCertificate(caCertPath, trustAttributes);
System.out.println("Imported certificate \"" + cert.getNickname() + "\"");

} else if (pkcs7Path != null) {

logger.warn("The --pkcs7 option has been deprecated. Use the following command instead:");
logger.warn(" $ pki pkcs7-import --input-file <filename>");
logger.warn(" $ pki pkcs7-import --pkcs7 <filename>");

logger.info("Importing certificates from " + pkcs7Path);

Expand Down Expand Up @@ -214,14 +218,14 @@ public void execute(CommandLine cmd) throws Exception {

// import certificates and private key into PKCS #12 file
importPKCS12(
mainCLI.certDatabase,
nssdb.getDirectory(),
nssdbPasswordFile,
pkcs12Path,
pkcs12PasswordPath);

} else if (importFromCAServer) {

logger.info("Importing CA certificate from " + mainCLI.getConfig().getServerURL());
logger.info("Importing CA certificate from " + clientConfig.getServerURL());

PKIClient client = getClient();
CAClient caClient = new CAClient(client);
Expand All @@ -235,7 +239,7 @@ public void execute(CommandLine cmd) throws Exception {
} else if (serialNumber != null) {

// connect to CA anonymously
ClientConfig config = new ClientConfig(mainCLI.config);
ClientConfig config = new ClientConfig(clientConfig);
config.setNSSDatabase(null);
config.setNSSPassword(null);
config.setCertNickname(null);
Expand All @@ -257,86 +261,21 @@ public void execute(CommandLine cmd) throws Exception {
out.write(encoded);
}

if (nickname == null) {
throw new Exception("Missing certificate nickname");
}

if (trustAttributes == null)
trustAttributes = "u,u,u";

importCert(
mainCLI.certDatabase,
nssdbPasswordFile,
certFile.getAbsolutePath(),
nickname,
trustAttributes);
nssdb.addPEMCertificate(nickname, certFile.getAbsolutePath(), trustAttributes);
System.out.println("Imported certificate \"" + nickname + "\"");

} else {
throw new Exception("Missing certificate to import");
}
}

public void importCert(
File dbPath,
File dbPasswordFile,
String certFile,
String nickname,
String trustAttributes) throws Exception {

if (nickname == null) {
throw new Exception("Missing certificate nickname");
}

List<String> command = new ArrayList<>();
command.add("/usr/bin/certutil");
command.add("-A");
command.add("-d");
command.add(dbPath.getAbsolutePath());

if (dbPasswordFile != null) {
command.add("-f");
command.add(dbPasswordFile.getAbsolutePath());
}

// accept PEM or PKCS #7 certificate
command.add("-a");

command.add("-i");
command.add(certFile);
command.add("-n");
command.add(nickname);
command.add("-t");
command.add(trustAttributes);

try {
runExternal(command);
} catch (Exception e) {
throw new Exception("Unable to import certificate file", e);
}

System.out.println("Imported certificate \"" + nickname + "\"");
}

public void importCACert(
File dbPath,
File dbPasswordFile,
String certFile,
String nickname,
String trustAttributes) throws Exception {

if (nickname != null) {
// import a single CA certificate with the provided nickname
importCert(dbPath, dbPasswordFile, certFile, nickname, trustAttributes);
return;
}

// import CA certificate chain with auto-generated nicknames
String pemCert = new String(Files.readAllBytes(Paths.get(certFile))).trim();
byte[] binCert = Cert.parseCertificate(pemCert);

CryptoManager manager = CryptoManager.getInstance();
X509Certificate cert = manager.importCACertPackage(binCert);
CryptoUtil.setTrustFlags(cert, trustAttributes);

System.out.println("Imported certificate \"" + cert.getNickname() + "\"");
}

public void importPKCS7(
String pkcs7Path,
String nickname,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.dogtagpki.cli.CommandCLI;
import org.dogtagpki.nss.NSSDatabase;

import com.netscape.cmstools.cli.MainCLI;

Expand Down Expand Up @@ -64,11 +65,12 @@ public void execute(CommandLine cmd) throws Exception {

String nickname = cmdArgs[0];

NSSDatabase nssdb = mainCLI.getNSSDatabase();
String trustAttributes = cmd.getOptionValue("trust", "u,u,u");

String[] command = {
"/usr/bin/certutil", "-M",
"-d", mainCLI.certDatabase.getAbsolutePath(),
"-d", nssdb.getPath().toString(),
"-n", nickname,
"-t", trustAttributes
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.apache.commons.cli.Option;
import org.dogtagpki.ca.CASystemCertClient;
import org.dogtagpki.cli.CommandCLI;
import org.dogtagpki.nss.NSSDatabase;
import org.mozilla.jss.CryptoManager;
import org.mozilla.jss.crypto.CryptoToken;
import org.mozilla.jss.crypto.KeyWrapAlgorithm;
Expand Down Expand Up @@ -238,20 +239,29 @@ public void execute(CommandLine cmd) throws Exception {
}

MainCLI mainCLI = (MainCLI) getRoot();
File certDatabase = mainCLI.certDatabase;
NSSDatabase nssdb = mainCLI.getNSSDatabase();

String password = mainCLI.config.getNSSPassword();

String csr;
PKIClient client;
if ("pkcs10".equals(requestType)) {
if ("rsa".equals(algorithm)) {
csr = generatePkcs10Request(certDatabase, password, algorithm,
Integer.toString(length), subjectDN);
csr = generatePkcs10Request(
nssdb.getDirectory(),
password,
algorithm,
Integer.toString(length),
subjectDN);
}

else if ("ec".equals(algorithm)) {
csr = generatePkcs10Request(certDatabase, password, algorithm, curve, subjectDN);
csr = generatePkcs10Request(
nssdb.getDirectory(),
password,
algorithm,
curve,
subjectDN);
} else {
throw new Exception("Error: Unknown algorithm: " + algorithm);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.apache.commons.cli.Option;
import org.apache.commons.lang.RandomStringUtils;
import org.dogtagpki.cli.CommandCLI;
import org.dogtagpki.nss.NSSDatabase;
import org.mozilla.jss.CryptoManager;
import org.mozilla.jss.crypto.X509Certificate;
import org.mozilla.jss.netscape.security.util.Cert;
Expand Down Expand Up @@ -145,9 +146,11 @@ public void execute(CommandLine cmd) throws Exception {
out.print(pkcs12Password);
}

NSSDatabase nssdb = mainCLI.getNSSDatabase();

logger.info("Exporting certificate chain and private key to " + pkcs12File);
exportPKCS12(
mainCLI.certDatabase.getAbsolutePath(),
nssdb.getPath().toString(),
mainCLI.config.getNSSPassword(),
pkcs12File.getAbsolutePath(),
pkcs12PasswordFile.getAbsolutePath(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@
package com.netscape.cmstools.client;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;

import org.apache.commons.cli.CommandLine;
import org.dogtagpki.cli.CommandCLI;
import org.dogtagpki.nss.NSSDatabase;

import com.netscape.certsrv.client.ClientConfig;
import com.netscape.cmstools.cli.MainCLI;

/**
Expand Down Expand Up @@ -60,16 +58,15 @@ public void execute(CommandLine cmd) throws Exception {
}

MainCLI mainCLI = clientCLI.mainCLI;
File certDatabase = mainCLI.getNSSDatabase();
NSSDatabase nssdb = new NSSDatabase(certDatabase);
NSSDatabase nssdb = mainCLI.getNSSDatabase();

// Make sure existing NSS database is deleted
if (nssdb.exists()) {

boolean force = cmd.hasOption("force");

if (!force) {
System.out.println("NSS database already exists in " + certDatabase.getAbsolutePath() + ".");
System.out.println("NSS database already exists in " + nssdb.getPath() + ".");
System.out.print("Overwrite (y/N)? ");
System.out.flush();

Expand All @@ -84,8 +81,6 @@ public void execute(CommandLine cmd) throws Exception {
nssdb.delete();
}

// Create NSS database with the provided password
ClientConfig config = mainCLI.getConfig();
nssdb.create(config.getNSSPassword());
nssdb.create();
}
}
1 change: 1 addition & 0 deletions base/java-tools/src/com/netscape/cmstools/nss/NSSCLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public NSSCLI(MainCLI mainCLI) {
addModule(new NSSCreateCLI(this));
addModule(new NSSRemoveCLI(this));

addModule(new NSSCertCLI(this));
addModule(new NSSKeyCLI(this));
}

Expand Down
19 changes: 19 additions & 0 deletions base/java-tools/src/com/netscape/cmstools/nss/NSSCertCLI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Copyright Red Hat, Inc.
//
// SPDX-License-Identifier: GPL-2.0-or-later
//
package com.netscape.cmstools.nss;

import org.dogtagpki.cli.CLI;

public class NSSCertCLI extends CLI {

public NSSCertCLI(NSSCLI nssCLI) {
super("cert", "NSS certificate management commands", nssCLI);

addModule(new NSSCertImportCLI(this));
addModule(new NSSCertRequestCLI(this));
addModule(new NSSCertIssueCLI(this));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// Copyright Red Hat, Inc.
//
// SPDX-License-Identifier: GPL-2.0-or-later
//
package com.netscape.cmstools.nss;

import java.nio.file.Files;
import java.nio.file.Paths;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.io.IOUtils;
import org.dogtagpki.cli.CommandCLI;
import org.dogtagpki.nss.NSSDatabase;
import org.mozilla.jss.netscape.security.util.Cert;
import org.mozilla.jss.netscape.security.x509.X509CertImpl;

import com.netscape.certsrv.client.ClientConfig;
import com.netscape.cmstools.cli.MainCLI;

public class NSSCertImportCLI extends CommandCLI {

public static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(NSSCertImportCLI.class);

public NSSCertImportCLI(NSSCertCLI nssCertCLI) {
super("import", "Import certificate", nssCertCLI);
}

public void printHelp() {
formatter.printHelp(getFullName() + " [OPTIONS...] [nickname]", options);
}

public void createOptions() {
Option option = new Option(null, "cert", true, "Certificate to import");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "format", true, "Certificate format: PEM (default), DER");
option.setArgName("format");
options.addOption(option);

option = new Option(null, "trust", true, "Trust attributes");
option.setArgName("attributes");
options.addOption(option);
}

public void execute(CommandLine cmd) throws Exception {

String[] cmdArgs = cmd.getArgs();
String nickname = null;

if (cmdArgs.length >= 1) {
nickname = cmdArgs[0];
}

String filename = cmd.getOptionValue("cert");
String format = cmd.getOptionValue("format");
String trustAttributes = cmd.getOptionValue("trust");

if (trustAttributes == null)
trustAttributes = ",,";

byte[] bytes;
if (filename == null) {
// read from standard input
bytes = IOUtils.toByteArray(System.in);

} else {
// read from file
bytes = Files.readAllBytes(Paths.get(filename));
}

if (format == null || "PEM".equalsIgnoreCase(format)) {
bytes = Cert.parseCertificate(new String(bytes));

} else if ("DER".equalsIgnoreCase(format)) {
// nothing to do

} else {
throw new Exception("Unsupported format: " + format);
}

X509CertImpl cert = new X509CertImpl(bytes);

MainCLI mainCLI = (MainCLI) getRoot();
mainCLI.init();

ClientConfig clientConfig = mainCLI.getConfig();
NSSDatabase nssdb = mainCLI.getNSSDatabase();

if (nickname == null) {
nssdb.addCertificate(cert, trustAttributes);

} else {
nssdb.addCertificate(nickname, cert, trustAttributes);
}
}
}
128 changes: 128 additions & 0 deletions base/java-tools/src/com/netscape/cmstools/nss/NSSCertIssueCLI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//
// Copyright Red Hat, Inc.
//
// SPDX-License-Identifier: GPL-2.0-or-later
//
package com.netscape.cmstools.nss;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.cert.X509Certificate;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.dogtag.util.cert.CertUtil;
import org.dogtagpki.cli.CommandCLI;
import org.dogtagpki.nss.NSSDatabase;
import org.dogtagpki.nss.NSSExtensionGenerator;
import org.mozilla.jss.CryptoManager;
import org.mozilla.jss.netscape.security.pkcs.PKCS10;
import org.mozilla.jss.netscape.security.x509.CertificateExtensions;

import com.netscape.certsrv.client.ClientConfig;
import com.netscape.cmstools.cli.MainCLI;

public class NSSCertIssueCLI extends CommandCLI {

public static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(NSSCertIssueCLI.class);

public NSSCertIssueCLI(NSSCertCLI nssCertCLI) {
super("issue", "Issue certificate", nssCertCLI);
}

public void printHelp() {
formatter.printHelp(getFullName() + " [OPTIONS...]", options);
}

public void createOptions() {
Option option = new Option(null, "issuer", true, "Issuer nickname (default is self-signed)");
option.setArgName("nickname");
options.addOption(option);

option = new Option(null, "csr", true, "Certificate signing request");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "ext", true, "Certificate extensions configuration");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "months-valid", true, "Months valid (default is 3)");
option.setArgName("months");
options.addOption(option);

option = new Option(null, "cert", true, "Certificate");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "format", true, "Certificate format: PEM (default), DER");
option.setArgName("format");
options.addOption(option);
}

public void execute(CommandLine cmd) throws Exception {

String issuerNickname = cmd.getOptionValue("issuer");
String csrFile = cmd.getOptionValue("csr");
String extConf = cmd.getOptionValue("ext");
String monthsValid = cmd.getOptionValue("months-valid");

if (csrFile == null) {
throw new Exception("Missing certificate signing request");
}

MainCLI mainCLI = (MainCLI) getRoot();
mainCLI.init();

ClientConfig clientConfig = mainCLI.getConfig();
NSSDatabase nssdb = mainCLI.getNSSDatabase();

org.mozilla.jss.crypto.X509Certificate issuer;
if (issuerNickname == null) {
issuer = null;

} else {
CryptoManager cm = CryptoManager.getInstance();
issuer = cm.findCertByNickname(issuerNickname);
}

String csrPEM = new String(Files.readAllBytes(Paths.get(csrFile)));
byte[] csrBytes = CertUtil.parseCSR(csrPEM);
PKCS10 pkcs10 = new PKCS10(csrBytes);

CertificateExtensions extensions = null;
if (extConf != null) {
NSSExtensionGenerator generator = new NSSExtensionGenerator();
generator.init(extConf);
extensions = generator.createExtensions(issuer, pkcs10);
}

X509Certificate cert = nssdb.createCertificate(
issuer,
pkcs10,
monthsValid == null ? null : new Integer(monthsValid),
extensions);

String format = cmd.getOptionValue("format");
byte[] bytes;

if (format == null || "PEM".equalsIgnoreCase(format)) {
bytes = CertUtil.toPEM(cert).getBytes();

} else if ("DER".equalsIgnoreCase(format)) {
bytes = cert.getEncoded();

} else {
throw new Exception("Unsupported format: " + format);
}

String filename = cmd.getOptionValue("cert");

if (filename != null) {
Files.write(Paths.get(filename) , bytes);

} else {
System.out.write(bytes);
}
}
}
131 changes: 131 additions & 0 deletions base/java-tools/src/com/netscape/cmstools/nss/NSSCertRequestCLI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//
// Copyright Red Hat, Inc.
//
// SPDX-License-Identifier: GPL-2.0-or-later
//
package com.netscape.cmstools.nss;

import java.nio.file.Files;
import java.nio.file.Paths;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.dogtag.util.cert.CertUtil;
import org.dogtagpki.cli.CommandCLI;
import org.dogtagpki.nss.NSSDatabase;
import org.dogtagpki.nss.NSSExtensionGenerator;
import org.mozilla.jss.netscape.security.pkcs.PKCS10;
import org.mozilla.jss.netscape.security.x509.CertificateExtensions;

import com.netscape.certsrv.client.ClientConfig;
import com.netscape.cmstools.cli.MainCLI;

public class NSSCertRequestCLI extends CommandCLI {

public static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(NSSCertRequestCLI.class);

public NSSCertRequestCLI(NSSCertCLI nssCertCLI) {
super("request", "Generate certificate signing request", nssCertCLI);
}

public void printHelp() {
formatter.printHelp(getFullName() + " [OPTIONS...]", options);
}

public void createOptions() {
Option option = new Option(null, "subject", true, "Subject name");
option.setArgName("name");
options.addOption(option);

option = new Option(null, "key-id", true, "Key ID");
option.setArgName("ID");
options.addOption(option);

option = new Option(null, "key-type", true, "Key type: RSA (default), EC, DSA");
option.setArgName("type");
options.addOption(option);

option = new Option(null, "key-size", true, "RSA key size (default is 2048)");
option.setArgName("size");
options.addOption(option);

option = new Option(null, "curve", true, "Elliptic curve name");
option.setArgName("name");
options.addOption(option);

option = new Option(null, "hash", true, "Hash algorithm");
option.setArgName("name");
options.addOption(option);

option = new Option(null, "ext", true, "Certificate extensions configuration");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "csr", true, "Certificate signing request");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "format", true, "Certificate signing request format: PEM (default), DER");
option.setArgName("format");
options.addOption(option);
}

public void execute(CommandLine cmd) throws Exception {

String subject = cmd.getOptionValue("subject");
String keyID = cmd.getOptionValue("key-id");
String keyType = cmd.getOptionValue("key-type");
String keySize = cmd.getOptionValue("key-size");
String curve = cmd.getOptionValue("curve");
String hash = cmd.getOptionValue("hash");
String extConf = cmd.getOptionValue("ext");

if (subject == null) {
throw new Exception("Missing subject name");
}

MainCLI mainCLI = (MainCLI) getRoot();
mainCLI.init();

ClientConfig clientConfig = mainCLI.getConfig();
NSSDatabase nssdb = mainCLI.getNSSDatabase();

CertificateExtensions extensions = null;
if (extConf != null) {
NSSExtensionGenerator generator = new NSSExtensionGenerator();
generator.init(extConf);
extensions = generator.createExtensions();
}

PKCS10 pkcs10 = nssdb.createRequest(
subject,
keyID,
keyType,
keySize,
curve,
hash,
extensions);

String format = cmd.getOptionValue("format");
byte[] bytes;

if (format == null || "PEM".equalsIgnoreCase(format)) {
bytes = CertUtil.toPEM(pkcs10).getBytes();

} else if ("DER".equalsIgnoreCase(format)) {
bytes = pkcs10.toByteArray();

} else {
throw new Exception("Unsupported format: " + format);
}

String filename = cmd.getOptionValue("csr");

if (filename != null) {
Files.write(Paths.get(filename) , bytes);

} else {
System.out.write(bytes);
}
}
}
11 changes: 3 additions & 8 deletions base/java-tools/src/com/netscape/cmstools/nss/NSSCreateCLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@
package com.netscape.cmstools.nss;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;

import org.apache.commons.cli.CommandLine;
import org.dogtagpki.cli.CommandCLI;
import org.dogtagpki.nss.NSSDatabase;

import com.netscape.certsrv.client.ClientConfig;
import com.netscape.cmstools.cli.MainCLI;

/**
Expand Down Expand Up @@ -47,16 +45,15 @@ public void execute(CommandLine cmd) throws Exception {
}

MainCLI mainCLI = nssCLI.mainCLI;
File certDatabase = mainCLI.getNSSDatabase();
NSSDatabase nssdb = new NSSDatabase(certDatabase);
NSSDatabase nssdb = mainCLI.getNSSDatabase();

// Make sure existing NSS database is deleted
if (nssdb.exists()) {

boolean force = cmd.hasOption("force");

if (!force) {
System.out.println("NSS database already exists in " + certDatabase.getAbsolutePath() + ".");
System.out.println("NSS database already exists in " + nssdb.getPath() + ".");
System.out.print("Overwrite (y/N)? ");
System.out.flush();

Expand All @@ -71,8 +68,6 @@ public void execute(CommandLine cmd) throws Exception {
nssdb.delete();
}

// Create NSS database with the provided password
ClientConfig config = mainCLI.getConfig();
nssdb.create(config.getNSSPassword());
nssdb.create();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package com.netscape.cmstools.nss;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;

import org.apache.commons.cli.CommandLine;
Expand Down Expand Up @@ -46,17 +45,16 @@ public void execute(CommandLine cmd) throws Exception {
}

MainCLI mainCLI = nssCLI.mainCLI;
File certDatabase = mainCLI.getNSSDatabase();
NSSDatabase nssdb = new NSSDatabase(certDatabase);
NSSDatabase nssdb = mainCLI.getNSSDatabase();

if (!nssdb.exists()) {
throw new Exception("There is no NSS database in " + certDatabase.getAbsolutePath());
throw new Exception("There is no NSS database in " + nssdb.getPath());
}

boolean force = cmd.hasOption("force");

if (!force) {
System.out.println("Removing NSS database in " + certDatabase.getAbsolutePath() + ".");
System.out.println("Removing NSS database in " + nssdb.getPath() + ".");
System.out.print("Are you sure (y/N)? ");
System.out.flush();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,27 @@ public void printHelp() {
}

public void createOptions() {
Option option = new Option(null, "pkcs12-file", true, "PKCS #12 file");
Option option = new Option(null, "pkcs12", true, "PKCS #12 file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "pkcs12-password", true, "PKCS #12 password");
option = new Option(null, "pkcs12-file", true, "DEPRECATED: PKCS #12 file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "password", true, "PKCS #12 password");
option.setArgName("password");
options.addOption(option);

option = new Option(null, "pkcs12-password", true, "DEPRECATED: PKCS #12 password");
option.setArgName("password");
options.addOption(option);

option = new Option(null, "pkcs12-password-file", true, "PKCS #12 password file");
option = new Option(null, "password-file", true, "PKCS #12 password file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "pkcs12-password-file", true, "DEPRECATED: PKCS #12 password file");
option.setArgName("path");
options.addOption(option);
}
Expand All @@ -70,17 +82,27 @@ public void execute(CommandLine cmd) throws Exception {
throw new Exception("Too many arguments specified.");
}

String filename = cmd.getOptionValue("pkcs12-file");
String filename = cmd.getOptionValue("pkcs12");
if (filename == null) {
filename = cmd.getOptionValue("pkcs12-file");
}

if (filename == null) {
throw new Exception("Missing PKCS #12 file.");
}

String passwordString = cmd.getOptionValue("pkcs12-password");
String passwordString = cmd.getOptionValue("password");
if (passwordString == null) {
passwordString = cmd.getOptionValue("pkcs12-password");
}

if (passwordString == null) {

String passwordFile = cmd.getOptionValue("pkcs12-password-file");
String passwordFile = cmd.getOptionValue("password-file");
if (passwordFile == null) {
passwordFile = cmd.getOptionValue("pkcs12-password-file");
}

if (passwordFile != null) {
try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) {
passwordString = in.readLine();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,27 @@ public void printHelp() {
}

public void createOptions() {
Option option = new Option(null, "pkcs12-file", true, "PKCS #12 file");
Option option = new Option(null, "pkcs12", true, "PKCS #12 file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "pkcs12-password", true, "PKCS #12 password");
option = new Option(null, "pkcs12-file", true, "DEPRECATED: PKCS #12 file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "password", true, "PKCS #12 password");
option.setArgName("password");
options.addOption(option);

option = new Option(null, "pkcs12-password", true, "DEPRECATED: PKCS #12 password");
option.setArgName("password");
options.addOption(option);

option = new Option(null, "pkcs12-password-file", true, "PKCS #12 password file");
option = new Option(null, "password-file", true, "PKCS #12 password file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "pkcs12-password-file", true, "DEPRECATED: PKCS #12 password file");
option.setArgName("path");
options.addOption(option);

Expand All @@ -76,17 +88,27 @@ public void execute(CommandLine cmd) throws Exception {

String nickname = cmdArgs[0];

String filename = cmd.getOptionValue("pkcs12-file");
String filename = cmd.getOptionValue("pkcs12");
if (filename == null) {
filename = cmd.getOptionValue("pkcs12-file");
}

if (filename == null) {
throw new Exception("Missing PKCS #12 file.");
}

String passwordString = cmd.getOptionValue("pkcs12-password");
String passwordString = cmd.getOptionValue("password");
if (passwordString == null) {
passwordString = cmd.getOptionValue("pkcs12-password");
}

if (passwordString == null) {

String passwordFile = cmd.getOptionValue("pkcs12-password-file");
String passwordFile = cmd.getOptionValue("password-file");
if (passwordFile == null) {
passwordFile = cmd.getOptionValue("pkcs12-password-file");
}

if (passwordFile != null) {
try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) {
passwordString = in.readLine();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,27 @@ public void printHelp() {
}

public void createOptions() {
Option option = new Option(null, "pkcs12-file", true, "PKCS #12 file");
Option option = new Option(null, "pkcs12", true, "PKCS #12 file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "pkcs12-password", true, "PKCS #12 password");
option = new Option(null, "pkcs12-file", true, "DEPRECATED: PKCS #12 file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "password", true, "PKCS #12 password");
option.setArgName("password");
options.addOption(option);

option = new Option(null, "pkcs12-password", true, "DEPRECATED: PKCS #12 password");
option.setArgName("password");
options.addOption(option);

option = new Option(null, "pkcs12-password-file", true, "PKCS #12 password file");
option = new Option(null, "password-file", true, "PKCS #12 password file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "pkcs12-password-file", true, "DEPRECATED: PKCS #12 password file");
option.setArgName("path");
options.addOption(option);

Expand All @@ -105,17 +117,28 @@ public void createOptions() {
public void execute(CommandLine cmd) throws Exception {

String[] nicknames = cmd.getArgs();
String filename = cmd.getOptionValue("pkcs12-file");

String filename = cmd.getOptionValue("pkcs12");
if (filename == null) {
filename = cmd.getOptionValue("pkcs12-file");
}

if (filename == null) {
throw new Exception("Missing PKCS #12 file.");
}

String passwordString = cmd.getOptionValue("pkcs12-password");
String passwordString = cmd.getOptionValue("password");
if (passwordString == null) {
passwordString = cmd.getOptionValue("pkcs12-password");
}

if (passwordString == null) {

String passwordFile = cmd.getOptionValue("pkcs12-password-file");
String passwordFile = cmd.getOptionValue("password-file");
if (passwordFile == null) {
passwordFile = cmd.getOptionValue("pkcs12-password-file");
}

if (passwordFile != null) {
try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) {
passwordString = in.readLine();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,27 @@ public void printHelp() {
}

public void createOptions() {
Option option = new Option(null, "pkcs12-file", true, "PKCS #12 file");
Option option = new Option(null, "pkcs12", true, "PKCS #12 file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "pkcs12-password", true, "PKCS #12 password");
option = new Option(null, "pkcs12-file", true, "DEPRECATED: PKCS #12 file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "password", true, "PKCS #12 password");
option.setArgName("password");
options.addOption(option);

option = new Option(null, "pkcs12-password", true, "DEPRECATED: PKCS #12 password");
option.setArgName("password");
options.addOption(option);

option = new Option(null, "pkcs12-password-file", true, "PKCS #12 password file");
option = new Option(null, "password-file", true, "PKCS #12 password file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "pkcs12-password-file", true, "DEPRECATED: PKCS #12 password file");
option.setArgName("path");
options.addOption(option);

Expand All @@ -67,17 +79,28 @@ public void createOptions() {
public void execute(CommandLine cmd) throws Exception {

String[] nicknames = cmd.getArgs();
String filename = cmd.getOptionValue("pkcs12-file");

String filename = cmd.getOptionValue("pkcs12");
if (filename == null) {
filename = cmd.getOptionValue("pkcs12-file");
}

if (filename == null) {
throw new Exception("Missing PKCS #12 file");
}

String passwordString = cmd.getOptionValue("pkcs12-password");
String passwordString = cmd.getOptionValue("password");
if (passwordString == null) {
passwordString = cmd.getOptionValue("pkcs12-password");
}

if (passwordString == null) {

String passwordFile = cmd.getOptionValue("pkcs12-password-file");
String passwordFile = cmd.getOptionValue("password-file");
if (passwordFile == null) {
passwordFile = cmd.getOptionValue("pkcs12-password-file");
}

if (passwordFile != null) {
try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) {
passwordString = in.readLine();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,20 @@ public void printHelp() {
}

public void createOptions() {
Option option = new Option(null, "input-file", true, "Input file");
Option option = new Option(null, "pkcs7", true, "PKCS #7 file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "trust-flags", true, "Trust flags");
option.setArgName("flags");
option = new Option(null, "input-file", true, "DEPRECATED: PKCS #7 file");
option.setArgName("path");
options.addOption(option);

option = new Option(null, "trust", true, "Trust attributes");
option.setArgName("attributes");
options.addOption(option);

option = new Option(null, "trust-flags", true, "DEPRECATED: Trust attributes");
option.setArgName("attributes");
options.addOption(option);
}

Expand All @@ -53,8 +61,15 @@ public void execute(CommandLine cmd) throws Exception {
nickname = null;
}

String filename = cmd.getOptionValue("input-file");
String trustFlags = cmd.getOptionValue("trust-flags");
String filename = cmd.getOptionValue("pkcs7");
if (filename == null) {
filename = cmd.getOptionValue("input-file");
}

String trustAttributes = cmd.getOptionValue("trust");
if (trustAttributes == null) {
trustAttributes = cmd.getOptionValue("trust-flags");
}

String input;
if (filename == null) {
Expand All @@ -70,6 +85,6 @@ public void execute(CommandLine cmd) throws Exception {
mainCLI.init();

PKCS7 pkcs7 = new PKCS7(input);
CryptoUtil.importPKCS7(pkcs7, nickname, trustFlags);
CryptoUtil.importPKCS7(pkcs7, nickname, trustAttributes);
}
}
2 changes: 1 addition & 1 deletion base/javadoc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ endif()

javadoc(pki-javadoc
SOURCEPATH
${CMAKE_SOURCE_DIR}/base/util/src
${CMAKE_SOURCE_DIR}/base/util/src/main/java
${CMAKE_SOURCE_DIR}/base/common/src
${CMAKE_SOURCE_DIR}/base/java-tools/src
${PKI_JAVADOC_SOURCEPATH}
Expand Down
2 changes: 1 addition & 1 deletion base/kra/shared/webapps/kra/agent/kra/GrantRecovery.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<td valign="top" align="right">
<font size="-1" face="PrimaSans BT, Verdana, sans-serif">Recovery authorization reference number:<br></font>
</td>
<td><INPUT TYPE="TEXT" NAME="recoveryID" SIZE=10 MAXLENGTH=99"></td>
<td><INPUT TYPE="NUMBER" NAME="recoveryID" MIN=0"></td>
</tr>
</table>

Expand Down
11 changes: 11 additions & 0 deletions base/native-tools/src/bulkissuance/bulkissuance.c
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,17 @@ client_main(
}
errExit("SSL_OptionSet SSL_SECURITY");
}
#ifdef SSL_ENABLE_POST_HANDSHAKE_AUTH
rv = SSL_OptionSet(model_sock,
SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE);
if (rv < 0) {
if( model_sock != NULL ) {
PR_Close( model_sock );
model_sock = NULL;
}
errExit("SSL_OptionSet SSL_ENABLE_POST_HANDSHAKE_AUTH");
}
#endif

SSL_SetURL(model_sock, hostName);

Expand Down
11 changes: 11 additions & 0 deletions base/native-tools/src/revoker/revoker.c
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,17 @@ client_main(
}
errExit("SSL_OptionSet SSL_SECURITY");
}
#ifdef SSL_ENABLE_POST_HANDSHAKE_AUTH
rv = SSL_OptionSet(model_sock,
SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE);
if (rv < 0) {
if( model_sock != NULL ) {
PR_Close( model_sock );
model_sock = NULL;
}
errExit("SSL_OptionSet SSL_ENABLE_POST_HANDSHAKE_AUTH");
}
#endif

SSL_SetURL(model_sock, hostName);

Expand Down
4 changes: 4 additions & 0 deletions base/native-tools/src/sslget/sslget.c
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,10 @@ client_main(
/* do SSL configuration. */

rv = SSL_OptionSet(model_sock, SSL_SECURITY, 1);
#ifdef SSL_ENABLE_POST_HANDSHAKE_AUTH
SSL_OptionSet(model_sock,
SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE);
#endif

if (rv < 0) {
if( model_sock != NULL ) {
Expand Down
6 changes: 6 additions & 0 deletions base/server/etc/default.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ pki_existing=False

# DEPRECATED: Use 'pki_cert_chain_path' instead.
pki_external_ca_cert_chain_path=%(pki_instance_configuration_path)s/external_ca_chain.cert

# In addition to specifying an external CA certificate, this parameter
# can be used with a one-shot installation process is used for installing
# non-CA subsystems on a new host, lacking any existing subsystems. This
# cert is used to establish trust to an existing CA installation on another
# system.
pki_cert_chain_path=%(pki_external_ca_cert_chain_path)s

# DEPRECATED: Use 'pki_cert_chain_nickname' instead.
Expand Down
228 changes: 228 additions & 0 deletions base/server/healthcheck/pki/server/healthcheck/certs/expiration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
# Authors:
# Dinesh Prasanth M K <dmoluguw@redhat.com>
#
# Copyright Red Hat, Inc.
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
import logging
import time

from datetime import datetime

from pki.server.healthcheck.certs.plugin import CertsPlugin, registry
from ipahealthcheck.core.plugin import Result, duration
from ipahealthcheck.core import constants

logger = logging.getLogger(__name__)


def check_cert_expiry_date(class_instance, cert):
"""
Calculate the expiry status of the given cert
:param class_instance: Reporting Class Instance
:type class_instance: object
:param cert: Certificate
:type cert: dict
:return: Result object with prefilled args
:rtype: Result
"""

# Get the current time in seconds
current_time = int(round(time.time()))

# Get the cert's expiry date in Milli seconds
cert_expiry_time = cert.get('not_after')

if cert_expiry_time is None:
logger.critical("Unable to retrieve cert: %s", cert['nickname'])
return Result(class_instance, constants.ERROR,
cert_id=cert['id'],
msg='Unable to get cert\'s expiry date')

# Convert to seconds
cert_expiry_time = cert_expiry_time / 1000

# Calculate the difference in seconds
delta_sec = cert_expiry_time - current_time

# Calculate the number of days left/passed
current_date = datetime.fromtimestamp(current_time)
cert_expiry_date = datetime.fromtimestamp(cert_expiry_time)
delta_days = (cert_expiry_date - current_date).days

expiry_date_human = cert_expiry_date.strftime("%b %d %Y")

if delta_sec <= 0:
logger.error("Expired Cert: %s", cert['id'])
return Result(class_instance, constants.ERROR,
cert_id=cert['id'],
expiry_date=expiry_date_human,
msg='Certificate has ALREADY EXPIRED')

elif delta_days == 0 and delta_sec <= 86400:
# Expiring in less than a day
logger.warning("Expiring in a day: %s", cert['id'])
return Result(class_instance, constants.WARNING,
cert_id=cert['id'],
msg='Expiring within next 24 hours')

elif delta_days < 30:
# Expiring in a month
logger.warning("Expiring in less than 30 days: %s", cert['id'])
return Result(class_instance, constants.WARNING,
cert_id=cert['id'],
expiry_date=expiry_date_human,
msg='Your certificate expires within 30 days.')
else:
# Valid certificate
logger.info("VALID certificate: %s", cert['id'])
return Result(class_instance, constants.SUCCESS,
cert_id=cert['id'],
expiry_date=expiry_date_human)


@registry
class CASystemCertExpiryCheck(CertsPlugin):
"""
Check the expiry of CA's system certs
"""

@duration
def check(self):

if not self.instance.exists():
logger.debug('Invalid instance: %s', self.instance.name)
yield Result(self, constants.CRITICAL,
msg='Invalid PKI instance: %s' % self.instance.name)
return

self.instance.load()

ca = self.instance.get_subsystem('ca')

if not ca:
logger.info("No CA configured, skipping CA System Cert Expiry check")
return

certs = ca.find_system_certs()

for cert in certs:
yield check_cert_expiry_date(class_instance=self, cert=cert)


@registry
class KRASystemCertExpiryCheck(CertsPlugin):
"""
Check the expiry of KRA's system certs
"""

@duration
def check(self):

if not self.instance.exists():
logger.debug('Invalid instance: %s', self.instance.name)
yield Result(self, constants.CRITICAL,
msg='Invalid PKI instance: %s' % self.instance.name)
return

self.instance.load()

kra = self.instance.get_subsystem('kra')

if not kra:
logger.info("No KRA configured, skipping KRA System Cert Expiry check")
return

certs = kra.find_system_certs()

for cert in certs:
yield check_cert_expiry_date(class_instance=self, cert=cert)


@registry
class OCSPSystemCertExpiryCheck(CertsPlugin):
"""
Check the expiry of OCSP's system certs
"""

@duration
def check(self):

if not self.instance.exists():
logger.debug('Invalid instance: %s', self.instance.name)
yield Result(self, constants.CRITICAL,
msg='Invalid PKI instance: %s' % self.instance.name)
return

self.instance.load()

ocsp = self.instance.get_subsystem('ocsp')

if not ocsp:
logger.info("No OCSP configured, skipping OCSP System Cert Expiry check")
return

certs = ocsp.find_system_certs()

for cert in certs:
yield check_cert_expiry_date(class_instance=self, cert=cert)


@registry
class TKSSystemCertExpiryCheck(CertsPlugin):
"""
Check the expiry of TKS's system certs
"""

@duration
def check(self):

if not self.instance.exists():
logger.debug('Invalid instance: %s', self.instance.name)
yield Result(self, constants.CRITICAL,
msg='Invalid PKI instance: %s' % self.instance.name)
return

self.instance.load()

tks = self.instance.get_subsystem('tks')

if not tks:
logger.info("No TKS configured, skipping TKS System Cert Expiry check")
return

certs = tks.find_system_certs()

for cert in certs:
yield check_cert_expiry_date(class_instance=self, cert=cert)


@registry
class TPSSystemCertExpiryCheck(CertsPlugin):
"""
Check the expiry of TPS's system certs
"""

@duration
def check(self):

if not self.instance.exists():
logger.debug('Invalid instance: %s', self.instance.name)
yield Result(self, constants.CRITICAL,
msg='Invalid PKI instance: %s' % self.instance.name)
return

self.instance.load()

tps = self.instance.get_subsystem('tps')

if not tps:
logger.info("No TPS configured, skipping TPS System Cert Expiry check")
return

certs = tps.find_system_certs()

for cert in certs:
yield check_cert_expiry_date(class_instance=self, cert=cert)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

import logging

# Temporary workaround to skip VERBOSE data. Fix already pushed to upstream
# freeipa-healthcheck: https://github.com/freeipa/freeipa-healthcheck/pull/126
logging.getLogger().setLevel(logging.WARNING)


Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import os

from pki.server.healthcheck.meta.plugin import MetaPlugin, registry
from ipahealthcheck.core.plugin import Result, duration
Expand Down Expand Up @@ -27,6 +28,7 @@ def check(self):
self.instance.load()

ca = self.instance.get_subsystem('ca')
ca_cert = os.path.join(self.instance.nssdb_dir, "ca.crt")

if not ca:
logger.info("No CA configured, skipping dogtag CA connectivity check")
Expand All @@ -38,11 +40,12 @@ def check(self):
if ca.is_ready():
logger.debug("CA instance is running")

# Make a plain HTTP GET to "find" one certificate, to test that
# Make a plain HTTPS GET to "find" one certificate, to test that
# the server is up AND is able to respond back
connection = PKIConnection(protocol='https',
hostname='localhost',
port='8443')
port='8443',
cert_paths=ca_cert)

cert_client = CertClient(connection)
cert = cert_client.list_certs(size=1)
Expand Down Expand Up @@ -98,6 +101,7 @@ def check(self):
self.instance.load()

kra = self.instance.get_subsystem('kra')
ca_cert = os.path.join(self.instance.nssdb_dir, "ca.crt")

if not kra:
logger.info("No KRA configured, skipping dogtag KRA connectivity check")
Expand All @@ -109,11 +113,12 @@ def check(self):
if kra.is_ready():
logger.info("KRA instance is running.")

# Make a plain HTTP GET to retrieve KRA transport cert, to test that
# Make a plain HTTPS GET to retrieve KRA transport cert, to test that
# the server is up AND is able to respond back
connection = PKIConnection(protocol='https',
hostname='localhost',
port='8443')
port='8443',
cert_paths=ca_cert)

system_cert_client = SystemCertClient(connection)

Expand Down
1 change: 1 addition & 0 deletions base/server/healthcheck/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
# plugin modules for pkihealthcheck.certs registry
'pkihealthcheck.certs': [
'trust_flags = pki.server.healthcheck.certs.trustflags',
'expiration = pki.server.healthcheck.certs.expiration',
],
},
classifiers=[
Expand Down
Loading