diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml
index a4f09dbe3..4706b89f8 100644
--- a/.github/workflows/ci-scripts-build.yml
+++ b/.github/workflows/ci-scripts-build.yml
@@ -151,8 +151,10 @@ jobs:
- name: "apt-get install"
run: |
sudo apt-get update
- sudo apt-get -y install g++-mingw-w64-x86-64 cmake gdb qemu-system-x86
+ sudo apt-get -y install g++-mingw-w64-x86-64 cmake gdb qemu-system-x86 libssl-dev
if: runner.os == 'Linux'
+ - name: Host Info
+ run: openssl version -a
- name: Automatic core dumper analysis
uses: mdavidsaver/ci-core-dumper@master
- name: Prepare and compile dependencies
diff --git a/.gitignore b/.gitignore
index 3b2a40b95..a630a3a40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,49 @@ __pycache__/
*.orig
*.log
.*.swp
-.vscode
\ No newline at end of file
+.vscode
+.clang-format
+.iocsh_history
+EPICS_CA.pem
+login_certs.txt
+system_certs.txt
+.idea/editor.xml
+.idea/misc.xml
+.idea/modules.xml
+.idea/pvxs.iml
+.idea/vcs.xml
+.idea/workspace.xml
+.idea/codeStyles/codeStyleConfig.xml
+.idea/copyright/profiles_settings.xml
+.idea/inspectionProfiles/Project_Default.xml
+.idea/shelf/Add_detailed_documentation_for_certificate_testing_code__Enhanced_the__testtlswithcmsandst.xml
+.idea/shelf/authenticator_framework.xml
+.idea/shelf/big_refactor.xml
+.idea/shelf/Changes.xml
+.idea/shelf/Don_t_kn.xml
+.idea/shelf/framework_updates.xml
+.idea/shelf/Kerberos_spike.xml
+.idea/shelf/move_lambdas_into_subscribe_and_watch_methods.xml
+.idea/shelf/rename_CertificateStatus_and_change_OCSPStatus_creation_and_verification.xml
+.idea/shelf/statuslistener.xml
+.idea/shelf/subdirectories_for_each_auth_method1.xml
+.idea/shelf/Add_detailed_documentation_for_certificate_testing_code__Enhanced_the_`testtlswithcmsandst/shelved.patch
+.idea/shelf/authenticator_framework/shelved.patch
+.idea/shelf/big_refactor/shelved.patch
+.idea/shelf/Changes/shelved.patch
+.idea/shelf/Don't_kn/shelved.patch
+.idea/shelf/framework_updates/shelved.patch
+.idea/shelf/Kerberos_spike/shelved.patch
+.idea/shelf/move_lambdas_into_subscribe_and_watch_methods/shelved.patch
+.idea/shelf/rename_CertificateStatus_and_change_OCSPStatus_creation_and_verification/shelved.patch
+.idea/shelf/statuslistener/shelved.patch
+.idea/shelf/subdirectories_for_each_auth_method1/shelved.patch
+configure/TOOLCHAIN.tmp
+test/testioc.db
+test/testiocg.db
+test/slac-test/Sign In.htm
+test/slac-test/Sign In.mhtml
+test/slac-test/Sign In_files/doe-logo.png
+test/slac-test/Sign In_files/logo.png
+test/slac-test/Sign In_files/stanford-logo.png
+test/slac-test/Sign In_files/style.css
diff --git a/.gitmodules b/.gitmodules
index 6ce0698fa..a430f4631 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,9 @@
[submodule "bundle/libevent"]
path = bundle/libevent
url = https://github.com/libevent/libevent
+[submodule "bundle/jwt-cpp"]
+ path = bundle/jwt-cpp
+ url = https://github.com/Thalhammer/jwt-cpp.git
+[submodule "bundle/CLI11"]
+ path = bundle/CLI11
+ url = https://github.com/CLIUtils/CLI11.git
diff --git a/.idea/jsonSchemas.xml b/.idea/jsonSchemas.xml
new file mode 100644
index 000000000..e135c6f0e
--- /dev/null
+++ b/.idea/jsonSchemas.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
index a1074ed6b..db8a90a8e 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,6 +4,7 @@ include LICENSE
include README.md
include configure/CONFIG_PVXS_VERSION
+include configure/probe-openssl.c
include src/*.h
include src/*.h@
include src/*.cpp
diff --git a/Makefile b/Makefile
index 5def74da9..739cea7c2 100644
--- a/Makefile
+++ b/Makefile
@@ -5,8 +5,11 @@ include $(TOP)/configure/CONFIG
# Directories to build, any order
DIRS += configure
+DIRS += setup
+setup_DEPEND_DIRS = configure
+
DIRS += src
-src_DEPEND_DIRS = configure
+src_DEPEND_DIRS = setup
DIRS += tools
tools_DEPEND_DIRS = src
@@ -22,6 +25,9 @@ endif
DIRS += test
test_DEPEND_DIRS = src ioc
+DIRS += certs
+certs_DEPEND_DIRS = src
+
DIRS += example
example_DEPEND_DIRS = src
diff --git a/bundle/CLI11 b/bundle/CLI11
new file mode 160000
index 000000000..063b2c911
--- /dev/null
+++ b/bundle/CLI11
@@ -0,0 +1 @@
+Subproject commit 063b2c911cea8bd4b908d6187c6a007ad320cb1a
diff --git a/bundle/Makefile b/bundle/Makefile
index f59cd8e8c..f404c425c 100644
--- a/bundle/Makefile
+++ b/bundle/Makefile
@@ -1,5 +1,7 @@
TOP=..
+_PVXS_BOOTSTRAP = YES
+
include $(TOP)/configure/CONFIG
CMAKE ?= cmake
@@ -21,7 +23,6 @@ LIBEVENT_$(T_A) = $(INSTALL_LOCATION)/bundle/usr/$(T_A)
CMAKEFLAGS += -DCMAKE_INSTALL_PREFIX:PATH="$(abspath $(LIBEVENT_$(T_A)))"
# not needed, and may not be available on embedded targets, so never try
-CMAKEFLAGS += -DEVENT__DISABLE_OPENSSL=ON
CMAKEFLAGS += -DEVENT__DISABLE_MBEDTLS=ON
# not run, so why bother?
diff --git a/bundle/jwt-cpp b/bundle/jwt-cpp
new file mode 160000
index 000000000..a6927cb81
--- /dev/null
+++ b/bundle/jwt-cpp
@@ -0,0 +1 @@
+Subproject commit a6927cb8140858c34e05d1a954626b9849fbcdfc
diff --git a/bundle/libevent b/bundle/libevent
index 1fe626c4d..90b9520f3 160000
--- a/bundle/libevent
+++ b/bundle/libevent
@@ -1 +1 @@
-Subproject commit 1fe626c4da14fef6cf45b95e48a438e0f93a499e
+Subproject commit 90b9520f3ca04dd1278c831e61a82859e3be090e
diff --git a/certs/Makefile b/certs/Makefile
new file mode 100644
index 000000000..1dd879ac7
--- /dev/null
+++ b/certs/Makefile
@@ -0,0 +1,55 @@
+TOP=..
+
+include $(TOP)/configure/CONFIG
+# cfg/ sometimes isn't correctly included due to a Base bug
+# so we do here (maybe again) as workaround
+-include $(wildcard $(TOP)/cfg/CONFIG*)
+#----------------------------------------
+# ADD MACRO DEFINITIONS AFTER THIS LINE
+#=============================
+
+ifeq ($(EVENT2_HAS_OPENSSL),YES)
+USR_CPPFLAGS += -DPVXS_ENABLE_OPENSSL -I$(TOP)/bundle/CLI11/include
+AUTHN = $(TOP)/certs/authn
+SRC_DIRS += $(TOP)/src
+SRC_DIRS += $(TOP)/ioc
+SRCS += p12filefactory.cpp
+SRCS += pemfilefactory.cpp
+SRCS += certfilefactory.cpp
+SRCS += certfactory.cpp
+
+PROD_LIBS = pvxs Com
+
+# access to API and private headers
+USR_CPPFLAGS += -I$(TOP)/src/pvxs
+USR_CPPFLAGS += -I$(TOP)/src
+USR_CPPFLAGS += -I$(TOP)/ioc
+
+#INC += certstatus.h
+INC += security.h
+
+PROD += pvacms
+pvacms_SRCS += pvacms.cpp
+pvacms_SRCS += configcms.cpp
+pvacms_SRCS += certstatus.cpp
+pvacms_SRCS += certstatusfactory.cpp
+pvacms_SRCS += credentials.cpp
+pvacms_SRCS += securityclient.cpp
+
+pvacms_LIBS += $(EPICS_BASE_IOC_LIBS)
+pvacms_SYS_LIBS += sqlite3 ssl crypto
+
+#PROD += ocsppva
+#pvaocsp_SRCS += ocsppva.cpp
+#pvaocsp_SRCS += configocsp.cpp
+
+include $(AUTHN)/Makefile
+
+endif # EVENT2_HAS_OPENSSL
+
+#===========================
+
+include $(TOP)/configure/RULES
+-include $(wildcard $(TOP)/cfg/RULES*)
+#----------------------------------------
+# ADD RULES AFTER THIS LINE
diff --git a/certs/authn/Makefile b/certs/authn/Makefile
new file mode 100644
index 000000000..d766f3852
--- /dev/null
+++ b/certs/authn/Makefile
@@ -0,0 +1,23 @@
+# This is a Makefile fragment, see cert/Makefile.
+
+SRC_DIRS += $(AUTHN)
+
+#--------------------------------------------
+# ADD AUTHENTICATION PLUGINS AFTER THIS LINE
+
+SRCS += auth.cpp
+SRCS += ccrmanager.cpp
+
+include $(AUTHN)/std/Makefile
+
+ifeq ($(PVXS_ENABLE_JWT_AUTH),YES)
+include $(AUTHN)/jwt/Makefile
+endif
+
+ifeq ($(PVXS_ENABLE_KRB_AUTH),YES)
+include $(AUTHN)/krb/Makefile
+endif
+
+ifeq ($(PVXS_ENABLE_LDAP_AUTH),YES)
+include $(AUTHN)/ldap/Makefile
+endif
diff --git a/certs/authn/auth.cpp b/certs/authn/auth.cpp
new file mode 100644
index 000000000..d5a918eb6
--- /dev/null
+++ b/certs/authn/auth.cpp
@@ -0,0 +1,93 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#include "auth.h"
+
+#include
+#include
+#include
+
+#include
+
+#include "ccrmanager.h"
+#include "certfactory.h"
+#include "ownedptr.h"
+#include "p12filefactory.h"
+#include "security.h"
+
+namespace pvxs {
+namespace certs {
+
+/**
+ * @brief Creates a signed certificate.
+ *
+ * Create a PVStructure that corresponds to the ccr parameter of a certificate
+ * creation request. This request will be sent to the PVACMS through the default
+ * channel (PVAccess) and will be used to create the certificate.
+ *
+ * @param credentials the credentials that describe the subject of the
+ * certificate
+ * @param key_pair the public/private key to be used in the certificate, only
+ * public key is used
+ * @param usage the desired certificate usage
+ * @return A managed shared CertCreationRequest object.
+ */
+std::shared_ptr Auth::createCertCreationRequest(const std::shared_ptr &credentials, const std::shared_ptr &key_pair,
+ const uint16_t &usage) const {
+ // Create a new CertCreationRequest object.
+ auto cert_creation_request = std::make_shared(type_, verifier_fields_);
+ cert_creation_request->credentials = credentials;
+
+ // Fill in the ccr from the base data we've gathered so far.
+ cert_creation_request->ccr["name"] = credentials->name;
+ cert_creation_request->ccr["country"] = credentials->country;
+ cert_creation_request->ccr["organization"] = credentials->organization;
+ cert_creation_request->ccr["organization_unit"] = credentials->organization_unit;
+ cert_creation_request->ccr["type"] = type_;
+ cert_creation_request->ccr["usage"] = usage;
+ cert_creation_request->ccr["not_before"] = credentials->not_before;
+ cert_creation_request->ccr["not_after"] = credentials->not_after;
+ cert_creation_request->ccr["pub_key"] = key_pair->public_key;
+ return cert_creation_request;
+}
+
+/**
+ * @brief Signs a certificate.
+ *
+ * This function takes a certificate creation request and sends its ccr
+ * PVStructure to PVACMS to be signed. It will wait for the signed signature or
+ * any reported error.
+ *
+ * @param cert_creation_request A shared pointer to a CertCreationRequest object
+ * containing the ccr PVStructure which contains the certificate, and its
+ * validity as well as any verifier specific required fields.
+ * @return the signed certificate
+ * @throws std::runtime_error when exceptions arise
+ *
+ * @note It is the responsibility of the caller to ensure that the
+ * cert_creation_request object is valid and contains the required information
+ * before calling this function.
+ */
+std::string Auth::processCertificateCreationRequest(const std::shared_ptr &cert_creation_request) const {
+ // Forward the ccr to the certificate management service
+ std::string p12_pem_string(ccr_manager_.createCertificate(cert_creation_request));
+ return p12_pem_string;
+}
+
+std::shared_ptr Auth::createKeyPair(const ConfigCommon &config) {
+ // Create a key pair
+ const auto key_pair(IdFileFactory::createKeyPair());
+
+ // Create private key file containing private key
+ if ( config.tls_private_key_filename.empty()) {
+ IdFileFactory::create(config.tls_cert_filename, config.tls_cert_password, key_pair)->writeIdentityFile();
+ } else {
+ IdFileFactory::create(config.tls_private_key_filename, config.tls_private_key_password, key_pair)->writeIdentityFile();
+ }
+ return key_pair;
+}
+} // namespace certs
+} // namespace pvxs
diff --git a/certs/authn/auth.h b/certs/authn/auth.h
new file mode 100644
index 000000000..e73efcd75
--- /dev/null
+++ b/certs/authn/auth.h
@@ -0,0 +1,131 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#ifndef PVXS_AUTH_H
+#define PVXS_AUTH_H
+
+#include
+#include
+#include
+
+#include
+
+#include "ccrmanager.h"
+#include "certfactory.h"
+#include "configstd.h"
+#include "ownedptr.h"
+#include "security.h"
+
+namespace pvxs {
+namespace certs {
+
+#define MAX_AUTH_NAME_LEN 256
+
+/**
+ * @class Auth
+ * @brief Abstract class for authentication operations.
+ *
+ * The Auth class provides an interface for retrieving credentials and
+ * creating certificate creation request.
+ */
+using namespace certs;
+class Auth {
+ public:
+ std::string type_;
+ std::vector verifier_fields_;
+
+ // Constructor and Destructor
+ Auth(const std::string &type, const std::vector &verifier_fields) : type_(type), verifier_fields_(verifier_fields) {};
+ virtual ~Auth() = default;
+
+ virtual std::shared_ptr getCredentials(const ConfigStd &config) const = 0;
+ virtual std::shared_ptr createCertCreationRequest(const std::shared_ptr &credentials,
+ const std::shared_ptr &key_pair, const uint16_t &usage) const;
+ // Called inside PVACMS to verify request
+ // if an out-of-band authentication is used then it will check the signature
+ // established by the signCcrPayloadIfNeeded() method
+ virtual bool verify(const Value ccr, std::function signature_verifier) const = 0;
+ // @brief: If implemented, signCcrPayloadIfNeeded(), will make a call to
+ // PVACMS server through an authentication-method specific API to verify the
+ // contents of the CCR match the authentication-method's credentials On the
+ // server the CCR payroll is signed if the credentials match and left blank
+ // otherwise. On the server later in the process the verify function for
+ // this type of authentication-method will mandate a check of the signature
+ // which will fail if it has not been set.
+ //
+ // Only used if the authentication method
+ // requires such out-of-band authentication
+ //
+ // Return false if we don't need this (default) - only implement in
+ // subclasses if needed
+ virtual bool signCcrPayloadIfNeeded(const Value ccr, std::string &signature) const { return false; };
+
+ virtual std::string processCertificateCreationRequest(const std::shared_ptr &ccr) const;
+
+ std::shared_ptr createKeyPair(const ConfigCommon &config);
+
+ protected:
+ // Called to have a standard presentation of the CCR for the
+ // purposes of generating and verifying signatures
+ inline const std::string ccrToString(const Value &ccr) const {
+ return SB() << ccr["type"].as() << ccr["name"].as() << ccr["country"].as()
+ << ccr["organization"].as() << ccr["organization_unit"].as() << ccr["not_before"].as()
+ << ccr["not_after"].as() << ccr["usage"].as();
+ }
+
+ private:
+ CCRManager ccr_manager_{};
+};
+
+/**
+ * @brief Function to cast a pointer to a base class into a pointer to a
+ * subclass
+ *
+ * This function checks if the given class S is a subclass of the given base
+ * class C, then casts the given argument of type C into a pointer to S.
+ *
+ * @tparam S The derived class type
+ * @tparam C The base class type
+ * @param baseClass A shared pointer to the base class object
+ * @return A shared pointer to the derived class object if it is a subclass of
+ * the base class, nullptr otherwise.
+ *
+ * @throws std::bad_cast If the cast from base class to derived class fails
+ * @throws std::invalid_argument If S is not a subclass of C
+ *
+ * @note This function uses std::is_base_of to check for subclass relationship
+ * and std::dynamic_pointer_cast for safe casting from base class to derived
+ * class.
+ *
+ * @code
+ *
+ * // Example usage:
+ *
+ * class BaseClass {};
+ * class DerivedClass : public BaseClass {};
+ *
+ * std::shared_ptr base = std::make_shared();
+ *
+ * std::shared_ptr derived = castAs(base);
+ *
+ * if (derived != nullptr) {
+ * // Successfully casted to derived class
+ * } else {
+ * // Not a subclass of derived class
+ * }
+ *
+ * @endcode
+ */
+template
+inline std::shared_ptr castAs(const std::shared_ptr &baseClass) {
+ static_assert(std::is_base_of::value, "not a subclass");
+ return std::dynamic_pointer_cast(baseClass);
+}
+
+} // namespace certs
+} // namespace pvxs
+
+#endif // PVXS_AUTH_H
diff --git a/certs/authn/authn.h b/certs/authn/authn.h
new file mode 100644
index 000000000..986a53239
--- /dev/null
+++ b/certs/authn/authn.h
@@ -0,0 +1,20 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#ifndef PVXS_AUTHN_H_
+#define PVXS_AUTHN_H_
+
+class Config {
+ public:
+};
+
+// Interface to create Config objects
+class ConfigFactoryInterface {
+ public:
+ virtual Config* create() = 0;
+};
+
+#endif // PVXS_AUTHN_H_
diff --git a/certs/authn/ccrmanager.cpp b/certs/authn/ccrmanager.cpp
new file mode 100644
index 000000000..c519adcd8
--- /dev/null
+++ b/certs/authn/ccrmanager.cpp
@@ -0,0 +1,40 @@
+// Created on 19/09/2024.
+//
+#include "ccrmanager.h"
+
+#include
+
+#include "client.h"
+#include "pvacms.h"
+#include "security.h"
+
+DEFINE_LOGGER(auths, "pvxs.certs.auth");
+
+namespace pvxs {
+namespace certs {
+
+using namespace members;
+
+std::string CCRManager::createCertificate(const std::shared_ptr &cert_creation_request) const {
+ auto uri = nt::NTURI({}).build();
+ uri += {Struct("query", CCR_PROTOTYPE(cert_creation_request->verifier_fields))};
+ auto arg = uri.create();
+
+ // Set values of request argument
+ arg["path"] = RPC_CERT_CREATE;
+ arg["query"].from(cert_creation_request->ccr);
+
+ auto ctxt(client::Context::fromEnv());
+ auto value(ctxt.rpc(RPC_CERT_CREATE, arg).exec()->wait(5.0));
+
+ log_info_printf(auths, "X.509 CLIENT certificate%s\n", "");
+ log_info_printf(auths, "%s\n", value["status.value.index"].as().c_str());
+ log_info_printf(auths, "%s\n", value["state"].as().c_str());
+ log_info_printf(auths, "%llu\n", value["serial"].as());
+ log_info_printf(auths, "%s\n", value["issuer"].as().c_str());
+ log_info_printf(auths, "%s\n", value["certid"].as().c_str());
+ log_info_printf(auths, "%s\n", value["statuspv"].as().c_str());
+ return value["cert"].as();
+}
+} // namespace certs
+} // namespace pvxs
diff --git a/certs/authn/ccrmanager.h b/certs/authn/ccrmanager.h
new file mode 100644
index 000000000..1b3439885
--- /dev/null
+++ b/certs/authn/ccrmanager.h
@@ -0,0 +1,19 @@
+// Created on 19/09/2024.
+//
+
+#ifndef PVXS_CCRMANAGER_H_
+#define PVXS_CCRMANAGER_H_
+
+#include "security.h"
+
+namespace pvxs {
+namespace certs {
+
+class CCRManager {
+ public:
+ std::string createCertificate(const std::shared_ptr& cert_creation_request) const;
+};
+} // namespace certs
+} // namespace pvxs
+
+#endif // PVXS_CCRMANAGER_H_
diff --git a/certs/authn/jwt/Makefile b/certs/authn/jwt/Makefile
new file mode 100644
index 000000000..4bc692c6c
--- /dev/null
+++ b/certs/authn/jwt/Makefile
@@ -0,0 +1,8 @@
+# This is a Makefile fragment, see cert/authn/Makefile.
+
+SRC_DIRS += $(AUTHN)/jwt
+
+PROD += authnjwt
+authnjwt_INC += authnjwt.h
+
+authnjwt_SRCS += authnjwt.cpp
diff --git a/certs/authn/jwt/authnjwt.cpp b/certs/authn/jwt/authnjwt.cpp
new file mode 100644
index 000000000..c4810ff2e
--- /dev/null
+++ b/certs/authn/jwt/authnjwt.cpp
@@ -0,0 +1,108 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#include "authnjwt.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+DEFINE_LOGGER(auths, "pvxs.auth.jwt");
+
+namespace pvxs {
+namespace certs {
+
+void handle_request(int client_socket) {
+ char buffer[1024] = {0};
+ read(client_socket, buffer, 1024);
+
+ std::string request(buffer);
+ log_info_printf(auths, "Received Request: %s\n", request.c_str());
+
+ // Parse request to find the token
+ std::string method = request.substr(0, request.find(" "));
+ std::string uri = request.substr(request.find(" "), request.find("HTTP/1.1") - request.find(" "));
+
+ if (method == "POST" && uri.find(TOKEN_ENDPOINT) != std::string::npos) {
+ size_t token_pos = request.find("token=");
+ if (token_pos != std::string::npos) {
+ std::string token = request.substr(token_pos + 6); // Length of 'token=' is 6
+ token = token.substr(0, token.find("&"));
+ log_info_printf(auths, "Received Token: %s\n", token.c_str());
+
+ std::string response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nToken received";
+ send(client_socket, response.c_str(), response.size(), 0);
+ } else {
+ std::string response = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\n\r\nMissing 'token' parameter";
+ send(client_socket, response.c_str(), response.size(), 0);
+ }
+ } else {
+ std::string response = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\n\r\nNot Found";
+ send(client_socket, response.c_str(), response.size(), 0);
+ }
+
+ close(client_socket);
+}
+
+} // namespace certs
+} // namespace pvxs
+
+int main() {
+ int server_fd, new_socket;
+ struct sockaddr_in address;
+ int addrlen = sizeof(address);
+
+ // Creating socket file descriptor
+ if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
+ log_err_printf(auths, "Socket Error: %s\n", "");
+ perror("socket failed");
+ exit(EXIT_FAILURE);
+ }
+
+ // Forcefully attaching socket to the port 8080
+ int opt = 1;
+ if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
+ log_err_printf(auths, "Setsockopt Error: %s\n", "");
+ perror("setsockopt");
+ close(server_fd);
+ exit(EXIT_FAILURE);
+ }
+
+ address.sin_family = AF_INET;
+ address.sin_addr.s_addr = INADDR_ANY;
+ address.sin_port = htons(pvxs::certs::PORT);
+
+ // Forcefully attaching socket to the port 8080
+ if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
+ log_err_printf(auths, "Bind failure: %s\n", "");
+ close(server_fd);
+ exit(EXIT_FAILURE);
+ }
+ if (listen(server_fd, 3) < 0) {
+ log_err_printf(auths, "Listen failure: %s\n", "");
+ close(server_fd);
+ exit(EXIT_FAILURE);
+ }
+
+ log_info_printf(auths, "Server listening on port: %d\n", pvxs::certs::PORT);
+
+ while (true) {
+ if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
+ log_err_printf(auths, "Accept failure: %s\n", "");
+ close(server_fd);
+ exit(EXIT_FAILURE);
+ }
+
+ std::thread(pvxs::certs::handle_request, new_socket).detach();
+ }
+
+ return 0;
+}
diff --git a/certs/authn/jwt/authnjwt.h b/certs/authn/jwt/authnjwt.h
new file mode 100644
index 000000000..efc142f79
--- /dev/null
+++ b/certs/authn/jwt/authnjwt.h
@@ -0,0 +1,53 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#ifndef PVXS_AUTH_JWT_H
+#define PVXS_AUTH_JWT_H
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+//#include "auth.h"
+#include "ownedptr.h"
+#include "security.h"
+
+#define PVXS_JWT_AUTH_TYPE "jwt"
+
+namespace pvxs {
+namespace certs {
+
+const int PORT = 8080;
+const std::string TOKEN_ENDPOINT = "/token";
+
+/**
+ * Definition of the JWT identification type that contains the token and any
+ * other required identification info.
+ */
+struct Jwt {
+ std::string token;
+ int32_t kid; // key ID if present
+};
+
+/**
+ * The subclass of Credentials that contains the JwtAuth specific
+ * identification object
+ */
+struct JwtCredentials : public Credentials {
+ Jwt jwt; // jwt
+};
+
+} // namespace certs
+} // namespace pvxs
+
+#endif // PVXS_AUTH_JWT_H
diff --git a/certs/authn/jwt/configjwt.cpp b/certs/authn/jwt/configjwt.cpp
new file mode 100644
index 000000000..807c576e6
--- /dev/null
+++ b/certs/authn/jwt/configjwt.cpp
@@ -0,0 +1,42 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#include "configjwt.h"
+
+std::unique_ptr getConfigFactory() {
+ struct ConfigCmsFactory : public ConfigFactoryInterface {
+ std::unique_ptr create() override {
+ // EPICS_AUTH_JWT_REQUEST_FORMAT
+ if (pickone({"EPICS_AUTH_JWT_REQUEST_FORMAT"})) {
+ self.jwt_request_format = pickone.val;
+ }
+
+ // EPICS_AUTH_JWT_REQUEST_METHOD
+ if (pickone({"EPICS_AUTH_JWT_REQUEST_METHOD"})) {
+ self.jwt_request_method = pickone.val == "POST" ? Config::POST : Config::GET;
+ }
+
+ // EPICS_AUTH_JWT_RESPONSE_FORMAT
+ if (pickone({"EPICS_AUTH_JWT_RESPONSE_FORMAT"})) {
+ self.jwt_response_format = pickone.val;
+ }
+
+ // EPICS_AUTH_JWT_TRUSTED_URI
+ if (pickone({"EPICS_AUTH_JWT_TRUSTED_URI"})) {
+ self.jwt_trusted_uri = pickone.val;
+ }
+
+ // EPICS_AUTH_JWT_USE_RESPONSE_CODE
+ if (pickone({"EPICS_AUTH_JWT_USE_RESPONSE_CODE"})) {
+ self.jwt_use_response_code = parseTo(pickone.val);
+ }
+
+ return std::make_unique();
+ }
+ };
+
+ return std::make_unique();
+}
diff --git a/certs/authn/jwt/configjwt.h b/certs/authn/jwt/configjwt.h
new file mode 100644
index 000000000..ee3fe4aa9
--- /dev/null
+++ b/certs/authn/jwt/configjwt.h
@@ -0,0 +1,31 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#ifndef PVXS_CONFIGJWT_H_
+#define PVXS_CONFIGJWT_H_
+
+#include
+
+#include "ownedptr.h"
+
+#include "certconfig.h"
+
+class ConfigJwt : public Config {
+ public:
+ /**
+ * @brief The JWT token
+ */
+ std::string jwt_token;
+};
+
+class ConfigJwtFactory : public ConfigFactoryInterface {
+ public:
+ std::unique_ptr create() override {
+ return std::make_unique();
+ }
+};
+
+#endif //PVXS_CONFIGJWT_H_
diff --git a/certs/authn/krb/Makefile b/certs/authn/krb/Makefile
new file mode 100644
index 000000000..1f3718dc2
--- /dev/null
+++ b/certs/authn/krb/Makefile
@@ -0,0 +1,8 @@
+# This is a Makefile fragment, see cert/authn/Makefile.
+
+SRC_DIRS += $(AUTHN)/krb
+
+PROD += authnkrb
+authnkrb_INC += authnkrb.h
+
+authnkrb_SRCS += authnkrb.cpp
diff --git a/certs/authn/krb/authnkrb.cpp b/certs/authn/krb/authnkrb.cpp
new file mode 100644
index 000000000..7c5c39bd6
--- /dev/null
+++ b/certs/authn/krb/authnkrb.cpp
@@ -0,0 +1,302 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#include "authnkrb.h"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+
+#include "auth.h"
+#include "authregistry.h"
+#include "security.h"
+
+namespace pvxs {
+namespace security {
+
+DEFINE_LOGGER(auths, "pvxs.security.auth.krb");
+
+// Get rid of OSX 10.7 and greater deprecation warnings.
+#if defined(__APPLE__) && defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+std::shared_ptr KrbAuth::getCredentials(const impl::ConfigCommon &config) const {
+ log_debug_printf(auths,
+ "\n******************************************\nKerberos "
+ "Authenticator: %s\n",
+ "Begin acquisition");
+
+ // Create KrbCredentials shared_ptr
+ auto kerberos_credentials = std::make_shared();
+
+ return kerberos_credentials;
+}
+
+/**
+ * @brief Creates a signed certificate for the Kerberos authentication type.
+ *
+ * This function generates a signed certificate using the provided credentials
+ * and key pair. The certificate creation request (CSR) will be passed through
+ * the custom requestor to PVACMS (the CA) to sign the included X.509
+ * certificate for client or server authentication, depending on the usage.
+ *
+ * @param credentials The credentials used to create the signed certificate.
+ * @param key_pair The key pair containing the public key used for signing.
+ * @param usage the desired certificate usage bitmask
+ * @return The certificate creation request.
+ */
+std::shared_ptr KrbAuth::createCertCreationRequest(const std::shared_ptr &credentials,
+ const std::shared_ptr &key_pair,
+ const uint16_t &usage) const {
+ auto krb_credentials = castAs(credentials);
+
+ // Call subclass to set up common CSR fields
+ auto cert_creation_request = Auth::createCertCreationRequest(credentials, key_pair, usage);
+
+ OM_uint32 major_status, minor_status;
+ gss_ctx_id_t context = GSS_C_NO_CONTEXT;
+
+ // We use GSS_C_NO_CREDENTIAL to specify that we want to use the default
+ // credentials Usually, the default credential is obtained from the system's
+ // Kerberos TGT
+
+ // Similarly, for the target name, it will be of the format service@hostname
+ gss_name_t target_name;
+ // TODO remove server hardcoding. Determine target PVACMS server by config
+ gssNameFromString("PVACMS@SLAC.STANFORD.EDU", target_name);
+
+ // Initialize the context from a kerberos ticket
+ gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+ major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL, &context, target_name,
+ krb5_oid, // Kerberos 5 credentials only
+ GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, 0, GSS_C_NO_CHANNEL_BINDINGS,
+ GSS_C_NO_BUFFER, /* No input token provided because we haven't got
+ anything from other side, and we won't use it
+ anyway*/
+ NULL, &output_token, NULL, NULL);
+
+ if (GSS_ERROR(major_status)) {
+ throw std::runtime_error(SB() << "Failed to initialize kerberos security context: "
+ << gssErrorDescription(major_status, minor_status));
+ }
+
+ // Add token to credentials
+ krb_credentials->token = std::vector(static_cast(output_token.value),
+ static_cast(output_token.value) + output_token.length);
+
+ // Clean up
+ gss_delete_sec_context(&minor_status, &context, &output_token);
+
+ // KRB specific fields
+ shared_array token_bytes(krb_credentials->token.begin(), krb_credentials->token.end());
+ cert_creation_request->credentials = krb_credentials;
+ cert_creation_request->ccr["verifier.token"] = token_bytes;
+
+ return cert_creation_request;
+}
+
+/**
+ * @brief Verify the Kerberos authentication against the provided CCR.
+ *
+ * This function verifies the Kerberos authentication by comparing the provided
+ * CCR with the Kerberos authentication information provided in the GSS-API
+ * verifier.token.
+ *
+ * @param ccr The CCR which includes the information required in the certificate
+ * as well as the verifier.token created in the client capturing the kerberos
+ * ticket and wrapping it as a GSS-API token
+ * @param compareFunc We don't use the side-band verification with kerberos so
+ * we won't use this callback.
+ * @return True if the kerberos credentials extracted from the token and
+ * validated by the KDC match those in the CCR, false otherwise.
+ */
+bool KrbAuth::verify(const Value ccr, std::function) const {
+ // Verify that the token in the ccr is created from a ticket generated by
+ // the same KDC I'm configured in as a service
+
+ // Extract and decode client token from ccr
+ auto token_bytes = ccr["verifier.token"].as>();
+ std::vector vec_bytes(token_bytes.begin(), token_bytes.end());
+
+ gss_buffer_desc client_token;
+ client_token.length = vec_bytes.size();
+
+ std::unique_ptr buffer(new uint8_t[client_token.length]);
+ client_token.value = buffer.get();
+ std::copy(vec_bytes.begin(), vec_bytes.end(), static_cast(client_token.value));
+
+ // Get ready for accepting the client's token
+ gss_ctx_id_t context = GSS_C_NO_CONTEXT;
+ gss_buffer_desc server_token;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+
+ // The server accepts the context using the client's token
+ major_status = gss_accept_sec_context(&minor_status, &context,
+ GSS_C_NO_CREDENTIAL, // use the default credential
+ &client_token, GSS_C_NO_CHANNEL_BINDINGS,
+ NULL, // don't need the name of client
+ krb5_oid_ptr, // Kerberos 5 credentials only
+ &server_token,
+ NULL, // don't care about ret_flags
+ NULL, // ignore time_rec
+ NULL // ignore delegated_cred_handle
+ );
+
+ // Note: If the context is not fully established, major_status will be
+ // GSS_S_CONTINUE_NEEDED, and we would need to send the server_token back to
+ // the client and run this process again until it returns GSS_S_COMPLETE.
+ // However, as we are only interested in kerberos tickets and don't care
+ // about mutual authentication here, we won't ever send anything back and
+ // are only interested in finding out if the context can be created with the
+ // client token, and then what the context can tell us about the peer
+
+ if (GSS_ERROR(major_status)) {
+ throw std::runtime_error(SB() << "Verify Credentials: Failed to validate kerberos token: "
+ << gssErrorDescription(major_status, minor_status));
+ }
+
+ // Now get the peer credentials information from the context
+
+ // Retrieve the credentials
+ gss_name_t peer_name;
+ OM_uint32 peer_lifetime;
+ gss_OID_set peer_mechanisms;
+ int peer_credential_usage;
+ time_t now = time(NULL);
+
+ major_status =
+ gss_inquire_cred(&minor_status, nullptr, &peer_name, &peer_lifetime, &peer_credential_usage, &peer_mechanisms);
+ throw std::runtime_error(SB() << "Verify Credentials: Failed to inquire credentials: "
+ << gssErrorDescription(major_status, minor_status));
+
+ // Get the principal name
+ gss_buffer_desc peer_name_buffer;
+ major_status = gss_display_name(&minor_status, peer_name, &peer_name_buffer, NULL);
+ if (GSS_ERROR(major_status)) {
+ gss_release_name(&minor_status, &peer_name);
+ throw std::runtime_error(SB() << "Verify Credentials: Failed to get principal name: "
+ << gssErrorDescription(major_status, minor_status));
+ }
+
+ std::string peer_principal(static_cast(peer_name_buffer.value), peer_name_buffer.length);
+ gss_release_buffer(&minor_status, &peer_name_buffer);
+ gss_release_name(&minor_status, &peer_name);
+
+ // Check if the credentials are for Kerberos
+ if (peer_mechanisms->elements != krb5_oid) {
+ throw std::runtime_error(SB() << "Verify Credentials: Client credentials are not for Kerberos "
+ "mechanism: "
+ << gssErrorDescription(major_status, minor_status));
+ }
+
+ // Now, 'principal' contains the principal name, 'lifetime' contains the
+ // remaining lifetime of the credentials in seconds, and 'expiration'
+ // contains the ticket expiration time
+
+ // Verify the peer credentials against ccr fields
+ // ccr["name"] == peer_principal(before @ sign)
+ // ccr["organization"] == peer_principal(after @ sign)
+ // ccr["organization_unit"] == blank
+ // ccr["country"] == blank
+ // ccr["type"] == "krb"
+ // ccr["not_before"] < now+peer_lifetime
+ // ccr["not_after"] <= now+peer_lifetime
+
+ // Split out name and organization if the principal has an at sign
+ std::size_t found;
+ auto peer_principal_name(ccr["name"].as());
+ std::string peer_principal_organization;
+ if ((found = peer_principal_name.find('@')) != std::string::npos) {
+ peer_principal_organization = peer_principal_name.substr(found + 1);
+ peer_principal_name.resize(found);
+ }
+ // Now the tests
+ if (peer_principal_name.compare(ccr["name"].as()) != 0) {
+ throw std::runtime_error(SB() << "Verify Credentials: Kerberos name does not match name in CCR");
+ }
+ if (peer_principal_organization.compare(ccr["organization"].as()) != 0) {
+ throw std::runtime_error(SB() << "Verify Credentials: Kerberos organization "
+ "does not match name in CCR");
+ }
+ if (!ccr["organization_unit"].as().empty()) {
+ throw std::runtime_error(SB() << "Verify Credentials: Organization Unit in CCR not blank");
+ }
+ if (!ccr["country"].as().empty()) {
+ throw std::runtime_error(SB() << "Verify Credentials: Country in CCR not blank");
+ }
+ if (ccr["type"].as().compare(PVXS_KRB_AUTH_TYPE) != 0) {
+ throw std::runtime_error(SB() << "Verify Credentials: Type of CCR not Kerberos");
+ }
+ if (ccr["not_before"].as() >= now + peer_lifetime) {
+ throw std::runtime_error(SB() << "Verify Credentials: CCR not_before after "
+ "end of kerberos ticket lifetime");
+ }
+
+ if (ccr["not_after"].as() > now + peer_lifetime) {
+ throw std::runtime_error(SB() << "Verify Credentials: CCR not_after after "
+ "end of kerberos ticket lifetime");
+ }
+
+ return true;
+}
+
+void KrbAuth::gssNameFromString(const std::string &name, gss_name_t &target_name) const {
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc name_buf;
+ gss_OID name_type = GSS_C_NT_HOSTBASED_SERVICE;
+
+ /* initialize the name buffer */
+ name_buf.value = (void *)name.c_str();
+ name_buf.length = name.size() + 1;
+
+ /* import the name */
+ major_status = gss_import_name(&minor_status, &name_buf, name_type, &target_name);
+ if (GSS_ERROR(major_status)) {
+ throw std::runtime_error(SB() << "Kerberos can't create name from \"" << name
+ << "\" : " << gssErrorDescription(major_status, minor_status));
+ }
+}
+
+std::string KrbAuth::gssErrorDescription(OM_uint32 major_status, OM_uint32 minor_status) const {
+ OM_uint32 msg_ctx;
+ OM_uint32 minor;
+ gss_buffer_desc status_string;
+ auto error_description = SB();
+ char context[GSS_STATUS_BUFFER_LEN] = "";
+
+ msg_ctx = 0;
+ while (!gss_display_status(&minor, major_status, GSS_C_GSS_CODE, GSS_C_NO_OID, &msg_ctx, &status_string)) {
+ snprintf(context, GSS_STATUS_BUFFER_LEN, "%.*s\n", (int)status_string.length, (char *)status_string.value);
+ error_description << context;
+ gss_release_buffer(&minor, &status_string);
+ }
+
+ msg_ctx = 0;
+ while (!gss_display_status(&minor, minor_status, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string)) {
+ snprintf(context, GSS_STATUS_BUFFER_LEN, "%.*s\n", (int)status_string.length, (char *)status_string.value);
+ error_description << context;
+ gss_release_buffer(&minor, &status_string);
+ }
+
+ return error_description.str();
+}
+
+#if defined(__APPLE__) && defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+} // namespace security
+} // namespace pvxs
diff --git a/certs/authn/krb/authnkrb.h b/certs/authn/krb/authnkrb.h
new file mode 100644
index 000000000..a30a87f42
--- /dev/null
+++ b/certs/authn/krb/authnkrb.h
@@ -0,0 +1,116 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#ifndef PVXS_AUTH_KERB_H
+#define PVXS_AUTH_KERB_H
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+
+#include "auth.h"
+#include "authregistry.h"
+#include "ownedptr.h"
+#include "security.h"
+
+#define PVXS_KRB_AUTH_TYPE "krb"
+#define GSS_STATUS_BUFFER_LEN 1024
+
+namespace pvxs {
+namespace security {
+
+// Declarations
+extern gss_OID_desc krb5_oid_desc;
+extern gss_OID krb5_oid;
+
+// Get rid of OSX 10.7 and greater deprecation warnings.
+#if defined(__APPLE__) && defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+/**
+ * The subclass of Credentials that contains the KrbAuth specific
+ * identification object
+ */
+struct KrbCredentials : public Credentials {
+ // gss-api token for default Kerberos Ticket Granting Ticket
+ // (TGT) on the system, stored as a byte array
+ std::vector token;
+
+ KrbCredentials() {}
+
+ ~KrbCredentials() {}
+};
+
+/**
+ * @class KrbAuth
+ * @brief The KrbAuth class provides Kerberos authentication
+ * functionality.
+ *
+ * This class is responsible for retrieving credentials for users that have been
+ * authenticated against a Kerberos server. It inherits from the Auth
+ * base class.
+ *
+ * In order to use the KrbAuth, it must be registered with the
+ * CertFactory using the REGISTER_AUTHENTICATOR() macro.
+ *
+ * The KrbAuth class implements the getCredentials() and
+ * createCertCreationRequest() methods. The getCredentials() method returns the
+ * credentials used for authentication. The createCertCreationRequest() method
+ * creates a kerberos specific certificate creation request using the provided
+ * credentials.
+ */
+class KrbAuth : public Auth {
+ public:
+ REGISTER_AUTHENTICATOR();
+
+ // Constructor. Adds in kerberos specific fields (ticket) to the verifier
+ // field of the ccr
+ KrbAuth()
+ : Auth(PVXS_KRB_AUTH_TYPE, {Member(TypeCode::Int8A, "token")}),
+ krb5_oid(&krb5_oid_desc),
+ krb5_oid_ptr(&krb5_oid) {
+ krb5_oid_desc.length = 9;
+ krb5_oid_desc.elements = (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02";
+ };
+ ~KrbAuth() override = default;
+
+ gss_OID krb5_oid;
+ gss_OID *krb5_oid_ptr;
+
+ std::shared_ptr getCredentials(const impl::ConfigCommon &config) const override;
+
+ std::shared_ptr createCertCreationRequest(const std::shared_ptr &credentials,
+ const std::shared_ptr &key_pair,
+ const uint16_t &usage) const override;
+
+ bool verify(
+ const Value ccr,
+ std::function signature_verifier) const override;
+
+ private:
+ gss_OID_desc krb5_oid_desc;
+
+ std::string gssErrorDescription(OM_uint32 major_status, OM_uint32 minor_status) const;
+
+ void gssNameFromString(const std::string &name, gss_name_t &target_name) const;
+};
+
+#if defined(__APPLE__) && defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+} // namespace security
+} // namespace pvxs
+
+#endif // PVXS_AUTH_KERB_H
diff --git a/certs/authn/krb/configkrb.cpp b/certs/authn/krb/configkrb.cpp
new file mode 100644
index 000000000..1b0ccadb5
--- /dev/null
+++ b/certs/authn/krb/configkrb.cpp
@@ -0,0 +1,27 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#include "configkrb.h"
+
+std::unique_ptr getConfigFactory() {
+ struct ConfigCmsFactory : public ConfigFactoryInterface {
+ std::unique_ptr create() override {
+ // EPICS_AUTH_KRB_KEYTAB
+ if (pickone({"EPICS_AUTH_KRB_KEYTAB"})) {
+ self.krb_keytab = pickone.val;
+ }
+
+ // EPICS_AUTH_KRB_REALM
+ if (pickone({"EPICS_AUTH_KRB_REALM"})) {
+ self.krb_realm = pickone.val;
+ }
+
+ return std::make_unique();
+ }
+ };
+
+ return std::make_unique();
+}
diff --git a/certs/authn/krb/configkrb.h b/certs/authn/krb/configkrb.h
new file mode 100644
index 000000000..597d7a627
--- /dev/null
+++ b/certs/authn/krb/configkrb.h
@@ -0,0 +1,46 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#ifndef PVXS_CONFIGKRB_H_
+#define PVXS_CONFIGKRB_H_
+
+#include
+
+#include "ownedptr.h"
+
+#include "certconfig.h"
+
+class ConfigKrb : public Config {
+ public:
+ /**
+ * @brief This is the string to which PVACMS/CLUSTER is prepended to
+ * create the service principal to be added to the Kerberos KDC to
+ * enable Kerberos ticket verification by the PVACMS.
+ *
+ * It is used in an EPICS agent when creating a GSSAPI context to
+ * create a token to send to the PVACMS to be validated, and used by
+ * the PVACMS to create another GSSAPI context to decode the token
+ * and validate the CCR.
+ *
+ * There is no default so this value *must* be
+ * specified if Kerberos support is configured.
+ *
+ * The KDC will share a keytab file containing the secret key
+ * for the PVACMS/CLUSTER service and it will be made available to
+ * all members of the cluster but protected so no other processes
+ * or users can access it.
+ */
+ std::string krb_realm;
+};
+
+class ConfigKrbFactory : public ConfigFactoryInterface {
+ public:
+ std::unique_ptr create() override {
+ return std::make_unique();
+ }
+};
+
+#endif //PVXS_CONFIGKRB_H_
diff --git a/certs/authn/ldap/authnldap.cpp b/certs/authn/ldap/authnldap.cpp
new file mode 100644
index 000000000..4e87f8397
--- /dev/null
+++ b/certs/authn/ldap/authnldap.cpp
@@ -0,0 +1,56 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#include "authnldap.h"
+
+#include
+#include
+
+#include
+
+#include "auth.h"
+#include "authregistry.h"
+#include "security.h"
+
+namespace pvxs {
+namespace security {
+
+DEFINE_LOGGER(auths, "pvxs.security.auth.LDAP");
+
+std::shared_ptr LdapAuth::getCredentials(const impl::ConfigCommon &config) const {
+ log_debug_printf(auths,
+ "\n******************************************\nLDAP "
+ "Authenticator: %s\n",
+ "Begin acquisition");
+
+ auto ldap_credentials = std::make_shared();
+ throw std::runtime_error("Process not authenticated with LDAP");
+ return ldap_credentials;
+};
+
+std::shared_ptr LdapAuth::createCertCreationRequest(
+ const std::shared_ptr &credentials, const std::shared_ptr &key_pair, const uint16_t &usage) const {
+ auto ldap_credentials = castAs(credentials);
+
+ auto cert_creation_request = Auth::createCertCreationRequest(credentials, key_pair, usage);
+
+ return cert_creation_request;
+};
+
+std::string LdapAuth::processCertificateCreationRequest(const std::shared_ptr &ccr) const {
+ throw std::runtime_error("Custom Signer: Failed to sign certificate.");
+ return nullptr;
+}
+
+bool LdapAuth::verify(const Value ccr,
+ std::function signature_verifier) const {
+ // Verify that the signature provided in the CCR that was established in the
+ // GSSAPI session was validated and signed
+ return signature_verifier(ccrToString(ccr), ccr["verifier.signature"].as());
+}
+
+} // namespace security
+} // namespace pvxs
diff --git a/certs/authn/ldap/authnldap.h b/certs/authn/ldap/authnldap.h
new file mode 100644
index 000000000..4bfecb3cb
--- /dev/null
+++ b/certs/authn/ldap/authnldap.h
@@ -0,0 +1,86 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#ifndef PVXS_AUTH_LDAP_H
+#define PVXS_AUTH_LDAP_H
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "auth.h"
+#include "authregistry.h"
+#include "certfactory.h"
+#include "ownedptr.h"
+#include "security.h"
+
+#define PVXS_LDAP_AUTH_TYPE "ldap"
+
+namespace pvxs {
+namespace security {
+
+/**
+ * Definition of the LDAP identification type containing all required LDAP
+ * credential information.
+ */
+struct LdapId {
+ int i; // TODO placeholder
+};
+
+/**
+ * The subclass of Credentials that contains the LdapAuth specific
+ * identification object
+ */
+struct LdapCredentials : Credentials {
+ LdapId id; // LDAP ID
+};
+
+/**
+ * @class LdapAuth
+ * @brief The LdapAuth class provides LDAP authentication
+ * functionality.
+ *
+ * This class is responsible for retrieving credentials for users that have been
+ * authenticated against an LDAP server. It inherits from the Auth base
+ * class.
+ *
+ * In order to use the LdapAuth, it must be registered with the
+ * CertFactory using the REGISTER_AUTHENTICATOR() macro.
+ *
+ * The LdapAuth class implements the getCredentials() and
+ * createCertCreationRequest() methods. The getCredentials() method returns the
+ * credentials used for authentication. The createCertCreationRequest() method
+ * creates a signed certificate using the provided credentials.
+ */
+class LdapAuth : public Auth {
+ public:
+ REGISTER_AUTHENTICATOR();
+
+ // Constructor
+ LdapAuth() : Auth(PVXS_LDAP_AUTH_TYPE, {}) {};
+ ~LdapAuth() override = default;
+
+ std::shared_ptr getCredentials(const impl::ConfigCommon &config) const override;
+
+ std::shared_ptr createCertCreationRequest(const std::shared_ptr &credentials,
+ const std::shared_ptr &key_pair,
+ const uint16_t &usage) const override;
+
+ bool verify(
+ const Value ccr,
+ std::function signature_verifier) const override;
+
+ std::string processCertificateCreationRequest(const std::shared_ptr &ccr) const override;
+};
+
+} // namespace security
+} // namespace pvxs
+
+#endif // PVXS_AUTH_LDAP_H
diff --git a/certs/authn/ldap/configldap.cpp b/certs/authn/ldap/configldap.cpp
new file mode 100644
index 000000000..5c0b38e2d
--- /dev/null
+++ b/certs/authn/ldap/configldap.cpp
@@ -0,0 +1,52 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#include "configldap.h"
+
+std::unique_ptr getConfigFactory() {
+ struct ConfigCmsFactory : public ConfigFactoryInterface {
+ std::unique_ptr create() override {
+ // EPICS_AUTH_LDAP_ACCOUNT
+ if (pickone({"EPICS_AUTH_LDAP_ACCOUNT"})) {
+ self.ldap_account = pickone.val;
+ }
+
+ // EPICS_AUTH_LDAP_ACCOUNT_PWD_FILE
+ if (pickone({"EPICS_AUTH_LDAP_ACCOUNT_PWD_FILE"})) {
+ auto filepath = pickone.val;
+ self.ensureDirectoryExists(filepath);
+ try {
+ self.ldap_account_password = self.getFileContents(filepath);
+ } catch (std::exception &e) {
+ log_err_printf(serversetup, "error reading password file: %s. %s", filepath.c_str(), e.what());
+ }
+ }
+
+ // EPICS_AUTH_LDAP_HOST
+ if (pickone({"EPICS_AUTH_LDAP_HOST"})) {
+ self.ldap_host = pickone.val;
+ }
+
+ // EPICS_AUTH_LDAP_PORT
+ if (pickone({"EPICS_AUTH_LDAP_PORT"})) {
+ try {
+ self.ldap_port = parseTo(pickone.val);
+ } catch (std::exception &e) {
+ log_err_printf(serversetup, "%s invalid integer : %s", pickone.name.c_str(), e.what());
+ }
+ }
+
+ // EPICS_AUTH_LDAP_SEARCH_ROOT
+ if (pickone({"EPICS_AUTH_LDAP_SEARCH_ROOT"})) {
+ self.ldap_search_root = pickone.val;
+ }
+
+ return std::make_unique();
+ }
+ };
+
+ return std::make_unique();
+}
diff --git a/certs/authn/ldap/configldap.h b/certs/authn/ldap/configldap.h
new file mode 100644
index 000000000..03977f7ac
--- /dev/null
+++ b/certs/authn/ldap/configldap.h
@@ -0,0 +1,32 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#ifndef PVXS_CONFIGLDAP_H_
+#define PVXS_CONFIGLDAP_H_
+
+#include
+
+#include "ownedptr.h"
+
+#include "certconfig.h"
+
+class ConfigLdap : public Config {
+ public:
+ std::string ldap_account;
+ std::string ldap_account_password;
+ std::string ldap_host;
+ unsigned short ldap_port;
+ std::string ldap_search_root;
+};
+
+class ConfigLdapFactory : public ConfigFactoryInterface {
+ public:
+ std::unique_ptr create() override {
+ return std::make_unique();
+ }
+};
+
+#endif //PVXS_CONFIGLDAP_H_
diff --git a/certs/authn/std/Makefile b/certs/authn/std/Makefile
new file mode 100644
index 000000000..b98668647
--- /dev/null
+++ b/certs/authn/std/Makefile
@@ -0,0 +1,10 @@
+# This is a Makefile fragment, see cert/authn/Makefile.
+
+SRC_DIRS += $(AUTHN)/std
+
+PROD += authnstd
+authnstd_INC += authnstd.h
+authnstd_INC += configstd.h
+
+authnstd_SRCS += authnstd.cpp
+authnstd_SRCS += configstd.cpp
diff --git a/certs/authn/std/authnstd.cpp b/certs/authn/std/authnstd.cpp
new file mode 100644
index 000000000..f9fc8b74d
--- /dev/null
+++ b/certs/authn/std/authnstd.cpp
@@ -0,0 +1,486 @@
+/**
+ * Copyright - See the COPYRIGHT that is included with this distribution.
+ * pvxs is distributed subject to a Software License Agreement found
+ * in file LICENSE that is included with this distribution.
+ */
+
+#include "authnstd.h"
+
+#include
+#include
+
+#include
+
+#include "certfilefactory.h"
+#include "configstd.h"
+#include "openssl.h"
+#include "p12filefactory.h"
+#include "utilpvt.h"
+
+DEFINE_LOGGER(auths, "pvxs.auth.std");
+
+namespace pvxs {
+namespace certs {
+
+/**
+ * @brief Extract the country code from a locale string
+ *
+ * @param locale_str the locale string to extract the country code from
+ * @return the country code extracted from the locale string
+ */
+static std::string extractCountryCode(const std::string &locale_str) {
+ // Look for underscore
+ auto pos = locale_str.find('_');
+ if (pos == std::string::npos || pos + 3 > locale_str.size()) {
+ return "";
+ }
+
+ std::string country_code = locale_str.substr(pos + 1, 2);
+ std::transform(country_code.begin(), country_code.end(), country_code.begin(), ::toupper);
+ return country_code;
+}
+
+/**
+ * @brief Get the current country code of where the process is running
+ * This returns the two letter country code. It is always upper case.
+ * For example for the United States it returns US, and for France, FR.
+ *
+ * @return the current country code of where the process is running
+ */
+static std::string getCountryCode() {
+ // 1. Try from std::locale("")
+ {
+ std::locale loc("");
+ std::string name = loc.name();
+ if (name != "C" && name != "POSIX") {
+ std::string cc = extractCountryCode(name);
+ if (!cc.empty()) {
+ return cc;
+ }
+ }
+ }
+
+ // 2. If we failed, try the LANG environment variable
+ {
+ const char *lang = std::getenv("LANG");
+ if (lang && *lang) {
+ std::string locale_str(lang);
+ std::string cc = extractCountryCode(locale_str);
+ if (!cc.empty()) {
+ return cc;
+ }
+ }
+ }
+
+ // 3. Default to "US" if both attempts failed
+ return "US";
+}
+
+/**
+ * @brief Print the usage message for the authnstd tool
+ *
+ * @param argv0 the name of the program
+ */
+void usage(const char *argv0) {
+ std::cerr << "Usage: " << argv0
+ << " \n"
+ "\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