From 66e0b84fabe4175be8077a5f587f84ba999dd074 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Fri, 8 Apr 2016 22:23:42 +1000 Subject: [PATCH] Lightweight CAs: add IPACustodiaKeyRetriever Add 'IPACustodiaKeyRetriever', a 'KeyRetriever' implementation for use when Dogtag is deployed as a FreeIPA CA. The Java class invokes 'pki-ipa-retrieve-key', a Python script that retrieves lightweight CA keys from the Custodia server on a replica that possesses the keys. 'pki-ipa-retrieve-key' depends on FreeIPA libraries, FreeIPA server configuration, and Kerberos and Custodia keys owned by 'pkiuser'. Part of: https://fedorahosted.org/pki/ticket/1625 --- base/ca/src/CMakeLists.txt | 9 ++- .../com/netscape/ca/IPACustodiaKeyRetriever.java | 75 ++++++++++++++++++++++ base/server/CMakeLists.txt | 11 ++++ base/server/libexec/pki-ipa-retrieve-key | 45 +++++++++++++ specs/pki-core.spec | 1 + 5 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java create mode 100755 base/server/libexec/pki-ipa-retrieve-key diff --git a/base/ca/src/CMakeLists.txt b/base/ca/src/CMakeLists.txt index 5b805e1b3a5eddb46d17178ca2ac204c36ae5680..1817dacfbacaeb2635db2550e32ff62c26d628ef 100644 --- a/base/ca/src/CMakeLists.txt +++ b/base/ca/src/CMakeLists.txt @@ -24,6 +24,13 @@ find_file(COMMONS_CODEC_JAR /usr/share/java ) +find_file(COMMONS_IO_JAR + NAMES + commons-io.jar + PATHS + /usr/share/java +) + find_file(COMMONS_LANG_JAR NAMES commons-lang.jar @@ -73,7 +80,7 @@ javac(pki-ca-classes com/netscape/ca/*.java org/dogtagpki/server/ca/*.java CLASSPATH - ${COMMONS_CODEC_JAR} ${COMMONS_LANG_JAR} + ${COMMONS_CODEC_JAR} ${COMMONS_IO_JAR} ${COMMONS_LANG_JAR} ${JSS_JAR} ${SYMKEY_JAR} ${LDAPJDK_JAR} ${SERVLET_JAR} ${TOMCAT_CATALINA_JAR} diff --git a/base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java b/base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java new file mode 100644 index 0000000000000000000000000000000000000000..4a162d3702fccc19dfef792a5213c653286930f3 --- /dev/null +++ b/base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java @@ -0,0 +1,75 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program 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; version 2 of the License. +// +// 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, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2016 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- + +package com.netscape.ca; + +import java.lang.Process; +import java.lang.ProcessBuilder; +import java.util.Collection; +import java.util.Stack; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.ArrayUtils; + +import com.netscape.certsrv.apps.CMS; + +public class IPACustodiaKeyRetriever implements KeyRetriever { + public Result retrieveKey(String nickname, Collection hostPorts) { + CMS.debug("Running IPACustodiaKeyRetriever"); + + Stack command = new Stack<>(); + command.push("/usr/libexec/pki-ipa-retrieve-key"); + command.push(nickname); + + for (String hostPort : hostPorts) { + String host = hostPort.split(":")[0]; + command.push(host); + CMS.debug("About to execute command: " + command); + ProcessBuilder pb = new ProcessBuilder(command); + try { + Process p = pb.start(); + int exitValue = p.waitFor(); + if (exitValue != 0) + continue; + + /* Custodia returns a PEM-encoded certificate and a + * base64-encoded PKIArchiveOptions containing the + * wrapped private key. These values are output by + * the Python 'pki-ipa-retrieve-key' program, + * separated by a null byte (password first) + */ + byte[] output = IOUtils.toByteArray(p.getInputStream()); + int splitIndex = ArrayUtils.indexOf(output, (byte) 0); + if (splitIndex == ArrayUtils.INDEX_NOT_FOUND) { + CMS.debug("Invalid output: null byte not found"); + continue; + } + return new Result( + ArrayUtils.subarray(output, 0, splitIndex), + ArrayUtils.subarray(output, splitIndex + 1, output.length) + ); + } catch (Throwable e) { + CMS.debug("Caught exception while executing command: " + e); + } finally { + command.pop(); + } + } + CMS.debug("Failed to retrieve key from any host."); + return null; + } +} diff --git a/base/server/CMakeLists.txt b/base/server/CMakeLists.txt index 5a6aea96a2317655fb454967f9f218020443bcb8..9e5b27833c8d023e63320c43d64ad64b0055c254 100644 --- a/base/server/CMakeLists.txt +++ b/base/server/CMakeLists.txt @@ -81,6 +81,17 @@ install( install( DIRECTORY + libexec/ + DESTINATION + ${LIBEXEC_INSTALL_DIR} + FILE_PERMISSIONS + OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_READ + WORLD_EXECUTE WORLD_READ +) + +install( + DIRECTORY upgrade DESTINATION ${DATA_INSTALL_DIR}/server/ diff --git a/base/server/libexec/pki-ipa-retrieve-key b/base/server/libexec/pki-ipa-retrieve-key new file mode 100755 index 0000000000000000000000000000000000000000..a7c1396a2fce6b3572a897cdf760d5264eefaab4 --- /dev/null +++ b/base/server/libexec/pki-ipa-retrieve-key @@ -0,0 +1,45 @@ +#!/usr/bin/python + +from __future__ import print_function + +import ConfigParser +import base64 +import os +import sys + +from jwcrypto.common import json_decode + +from ipaplatform.constants import constants +from ipaplatform.paths import paths +from ipapython.secrets.client import CustodiaClient + +conf = ConfigParser.ConfigParser() +conf.read(paths.IPA_DEFAULT_CONF) +hostname = conf.get('global', 'host') +realm = conf.get('global', 'realm') + +keyname = "ca_wrapped/" + sys.argv[1] +servername = sys.argv[2] + +service = constants.PKI_GSSAPI_SERVICE_NAME +client_keyfile = os.path.join(paths.PKI_TOMCAT, service + '.keys') +client_keytab = os.path.join(paths.PKI_TOMCAT, service + '.keytab') + +client = CustodiaClient( + client=hostname, server=servername, realm=realm, + ldap_uri="ldaps://" + hostname, + client_servicename=service, + keyfile=client_keyfile, keytab=client_keytab, + ) + +result_json = client.fetch_key(keyname, store=False) +result = json_decode(result_json) +certificate = result["certificate"] +wrapped_key = base64.b64decode(result["wrapped_key"]) + +# Custodia returns a PEM-encoded certificate and a base64-encoded +# DER PKIArchiveOptions object. Output these values, separated by a +# null byte (certificate first), to be read by the Java +# IPACustodiaKeyRetriever that invoked this program. + +print(certificate, wrapped_key, sep='\0', end='') diff --git a/specs/pki-core.spec b/specs/pki-core.spec index bce6bd2d298bc8e1ad5cb40618f982b5ba23b27d..509ecdafa4e5d0e651ce22497db06420abcdf259 100644 --- a/specs/pki-core.spec +++ b/specs/pki-core.spec @@ -1007,6 +1007,7 @@ systemctl daemon-reload %{_sbindir}/pki-server %{_sbindir}/pki-server-nuxwdog %{_sbindir}/pki-server-upgrade +%{_libexecdir}/pki-ipa-retrieve-key %{python2_sitelib}/pki/server/ %dir %{_datadir}/pki/deployment %{_datadir}/pki/deployment/config/ -- 2.5.5