Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ With JDK 11, revocation checking has some issues:
for CMP and TLS trust chains.
* An OCSP AIA extension is not always used in path validation
for TLS trust chains.


# Lightweight CMP client CLI application

Expand Down Expand Up @@ -373,3 +373,15 @@ usage: java -jar path/to/CmpClient.jar

The lower part of the [configuration README file](/doc/config/README.md)
explains the YAML configuration file structure.

# RA plugin

The Inventory interface supports two ways to use an external certificate request checker using system environment variables
1. Plugin for external processes: `RA_INVENTORY_EXEC = <path to executable>`
2. Plugin for HTTP service: `RA_INVENTORY_URL = <URL to HTTP server>`

## Process-based checker
An example for an external process is the python program [scripts/external-inventory-example.pyw](scripts/external-inventory-example.pyw).

## HTTP (REST-API) checker
The test case expects the HTTP server running on http://127.0.0.1:8000/check
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<configuration>
<excludes>
<exclude>**/local/**</exclude>
<exclude>**/TestInventoryHttpPlugin.java</exclude>
</excludes>
</configuration>
</plugin>
Expand Down
3 changes: 3 additions & 0 deletions resources/rule.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
subject.o must be requester.o
AND subject.c must be requester.c
AND subject.cn must end with ".siemens.com"
47 changes: 47 additions & 0 deletions scripts/external-inventory-example.pyw
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env python3

import time
import json
import os
import sys
import tempfile
from datetime import datetime

def main():
# Check if '-reason' is in the command line arguments
report_reason = '-reason' in sys.argv

# Read JSON string from the standard input
try:
json_from_input = sys.stdin.read()
except KeyboardInterrupt:
sys.exit(1)

# Parse JSON string
try:
json_data = json.loads(json_from_input)
except json.JSONDecodeError as e:
return False, f"Error decoding JSON: {e}"

# Get the system's temporary directory
temp_dir = tempfile.gettempdir()

# Create or append to the log file
log_file_path = os.path.join(temp_dir, "RaPluginLog.txt")
with open(log_file_path, 'a') as file:
# Write the current time to the file
file.write(f"-------------------------{datetime.now()}\n")

# Write the input to the file
json.dump(json_data, file, indent=2)

# Print '1' to the screen
print("1", end='')

# If '-reason' is in the command line arguments, print the reason JSON
if report_reason:
print("\n\n{\"reason\":\"I trust you\"}")

