Skip to content

Commit

Permalink
Add support for TLS certificate and key management
Browse files Browse the repository at this point in the history
- Implement new environment variables for TLS keychain and password files.
- Extend the command-line options to manage certificate usage, names, and organization details.
- Update configurations to manage server and client certificates separately.
- Improve help messages and ensure mandatory environment variables are set.
- Revise documentation to reflect these changes.
  • Loading branch information
george-mcintyre committed Dec 6, 2024
1 parent dc9865b commit 192dc67
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 28 deletions.
114 changes: 105 additions & 9 deletions certs/authn/std/authnstd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,83 @@ void usage(const char *argv0) {
std::cerr << "Usage: " << argv0
<< " <opts> \n"
"\n"
" -v Make more noise.\n";
" -v Make more noise.\n"
" -h Show this help message and exit\n"
" -d Shorthand for $PVXS_LOG=\"pvxs.*=DEBUG\". Make a lot of noise.\n"
" -D Run in Daemon mode. Monitors and updates certs as needed\n"
" -V Show version and exit\n"
" -u <use> Usage. client, server, or gateway\n"
" -N <name> Name override the CN subject field\n"
" -O <name> Org override the O subject field\n"
" -o <name> Override the OU subject field\n"
" \n"
"ENVIRONMENT VARIABLES: at least one mandatory variable must be set\n"
"\tEPICS_PVA_TLS_KEYCHAIN\t\t\tSet name and location of client certificate file (mandatory for clients)\n"
"\tEPICS_PVAS_TLS_KEYCHAIN\t\t\tSet name and location of server certificate file (mandatory for server)\n"
"\tEPICS_PVA_TLS_KEYCHAIN_PWD_FILE\t\tSet name and location of client certificate password file (optional)\n"
"\tEPICS_PVAS_TLS_KEYCHAIN_PWD_FILE\tSet name and location of server certificate password file (optional)\n"
"\tEPICS_PVA_TLS_PKEY\t\t\tSet name and location of client private key file (optional)\n"
"\tEPICS_PVAS_TLS_PKEY\t\t\tSet name and location of server private key file (optional)\n"
"\tEPICS_PVA_TLS_PKEY_PWD_FILE\t\tSet name and location of client private key password file (optional)\n"
"\tEPICS_PVAS_TLS_PKEY_PWD_FILE\t\tSet name and location of server private key password file (optional)\n"
;
}

