|
|
@@ -184,6 +184,23 @@ Result<CredentialInfo> CredentialStore::create(const CreateCredentialRequest& re
|
|
|
};
|
|
|
break;
|
|
|
}
|
|
|
+ case CredentialType::ClientCertificate: {
|
|
|
+ auto data = ClientCertificateData::fromJson(request.data);
|
|
|
+ if (data.certificate_pem.empty() || data.private_key_pem.empty()) {
|
|
|
+ return Error(ErrorCode::InvalidArgument,
|
|
|
+ "A client certificate needs both the certificate and its private key");
|
|
|
+ }
|
|
|
+ // Both halves are encrypted together. The certificate alone is
|
|
|
+ // public, but keeping the pair in one blob means they cannot drift
|
|
|
+ // apart - a certificate stored without its key proves nothing, and
|
|
|
+ // a key without its certificate cannot be presented.
|
|
|
+ data_to_encrypt = data.toJson();
|
|
|
+ // Nothing about the key, not even its length, in the public half.
|
|
|
+ public_data = {
|
|
|
+ {"hasPrivateKey", true}
|
|
|
+ };
|
|
|
+ break;
|
|
|
+ }
|
|
|
default:
|
|
|
return Error(ErrorCode::InvalidArgument, "Unsupported credential type");
|
|
|
}
|
|
|
@@ -521,6 +538,12 @@ Result<HttpAuth> CredentialStore::getHttpAuth(const std::string& id, const std::
|
|
|
return Error(ErrorCode::InvalidArgument, "MySQL credentials cannot be used for HTTP authentication");
|
|
|
case CredentialType::Postgresql:
|
|
|
return Error(ErrorCode::InvalidArgument, "PostgreSQL credentials cannot be used for HTTP authentication");
|
|
|
+ case CredentialType::ClientCertificate:
|
|
|
+ // Not a header. This one is presented during the TLS handshake, and
|
|
|
+ // is assigned in a workflow's settings rather than on a node.
|
|
|
+ return Error(ErrorCode::InvalidArgument,
|
|
|
+ "A client certificate is presented during the TLS handshake, not as an "
|
|
|
+ "authentication header. Assign it in the workflow's settings instead.");
|
|
|
default:
|
|
|
return Error(ErrorCode::Internal, "Unsupported credential type");
|
|
|
}
|
|
|
@@ -555,6 +578,33 @@ Result<ImapData> CredentialStore::getImapCredentials(const std::string& id, cons
|
|
|
return ImapData::fromJson(decrypt_result.value());
|
|
|
}
|
|
|
|
|
|
+Result<ClientCertificateData> CredentialStore::getClientCertificate(const std::string& id,
|
|
|
+ const std::string& workflow_id) {
|
|
|
+ auto get_result = storage_.get(COLLECTION, id);
|
|
|
+ if (get_result.failed()) {
|
|
|
+ return Error(ErrorCode::NotFound, "Credential not found: " + id);
|
|
|
+ }
|
|
|
+
|
|
|
+ auto doc = CredentialDocument::fromJson(get_result.value());
|
|
|
+
|
|
|
+ if (doc.metadata.type != CredentialType::ClientCertificate) {
|
|
|
+ return Error(ErrorCode::InvalidArgument,
|
|
|
+ "Credential " + id + " is not a client certificate");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!hasWorkflowAccess(doc.metadata, workflow_id)) {
|
|
|
+ return Error(ErrorCode::PermissionDenied,
|
|
|
+ "Workflow " + workflow_id + " does not have access to credential " + id);
|
|
|
+ }
|
|
|
+
|
|
|
+ auto decrypt_result = decryptData(doc.encrypted_data);
|
|
|
+ if (decrypt_result.failed()) {
|
|
|
+ return decrypt_result.error();
|
|
|
+ }
|
|
|
+
|
|
|
+ return ClientCertificateData::fromJson(decrypt_result.value());
|
|
|
+}
|
|
|
+
|
|
|
Result<SmtpData> CredentialStore::getSmtpCredentials(const std::string& id, const std::string& workflow_id) {
|
|
|
auto get_result = storage_.get(COLLECTION, id);
|
|
|
if (get_result.failed()) {
|