if __name__ == "__main__":
main()

Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package com.siemens.pki.lightweightcmpra.plugin;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.siemens.pki.cmpracomponent.configuration.CheckAndModifyResult;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DslProcessInterfaceImpl extends InventoryPluginBase {

private static final Logger LOGGER = LoggerFactory.getLogger(DslProcessInterfaceImpl.class);

/**
* check and optionally modify a CRMF certificate request that was received in a
* CMP ir, cr or kur message.
*
* @param transactionID the transactionID of the CMP request message. The
* transactionID can be used to correlate calls of
* {@link #checkAndModifyCertRequest(byte[], String, byte[], String, byte[])}
* and
* {@link #learnEnrollmentResult(byte[], byte[], String, String, String)}.
* @param requesterDn Distinguished Name (DN) of the CMP requester. This
* is the subject of the first certificate in the
* extraCerts field of the CMP request or the sender
* extracted from the PKI message header. If neither
* signature-based protection was used nor the sender
* field was set the requesterDn is <code>null</code>.
* The DN is an X500 name formatted as string
* according to the BouncyCastle library defaults.
* @param certTemplate the ASN.1 DER-encoded CertTemplate of the
* certificate request as received from the requester.
* Note that it may indicate central key generation,
* optionally specifying key parameters.
* @param requestedSubjectDn subject DN extracted from the CertTemplate of the
* request or <code>null</code> if subject was not
* present. The DN is an X500 name formatted as string
* according to the BouncyCastle library defaults.
* This parameter is provided for convenience.
* @param pkiMessage the ASN.1 DER-encoded CMP ir, cr or kur message
* @return result of validation check
*/
@Override
public CheckAndModifyResult checkAndModifyCertRequest(byte[] transactionID, String requesterDn, byte[] certTemplate, String requestedSubjectDn, byte[] pkiMessage) {
/* 1. form Json
* 2. stringify json
* 3. base64-encode pkimessage
* 3. form wrapped requestÄ (JSON \r\n\r\n pkimessage in base64) --> attach separator and pkimessage to json-string
* 4. call process and put the stuff from §3 into its stdout
* 5. wait for it to returnü capture status code and stdout
* 6. react to the data you received ##TEST return true
*/
// Form Json
ObjectNode json = null;
try {
json = createJson(transactionID, requesterDn, certTemplate, requestedSubjectDn, pkiMessage);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}

// Stringify json
ObjectMapper objectMapper = new ObjectMapper();
String jsonString;

try {
jsonString = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(json);
} catch (JsonProcessingException ex) {
ex.printStackTrace();
return NEGATIVE_CHECK_RESULT;
}

// Get the user's temporary directory
String tempDir = System.getProperty("java.io.tmpdir");

// Create a Path for the file
Path csrPath = Paths.get(tempDir, "request.json");

try {
// Write the JSON string to the file
Files.write(csrPath, jsonString.getBytes());
} catch (IOException e) {
LOGGER.error("inventory error while checking certificate request:", e);
}


try {
// Start RA DSL program in docker container with parameters
String requestVolume = csrPath.toAbsolutePath() + ":/tmp/request.json";
String ruleVolume = Paths.get(System.getProperty("RA_DSL_RULE")).toAbsolutePath() +":/tmp/input.txt";
String[] command = {"docker", "run", "-v", ruleVolume, "-v", requestVolume, "radsl"};
ProcessBuilder processBuilder = new ProcessBuilder(command);

// Redirect standard error and output to Java process's error and output
processBuilder.redirectErrorStream(true);

// Start the process
Process process = processBuilder.start();

// Wait for the process to finish, but not more than 100 seconds
boolean isFinished = process.waitFor(100, TimeUnit.SECONDS);

final String processResult;
final int processStatus;

// If the process finished, capture its output and status code
if (isFinished) {
processStatus = process.exitValue();

// Read the process output
StringBuilder output = new StringBuilder();
// Create a BufferedReader to read the output
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
output.append(line);
}

processResult = output.toString();
} else {
// Handle case where the process didn't finish within 100 seconds
processStatus = -1;
processResult = "";
LOGGER.error("external process didn't finish within 100 seconds.");
}

return new CheckAndModifyResult() {

@Override
public byte[] getUpdatedCertTemplate() {
return null;
}

@Override
public boolean isGranted() {
return (processStatus == 0 && processResult.contains("true"));
}

};
} catch (IOException | InterruptedException e) {
LOGGER.error("inventory error while checking certificate request:", e);
return NEGATIVE_CHECK_RESULT;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.siemens.pki.lightweightcmpra.plugin;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.siemens.pki.cmpracomponent.configuration.CheckAndModifyResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import java.net.http.HttpClient;

public class HttpInventoryInterfaceImpl extends InventoryPluginBase {

private static final Logger LOGGER = LoggerFactory.getLogger(HttpInventoryInterfaceImpl.class);

/**
* check and optionally modify a CRMF certificate request that was received in a
* CMP ir, cr or kur message.
*
* @param transactionID the transactionID of the CMP request message. The
* transactionID can be used to correlate calls of
* {@link #checkAndModifyCertRequest(byte[], String, byte[], String, byte[])}
* and
* {@link #learnEnrollmentResult(byte[], byte[], String, String, String)}.
* @param requesterDn Distinguished Name (DN) of the CMP requester. This
* is the subject of the first certificate in the
* extraCerts field of the CMP request or the sender
* extracted from the PKI message header. If neither
* signature-based protection was used nor the sender
* field was set the requesterDn is <code>null</code>.
* The DN is an X500 name formatted as string
* according to the BouncyCastle library defaults.
* @param certTemplate the ASN.1 DER-encoded CertTemplate of the
* certificate request as received from the requester.
* Note that it may indicate central key generation,
* optionally specifying key parameters.
* @param requestedSubjectDn subject DN extracted from the CertTemplate of the
* request or <code>null</code> if subject was not
* present. The DN is an X500 name formatted as string
* according to the BouncyCastle library defaults.
* This parameter is provided for convenience.
* @param pkiMessage the ASN.1 DER-encoded CMP ir, cr or kur message
* @return result of validation check
*/
@Override
public CheckAndModifyResult checkAndModifyCertRequest(byte[] transactionID, String requesterDn, byte[] certTemplate, String requestedSubjectDn, byte[] pkiMessage) {
/* 1. form Json
* 2. stringify json
* 3. base64-encode pkimessage
* 3. form wrapped requestÄ (JSON \r\n\r\n pkimessage in base64) --> attach separator and pkimessage to json-string
* 4. call process and put the stuff from §3 into its stdout
* 5. wait for it to returnü capture status code and stdout
* 6. react to the data you received ##TEST return true
*/
// Form Json
ObjectNode json = null;
try {
json = createJson(transactionID, requesterDn, certTemplate, requestedSubjectDn, pkiMessage);
} catch (JsonProcessingException e) {
LOGGER.error("inventory error while checking certificate request:", e);
return NEGATIVE_CHECK_RESULT;
}
// Stringify json
ObjectMapper objectMapper = new ObjectMapper();
String jsonString;

try {
jsonString = objectMapper.writeValueAsString(json);
} catch (JsonProcessingException e) {
LOGGER.error("inventory error while checking certificate request:", e);
return NEGATIVE_CHECK_RESULT;
}


try {
// Create an instance of HttpClient
HttpClient httpClient = HttpClient.newHttpClient();
// Create an instance of HttpPost with the desired URL
String postUrl = System.getProperty("RA_INVENTORY_URL");

HttpRequest request = HttpRequest
.newBuilder()
.uri(URI.create(postUrl))
.POST(HttpRequest.BodyPublishers.ofString(jsonString))
.header("Content-type", "application/json")
.build();

// Execute the request and obtain the response
HttpResponse<String> httpResponse =httpClient.send(request, HttpResponse.BodyHandlers.ofString());

// Parse response

// return validation result
return new CheckAndModifyResult() {
@Override
public byte[] getUpdatedCertTemplate() {
return null;
}

@Override
public boolean isGranted() {
return httpResponse.statusCode() == 200;
}
};

} catch (RuntimeException | InterruptedException | IOException e) {
LOGGER.error("inventory error while checking certificate request:", e);
return NEGATIVE_CHECK_RESULT;
}
}

}
Loading