int readOptions(ConfigStd &config, int argc, char *argv[], bool &verbose) {
int readOptions(ConfigStd &config, int argc, char *argv[], bool &verbose, uint16_t &cert_usage, std::string &name, std::string &org, std::string &ou) {
int opt;
while ((opt = getopt(argc, argv, "v")) != -1) {
while ((opt = getopt(argc, argv, "vhVdu:N:O:o:D")) != -1) {
switch (opt) {
case 'v':
verbose = true;
break;
case 'h':
usage(argv[0]);
return 1;
case 'D':
usage(argv[0]);
std::cerr << "\nNot yet supported: -" << char(optopt) << std::endl;
return 4;
case 'd':
logger_level_set("pvxs.*", Level::Debug);
break;
case 'V':
std::cout << pvxs::version_information;
return 1;
case 'u': {
std::string usage_str = optarg;
if ( usage_str == "gateway" || usage_str == "server") {
// Use the Server versions of environment variables
config.tls_cert_filename = config.tls_srv_cert_filename;
config.tls_private_key_filename = config.tls_srv_private_key_filename ;
config.tls_cert_password = config.tls_srv_cert_password;
config.tls_private_key_password = config.tls_srv_private_key_password ;
if (usage_str == "gateway") {
cert_usage = pvxs::ssl::kForClientAndServer;
} else if (usage_str == "server") {
cert_usage = pvxs::ssl::kForServer;
}
} else if (usage_str == "client") {
cert_usage = pvxs::ssl::kForClient;
} else {
usage(argv[0]);
std::cerr << "\nUnknown argument: -" << char(optopt) << " " << usage_str << std::endl;
return 2;
}
}
break;
case 'N':
name = optarg;
break;
case 'O':
org = optarg;
break;
case 'o':
ou = optarg;
break;
default:
usage(argv[0]);
std::cerr << "\nUnknown argument: -" << char(optopt) << std::endl;
return 2;
return 3;
}
}

Expand Down Expand Up @@ -126,7 +189,7 @@ std::shared_ptr<Credentials> AuthStd::getCredentials(const ConfigStd &config) co
// Set the expiration time of the certificate
time_t now = time(nullptr);
x509_credentials->not_before = now;
x509_credentials->not_after = now + PVXS_X509_AUTH_DEFAULT_VALIDITY_S;
x509_credentials->not_after = now + (config.cert_validity_mins*60);

if (!config.device_name.empty()) {
// Get Device Name (Organization)
Expand Down Expand Up @@ -189,22 +252,49 @@ int main(int argc, char *argv[]) {
pvxs::logger_config_env();

bool verbose{false}, retrieved_credentials{false};
uint16_t cert_usage{pvxs::ssl::kForClient};
std::string name, org, ou;

try {
auto config = ConfigStd::fromEnv();
std::shared_ptr<KeyPair> key_pair;

// Read commandline options
int exit_status;
if ((exit_status = readOptions(config, argc, argv, verbose))) {

if ((exit_status = readOptions(config, argc, argv, verbose, cert_usage, name, org, ou))) {
return exit_status - 1;
}

if ( config.tls_cert_filename.empty() ) {
std::cerr << "You must set at least one mandatory environment variables to create certificates: " << std::endl;
std::cerr << "\tEPICS_PVA_TLS_KEYCHAIN\t\t\tSet name and location of client certificate file (mandatory for clients)" << std::endl;
std::cerr << "\tEPICS_PVAS_TLS_KEYCHAIN\t\t\tSet name and location of server certificate file (mandatory for server)" << std::endl;
std::cerr << "\tEPICS_PVA_TLS_KEYCHAIN_PWD_FILE\t\tSet name and location of client certificate password file (optional)" << std::endl;
std::cerr << "\tEPICS_PVAS_TLS_KEYCHAIN_PWD_FILE\tSet name and location of server certificate password file (optional)" << std::endl;
std::cerr << "\tEPICS_PVA_TLS_PKEY\t\t\tSet name and location of client private key file (optional)" << std::endl;
std::cerr << "\tEPICS_PVAS_TLS_PKEY\t\t\tSet name and location of server private key file (optional)" << std::endl;
std::cerr << "\tEPICS_PVA_TLS_PKEY_PWD_FILE\t\tSet name and location of client private key password file (optional)" << std::endl;
std::cerr << "\tEPICS_PVAS_TLS_PKEY_PWD_FILE\t\tSet name and location of server private key password file (optional)" << std::endl;
return 10;
}
if (verbose) logger_level_set("pvxs.certs.auth.std*", pvxs::Level::Info);

// Standard authenticator
AuthStd authenticator;

// Try to retrieve credentials from the authenticator
if ( !name.empty() ) {
config.use_process_name = true;
config.process_name = name;
}
if ( !org.empty() ) {
config.device_name = org;
}
if (auto credentials = authenticator.getCredentials(config)) {
if ( !ou.empty() ) {
credentials->organization_unit = ou;
}
log_debug_printf(auths, "Credentials retrieved for: %s authenticator\n", authenticator.type_.c_str());
retrieved_credentials = true;

Expand All @@ -224,7 +314,7 @@ int main(int argc, char *argv[]) {

// Create a certificate creation request using the credentials and
// key pair
auto cert_creation_request = authenticator.createCertCreationRequest(credentials, key_pair, pvxs::ssl::kForClient);
auto cert_creation_request = authenticator.createCertCreationRequest(credentials, key_pair, cert_usage);

log_debug_printf(auths, "CCR created for: %s authentication type\n", authenticator.type_.c_str());

Expand All @@ -239,12 +329,18 @@ int main(int argc, char *argv[]) {
// Attempt to write the certificate and private key
// to a cert file protected by the configured password
auto file_factory =
CertFileFactory::create(config.tls_cert_filename, config.tls_cert_password, key_pair, nullptr, nullptr, "certificate", p12_pem_string);
CertFileFactory::create(
(cert_usage ? config.tls_cert_filename : config.tls_cert_filename), config.tls_cert_password, key_pair, nullptr, nullptr, "certificate", p12_pem_string);
file_factory->writeCertFile();

log_info_printf(auths, "New Cert File created using %s: %s\n", METHOD_STRING(authenticator.type_).c_str(), config.tls_cert_filename.c_str());
std::cout << "Certificate created with " << ((authenticator.type_ == PVXS_DEFAULT_AUTH_TYPE) ? "basic" : authenticator.type_)
<< " credentials and stored in:" << config.tls_cert_filename << "\n";
<< " credentials and stored in:" << config.tls_cert_filename
<< (config.tls_private_key_filename.empty() or config.tls_private_key_filename == config.tls_cert_filename ? "" : " and " + config.tls_private_key_filename)
<< "\n\tNAME:\t" << credentials->name
<< "\n\tORG:\t" << credentials->organization
<< "\n\tOU:\t" << credentials->organization_unit
<< "\n";

// Create the root certificate if it is not already there so
// that the user can trust it
Expand Down
20 changes: 20 additions & 0 deletions certs/authn/std/configstd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,26 @@ void ConfigStd::fromStdEnv(const std::map<std::string, std::string> &defs) {
if (pickone({"EPICS_AUTH_STD_USE_PROCESS_NAME"})) {
use_process_name = parseTo<bool>(pickone.val);
}

// EPICS_PVAS_TLS_KEYCHAIN
if (pickone({"EPICS_PVAS_TLS_KEYCHAIN"})) {
ensureDirectoryExists(tls_srv_cert_filename = tls_srv_private_key_filename = pickone.val);
}

// EPICS_PVAS_TLS_KEYCHAIN
if (pickone({"EPICS_PVAS_TLS_KEYCHAIN_PWD_FILE"})) {
tls_srv_cert_password = tls_srv_private_key_password = getFileContents(pickone.val);
}

// EPICS_PVAS_TLS_PKEY
if (pickone({"EPICS_PVAS_TLS_PKEY"})) {
ensureDirectoryExists(tls_srv_private_key_filename = pickone.val);
}

// EPICS_PVAS_TLS_PKEY_PWD_FILE
if (pickone({"EPICS_PVAS_TLS_PKEY_PWD_FILE"})) {
tls_srv_private_key_password = getFileContents(pickone.val);
}
}

} // certs
Expand Down
5 changes: 5 additions & 0 deletions certs/authn/std/configstd.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ class ConfigStd : public pvxs::client::Config {
*/
std::string device_name;

std::string tls_srv_cert_filename;
std::string tls_srv_private_key_filename ;
std::string tls_srv_cert_password;
std::string tls_srv_private_key_password ;

/**
* @brief Value will be used as the process name when an EPICS agent
* is determining basic credentials instead of the logged-on
Expand Down
47 changes: 28 additions & 19 deletions documentation/securepva.rst
Original file line number Diff line number Diff line change
Expand Up @@ -222,18 +222,17 @@ Quick Start Guide
#### Set key paths (keys will be created here if they don't already exist)
# An EPICS client agent key if required
export EPICS_PVA_TLS_KEY=~/.ssh/clientkey.p12
export EPICS_PVA_TLS_PKEY=~/.epics/client.p12
# An EPICS server agent key if required
export EPICS_PVAS_TLS_KEY=~/.ssh/serverkey.p12
export EPICS_PVAS_TLS_PKEY=~/.epics/server.p12
#### Set certificate paths (certificates will be created here if they don't already exist)
# An EPICS client agent certificate if required
export EPICS_PVA_TLS_KEYCHAIN=~/.epics/client.p12
export EPICS_PVA_TLS_KEYCHAIN=~/.epics/client.pem
# An EPICS server agent certificate if required
export EPICS_PVAS_TLS_KEYCHAIN=~/.epics/server.p12
export EPICS_PVAS_TLS_KEYCHAIN=~/.epics/server.pem
8. Create Certificates
^^^^^^^^^^^^^^^^^^^^
Expand All @@ -243,12 +242,12 @@ Quick Start Guide
#### 1. Create a new client private key at location specified by EPICS_PVA_TLS_KEY if it does not already exist
#### 2. Create client certificate at location specified by EPICS_PVA_TLS_KEYCHAIN
./pvxs/bin/*/authnstd -C client
./pvxs/bin/*/authnstd -u client
#### 1. Create a new server private key at location specified by EPICS_PVAS_TLS_KEY if it does not already exist
#### 2. Create server certificate at location specified by EPICS_PVAS_TLS_KEYCHAIN
./pvxs/bin/*/authnstd -C server
./pvxs/bin/*/authnstd -u server
.. _transport_layer_security:
Expand Down Expand Up @@ -1047,17 +1046,27 @@ private key, and password file locations.
.. code-block:: sh
authnstd <opts>
Options:
-h show help
-v verbose output
-t {client | server} Client or server certificate certificate type
-C Create a certificate and exit
-D Start authentication daemon to monitor certificate files and certificate status.
Will attempt to install a new certificate if the existing one expires,
or if the certificate file is deleted, or if the certificate is REVOKED.
Usage: authnstd <opts>
-v Make more noise.
-h Show this help message and exit
-d Shorthand for $PVXS_LOG="pvxs.*=DEBUG". Make a lot of noise.
-D Run in Daemon mode. Monitors and updates certs as needed
-V Show version and exit
-u <use> Usage. client, server, or gateway
-N <name> Name override the CN subject field
-O <name> Org override the O subject field
-o <name> Override the OU subject field
ENVIRONMENT VARIABLES: at least one mandatory variable must be set
EPICS_PVA_TLS_KEYCHAIN Set name and location of client certificate file (mandatory for clients)
EPICS_PVAS_TLS_KEYCHAIN Set name and location of server certificate file (mandatory for server)
EPICS_PVA_TLS_KEYCHAIN_PWD_FILE Set name and location of client certificate password file (optional)
EPICS_PVAS_TLS_KEYCHAIN_PWD_FILE Set name and location of server certificate password file (optional)
EPICS_PVA_TLS_PKEY Set name and location of client private key file (optional)
EPICS_PVAS_TLS_PKEY Set name and location of server private key file (optional)
EPICS_PVA_TLS_PKEY_PWD_FILE Set name and location of client private key password file (optional)
EPICS_PVAS_TLS_PKEY_PWD_FILE Set name and location of server private key password file (optional)
**Environment Variables for authnstd**
Expand Down

0 comments on commit 192dc67

Please sign in to comment.