Skip to content

Conversation

@svenska-primekey
Copy link
Collaborator

@svenska-primekey svenska-primekey commented Dec 22, 2025

EJBCA Easy REST Client - Feature Summary

This document outlines all the features and changes made to the EJBCA Easy REST Client stress testing commands.


OCSP Stress Test Command

Overview

The OCSP Stress Test Command (ocspstress) provides multi-threaded OCSP (Online Certificate Status Protocol) stress testing capabilities against OCSP responders. It enables performance testing of OCSP infrastructure by sending concurrent requests and measuring response times and certificate status distribution.

File: /Users/srajala/git/ejbca-easy-rest-client/src/main/java/com/keyfactor/ejbca/client/stress/OcspStressTestCommand.java

Features

1. Multi-threaded OCSP Requests

Sends concurrent OCSP status lookup requests using a configurable number of threads to test OCSP responder performance under load.

Usage:

ocspstress --ocspurl "http://myhost:8080/ejbca/publicweb/status/ocsp" \
  --ocspsnfile serial_numbers.txt \
  --cacertfile ca.pem \
  --threads 10 \
  --waittime 100

Parameters:

  • --ocspurl: OCSP responder URL (mandatory)
  • --ocspsnfile: Serial number file (mandatory, two formats supported)
  • --cacertfile: PEM-encoded CA certificate file (mandatory)
  • --threads: Number of concurrent threads (mandatory)
  • --waittime: Milliseconds to wait between requests per thread (mandatory)

2. Dual Serial Number File Format Support

Supports two input file formats for maximum flexibility:

Format 1: Simple serial number list

12345678
0xABCDEF123456
987654321
  • One serial number per line
  • Supports decimal or hexadecimal (with 0x prefix)
  • Comments starting with # are ignored

Format 2: Certificate info from --savecerts

1A2B3C4D5E6F|CN=Test CA,O=Keyfactor,C=US
7G8H9I0J1K2L|CN=Test CA,O=Keyfactor,C=US
  • Pipe-delimited format: serialNumber|issuerDn
  • Compatible with X509StressTestCommand --savecerts output
  • Enables seamless workflow from issuance to OCSP testing

Implementation: Lines 328-377 in loadCertificateInfo() method

3. HTTP Method Support

Supports multiple OCSP request types:

  • POST (default): Standard OCSP POST request with application/ocsp-request content type
  • GET: OCSP request encoded in URL (Base64-encoded request with proper URL encoding per RFC 6960)

Usage:

# POST request (default)
ocspstress --ocspurl "..." --ocspsnfile file.txt --cacertfile ca.pem --threads 5 --waittime 100

# GET request
ocspstress --ocspurl "..." --ocspsnfile file.txt --cacertfile ca.pem --threads 5 --waittime 100 --reqtype GET

Implementation Details:

  • Lines 514-531 in sendOcspRequest() method
  • GET requests use standard Base64 encoding with URL percent-encoding per RFC 6960 Section A.1.1
  • Percent-encodes Base64 special characters for URL safety:
    • +%2B
    • /%2F
    • =%3D
  • EJBCA's OCSP servlet decodes the URL, then converts spaces back to + before Base64 decoding
  • This encoding scheme works with both signed and unsigned OCSP requests
  • Compatible with EJBCA's URL decoding logic: URLDecoder.decode().replaceAll(" ", "+")

RFC 6960 Size Limitation:

  • Lines 115-125 in parameter registration
  • CLI help text includes warning that GET requests longer than 255 bytes after encoding SHOULD use POST instead
  • Signed OCSP requests are typically much larger than unsigned requests and may exceed this limit
  • This follows RFC 6960 Section A.1 recommendation

4. Random Certificate Selection

Each thread randomly selects certificates from the loaded serial number list, simulating realistic OCSP lookup patterns.

Implementation: Lines 224-232 in execute() method

CertificateInfo cert = certList.get(random.nextInt(certList.size()));

5. Configurable Test Duration

Control how long the stress test runs:

Unlimited duration (default):

ocspstress --ocspurl "..." --ocspsnfile file.txt --cacertfile ca.pem --threads 5 --waittime 100

Runs indefinitely until interrupted (Ctrl+C)

Fixed duration:

ocspstress --ocspurl "..." --ocspsnfile file.txt --cacertfile ca.pem --threads 5 --waittime 100 --duration 60

Runs for 60 seconds then stops automatically

Implementation: Lines 177-186 in execute() method

6. Nonce Extension Support

Adds OCSP nonce extension to requests for replay attack prevention.

Default nonce length: 32 bytes

Custom nonce length:

ocspstress --ocspurl "..." --ocspsnfile file.txt --cacertfile ca.pem --threads 5 --waittime 100 --ocspnoncelen 16

Implementation: Lines 391-410 in buildOcspRequest() method

if (nonceLength > 0) {
    byte[] nonce = new byte[nonceLength];
    new SecureRandom().nextBytes(nonce);
    Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false,
        new DEROctetString(nonce));
    builder.setRequestExtensions(new Extensions(ext));
}

7. Random Wait Time

Optionally randomize the wait time between requests to simulate more realistic load patterns.

Fixed wait time (default):

ocspstress ... --waittime 100

Waits exactly 100ms between requests

Random wait time:

ocspstress ... --waittime 100 --randomwait

Waits between 0-100ms (random) between requests

Implementation: Lines 258-264 in execute() method

int actualWait = randomWait ? random.nextInt(waitTime + 1) : waitTime;
Thread.sleep(actualWait);

8. Comprehensive Performance Metrics

The stress test collects and reports detailed performance statistics:

Response Time Metrics:

  • Minimum response time
  • Maximum response time
  • Average response time
  • P50 (median) response time
  • P95 response time
  • P99 response time

Throughput Metrics:

  • Total requests
  • Successful requests
  • Failed requests
  • Requests per second

Certificate Status Distribution:

  • GOOD count
  • REVOKED count
  • UNKNOWN count

Sample Output:

===== OCSP Stress Test Results =====
Total execution time: 60.00 seconds
Total requests: 5000
Successful requests: 4995
Failed requests: 5
Throughput: 83.25 requests/second

Response Times (ms):
  Min: 8
  Max: 156
  Avg: 12
  P50: 11
  P95: 18
  P99: 24

Certificate Status Distribution:
  GOOD: 4500
  REVOKED: 450
  UNKNOWN: 45

Implementation: Lines 526-576 in reportStatistics() method

9. Debug Request and Response Saving

Save OCSP requests and responses to disk for debugging and analysis using the --saveocsp flag.

Usage:

ocspstress --ocspurl "..." --ocspsnfile file.txt --cacertfile ca.pem \
  --threads 5 --waittime 100 --saveocsp /tmp/ocsp-debug

Behavior:

  • Saves both OCSP requests and responses to the specified directory
  • Directory is automatically created if it doesn't exist
  • Request filename format: ocsp-req-t{threadId}-r{requestNum}.der
  • Response filename format: ocsp-resp-t{threadId}-r{requestNum}.der
  • Files saved in DER (binary ASN.1) format
  • Can be analyzed with OpenSSL:
    openssl ocsp -reqin ocsp-req-t0-r0.der -text -noverify
    openssl ocsp -respin ocsp-resp-t0-r0.der -text -noverify
  • Failures are silently ignored (doesn't affect test execution)

Parameters:

  • --saveocsp <directory>: Directory path to save OCSP requests and responses (optional)

Implementation:

  • Lines 855-868: saveDebugRequest() method
  • Lines 870-886: saveDebugResponse() method
  • Lines 290-304: Directory validation and creation in execute() method

10. Real-time Progress Updates

Displays live progress updates during stress test execution showing request counts, success/failure rates, and throughput.

Usage:

# Default 5-second interval
ocspstress --ocspurl "..." --ocspsnfile file.txt --cacertfile ca.pem \
  --threads 10 --waittime 50

# Custom 10-second interval
ocspstress --ocspurl "..." --ocspsnfile file.txt --cacertfile ca.pem \
  --threads 10 --waittime 50 --progressinterval 10

# Disable progress updates
ocspstress --ocspurl "..." --ocspsnfile file.txt --cacertfile ca.pem \
  --threads 10 --waittime 50 --progressinterval 0

Behavior:

  • Progress updates display every N seconds (configurable, default 5 seconds)
  • Shows current request count, successful requests, failed requests
  • Displays current throughput (requests per second) based on interval
  • Updates are non-blocking and run on a daemon thread
  • Automatically stops when test completes or is interrupted

Sample Output:

Starting OCSP stress test with 10 threads
OCSP URL: http://localhost:8080/ocsp
Request type: POST
Wait time: 50 ms
Duration: unlimited

Progress: 834 requests (834 successful, 0 failed) - 83.40 req/s
Progress: 1671 requests (1671 successful, 0 failed) - 83.70 req/s
Progress: 2508 requests (2508 successful, 0 failed) - 83.70 req/s

Parameters:

  • --progressinterval <seconds>: Progress update interval (default: 5 seconds, 0 to disable)

Implementation:

  • Volatile counters for thread-safe tracking: totalRequestsCompleted, totalSuccessfulRequests, totalFailedRequests
  • Daemon thread periodically samples counters and calculates throughput
  • Synchronized blocks ensure thread-safe counter increments
  • Progress thread automatically terminates on test completion

11. CSV/Markdown Output Formats

Export stress test results to CSV or Markdown files for automated testing and reporting.

Usage:

# CSV output for automated testing
ocspstress --ocspurl "..." --ocspsnfile file.txt --cacertfile ca.pem \
  --threads 10 --waittime 50 --duration 60 \
  --outputformat csv --outputfile results.csv

# Markdown output for human-readable reports
ocspstress --ocspurl "..." --ocspsnfile file.txt --cacertfile ca.pem \
  --threads 10 --waittime 50 --duration 60 \
  --outputformat markdown --outputfile results.md

# Console output (default, no file created)
ocspstress --ocspurl "..." --ocspsnfile file.txt --cacertfile ca.pem \
  --threads 10 --waittime 50 --duration 60

CSV Format:

  • Single header row with column names
  • Single data row with test results
  • Includes timestamp for each test run
  • Easy to parse and import into spreadsheets or automated systems
  • Columns: Test Duration, Total Requests, Successful, Failed, Throughput, Response Times (Min/Max/Avg/P50/P95/P99), Certificate Status Counts

Sample CSV:

Test Duration (s),Total Requests,Successful,Failed,Throughput (req/s),Min Response (ms),Max Response (ms),Avg Response (ms),P50 (ms),P95 (ms),P99 (ms),GOOD,REVOKED,UNKNOWN,Timestamp
60.00,5000,4995,5,83.25,8,156,12,11,18,24,4500,450,45,2025-12-31 14:30:00

Markdown Format:

  • Human-readable formatted tables
  • Includes test configuration, performance metrics, response times, and status distribution
  • Perfect for embedding in documentation or sharing with stakeholders
  • Includes timestamp and all test parameters

Sample Markdown:

# OCSP Stress Test Results

**Generated:** 2025-12-31 14:30:00

## Test Configuration

| Parameter | Value |
|-----------|-------|
| OCSP URL | http://localhost:8080/ocsp |
| Request Type | POST |
| Threads | 10 |
| Wait Time | 50 ms |

## Performance Metrics

| Metric | Value |
|--------|-------|
| Total Requests | 5000 |
| Successful | 4995 |
| Failed | 5 |
| Throughput | 83.25 req/s |

## Response Times

| Metric | Time (ms) |
|--------|-----------|
| Min | 8 |
| Max | 156 |
| Avg | 12 |
| P50 | 11 |
| P95 | 18 |
| P99 | 24 |

## Certificate Status Distribution

| Status | Count |
|--------|-------|
| GOOD | 4500 |
| REVOKED | 450 |
| UNKNOWN | 45 |

Parameters:

  • --outputformat <format>: Output format - 'console' (default), 'csv', or 'markdown'
  • --outputfile <filename>: File path to save results (required when format is csv or markdown)

Implementation:

  • writeResultsToCsv() method generates CSV output
  • writeResultsToMarkdown() method generates Markdown output with formatted tables
  • Both methods use try-with-resources for safe file handling
  • Validation ensures output file is specified when format requires it
  • All statistics from console output are included in file outputs

12. HTTPS with Client Authentication

Supports mutual TLS authentication via inherited base class functionality.

Usage:

ocspstress --authkeystore client.p12 --authkeystorepass password123 \
  --hostname secure-ocsp.example.com:8443 \
  --ocspurl "https://secure-ocsp.example.com:8443/ocsp" \
  --ocspsnfile file.txt --cacertfile ca.pem --threads 5 --waittime 100

Inherited from ErceCommandBase: SSL context creation with mutual TLS support

Code Organization

Class Structure

ErceCommandBase (existing base class)
  └── OcspStressTestCommand (new class)

Inner Classes (Lines 113-138)

  • CertificateInfo: Stores serial number and issuer DN
  • OcspTestResult: Aggregates failures, status counts, and response times per thread

Key Methods

  • loadCertificateInfo() (lines 328-377): Parse serial number files
  • loadCaCertificate() (lines 382-396): Load CA certificate from PEM
  • buildOcspRequest() (lines 391-410): Generate OCSP request with nonce
  • sendOcspRequest() (lines 416-445): Send HTTP POST/GET request
  • parseOcspResponse() (lines 450-478): Parse OCSP response and extract status
  • saveDebugRequest() (lines 506-520): Save requests for debugging
  • reportStatistics() (lines 526-576): Generate performance report

Required Libraries

  • Bouncy Castle OCSP: org.bouncycastle.cert.ocsp.*
  • Apache HttpComponents: HTTP client for POST/GET requests
  • Java Concurrency: CompletableFuture for multi-threading

Usage Examples

Example 1: Basic OCSP Stress Test

ocspstress --ocspurl "http://localhost:8080/ejbca/publicweb/status/ocsp" \
  --ocspsnfile serials.txt \
  --cacertfile myca.pem \
  --threads 10 \
  --waittime 50

Runs indefinitely with 10 threads, 50ms wait between requests

Example 2: Time-Limited Test with Metrics

ocspstress --ocspurl "http://localhost:8080/ejbca/publicweb/status/ocsp" \
  --ocspsnfile serials.txt \
  --cacertfile myca.pem \
  --threads 20 \
  --waittime 100 \
  --duration 300 \
  --randomwait

Runs for 5 minutes with 20 threads and random wait times

Example 3: HTTPS with Client Auth and Debug

mkdir ./ocsp-requests
ocspstress --authkeystore client.p12 --authkeystorepass secret \
  --hostname secure-ocsp.example.com:8443 \
  --ocspurl "https://secure-ocsp.example.com:8443/ocsp" \
  --ocspsnfile issued_certs.txt \
  --cacertfile ca.pem \
  --threads 5 \
  --waittime 200 \
  --duration 60 \
  --ocspnoncelen 16

Uses client certificate auth, saves debug requests, custom nonce length

Example 4: Integration with X509 Stress Test

# Step 1: Issue certificates and save for OCSP testing
stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100 --savecerts issued_certs.txt

# Step 2: Run OCSP stress test on issued certificates
ocspstress --ocspurl "http://localhost:8080/ejbca/publicweb/status/ocsp" \
  --ocspsnfile issued_certs.txt \
  --cacertfile myca.pem \
  --threads 10 \
  --waittime 50

Seamless workflow from certificate issuance to OCSP testing

Build Status

Successfully compiled and tested


X509 Stress Test Command

This document outlines all the features and changes made to the X509StressTestCommand for EJBCA stress testing.

Features Added

1. Certificate Revocation After Issuance (--revoke)

Line References: 107, 158-161, 213-214, 351-360, 686-741

Automatically revokes certificates immediately after successful issuance to test revocation performance and certificate lifecycle.

Usage:

stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100 --revoke

Behavior:

  • After each successful certificate issuance (200/201 response), the certificate is automatically revoked
  • Parses the issued certificate from the JSON response
  • Extracts serial number and issuer DN from the certificate
  • Calls the EJBCA revoke API with reason "UNSPECIFIED" using current timestamp
  • Reports revocation failures separately in the error log
  • Useful for testing complete certificate lifecycle and revocation throughput

Implementation:

  • revokeCertificate() method at lines 711-769 handles the revocation
  • Uses HTTP PUT to /ejbca/ejbca-rest-api/v1/certificate/{issuer_dn}/{serial_number}/revoke?reason=UNSPECIFIED
  • Without --backdaterevoke: URL does NOT include date parameter (current time used by server)
  • With --backdaterevoke: URL includes &date={notBefore} parameter
  • escapeInvalidUrlCharacters() helper method at lines 771-773 for URL encoding
  • Revocation errors are tracked separately from issuance errors
  • Statistics report both issuance and revocation success/failure counts separately
  • Revocation errors don't affect issuance success count

1a. Backdated Revocation (--backdaterevoke)

Line References: 107, 160-161, 214, 354, 686-741

Allows backdating certificate revocation to the certificate's validity start date (notBefore) instead of using the current time. Must be used in conjunction with --revoke flag.

Usage:

# Revoke using certificate's notBefore date
stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100 --revoke --backdaterevoke

Behavior:

  • When --backdaterevoke is set along with --revoke, revocation uses the certificate's notBefore date
  • Useful for testing certificate profiles that allow backdated revocation
  • Without --backdaterevoke, revocation uses current timestamp (default behavior)
  • Prevents 422 errors for certificate profiles that don't permit backdated revocation

Implementation:

  • backdateRevocation flag parsed at line 178
  • Passed to revokeCertificate() method at line 286
  • Method conditionally adds &date= parameter to URL only when flag is true (lines 729-734)
  • Uses certificate.getNotBefore() and converts to OffsetDateTime with UTC timezone (line 731)
  • StressTestResult inner class (lines 117-125) tracks issuance and revocation failures separately

URL Format:

# Without --backdaterevoke (default)
/revoke?reason=UNSPECIFIED

# With --backdaterevoke
/revoke?reason=UNSPECIFIED&date=2025-12-30T10:30:00Z

Error Prevention:
Without --backdaterevoke (default):

Revocation uses current time - works with all certificate profiles

With --backdaterevoke:

Revocation uses certificate's notBefore date - requires certificate profile to allow backdated revocation
Prevents: "Back dated revocation not allowed for certificate profile"

2. Custom Subject DN Support (--subjectdn)

Line References: 83, 126-127, 147-152, 386-410

Allows specifying a custom Subject DN for certificates instead of using the default CN=<prefix>_<threadId>_<certId>_<postfix>.

Usage:

stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100 --subjectdn "CN=TestUser,O=Keyfactor,C=US"

Behavior:

  • If a CN attribute is present in the provided DN, the prefix, threadId, certId, and postfix are applied to it
  • Example: CN=Test,O=Org becomes CN=ErceStressTest__Test_0_0,O=Org (for first cert)
  • Helper method applyPrefixPostfixToCN() at lines 474-497 handles the transformation

3. Subject Alternative Name (SAN) Support (--san)

Line References: 84, 128-129, 154-159, 389-411, 444-449

Adds Subject Alternative Name extension to certificates with support for DNS names, IP addresses, and other SAN types.

Usage:

# DNS name only
stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100 --san "dnsName=example.com"

# Multiple SAN values
stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100 --san "dnsName=example.com,ipAddress=192.168.1.1"

Behavior:

  • DNS names get prefix/postfix applied: dnsName=example.com becomes dnsName=ErceStressTest__example.com_0_0
  • Other SAN types (ipAddress, email, URI) remain unchanged
  • Helper method applyPrefixPostfixToSAN() at lines 499-523 handles the transformation
  • SAN extension added via generateCertificateRequest() at lines 444-449

4. Empty Subject DN with SAN Only

Line References: 388-399, 412, 422-423

When only --san is provided without --subjectdn, the CSR is created with an empty subject DN.

Usage:

stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100 --san "dnsName=example.com"

Behavior:

  • CSR contains empty subject DN: new X500Name("")
  • Only the SAN extension is present in the certificate
  • Useful for testing SAN-only certificates

5. Certificate History Testing (--history)

Line References: 85, 130-131, 161-175, 229, 243-244, 311-325, 364-372, 413-445

Generates multiple certificates per end entity with unique keys to test certificate renewal and history in EJBCA.

Usage:

# Generate 3 certificates per end entity (1 base + 2 additional)
stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100 --history 2

Behavior:

  • Each end entity receives 1 + historyCount certificates
  • Each certificate uses a unique key pair (enforces EJBCA's unique public key requirement)
  • All certificates for the same end entity share the same username and subject DN
  • Total certificates = numberOfThreads × requestPerThread × (1 + historyCount)
  • Example: 10 threads × 100 certs × 3 = 3000 total certificates for 1000 end entities

Statistics Output:

Total execution time: 45.2 seconds.
Average issuance time: 0.015 seconds.
Throughput: 66.37 certificates issued per second.
3000 certificates were successfully issued, with 0 failures.
Total end entities: 1000 (3 certificate(s) per entity).

6. Enhanced Error Reporting with CN

Line References: 91-92, 255-256, 266, 275, 369, 376, 426, 443, 525-542

Error messages now include the Common Name (CN) from the subject DN to easily identify failed enrollments.

Before:

Thread ID: 9, Iteration: 9 - Return code was: 400: {"error_code":400,"error_message":"Subject DN field 'ORGANIZATIONALUNIT' must exist."}

After:

Thread ID: 9, Iteration: 9, CN=ErceStressTest__TestUser_9_9 - Return code was: 400: {"error_code":400,"error_message":"Subject DN field 'ORGANIZATIONALUNIT' must exist."}

Implementation:

  • subjectDns array stores DN for each certificate (line 92, 376, 443)
  • extractCNForErrorMessage() method extracts CN from DN (lines 525-542)
  • CN info included in error messages at lines 266 and 275

Technical Implementation Details

Data Structures

private String[][] payloads;      // Line 91 - Stores JSON payloads for each certificate
private String[][] subjectDns;    // Line 92 - Stores subject DNs for error reporting

Key Methods

generatePayloads() - Lines 362-456

  • Calculates total certificates per entity: certsPerEntity = 1 + historyCount
  • Creates payload arrays sized for history: requestPerThread × certsPerEntity
  • Nested loop generates multiple certificates per end entity
  • Each certificate gets unique key pair (lines 415-421)

generateCertificateRequest() - Lines 458-472

  • Accepts subject DN, key pair, algorithm, and SAN
  • Adds SAN extension if provided (lines 444-449)
  • Returns PKCS#10 CSR with extensions

applyPrefixPostfixToCN() - Lines 474-497

  • Parses subject DN string
  • Finds CN attribute and applies transformations
  • Format: CN=<prefix>_<value>_<threadId>_<certId>_<postfix>

applyPrefixPostfixToSAN() - Lines 499-523

  • Parses SAN string by comma delimiter
  • Applies prefix/postfix only to dnsName attributes
  • Preserves other SAN types unchanged

extractCNForErrorMessage() - Lines 525-542

  • Extracts CN from subject DN
  • Returns formatted string: , CN=value
  • Returns empty string if no CN found

Execution Flow

  1. Parameter Parsing (lines 136-175): Parse CLI arguments including history count
  2. Payload Generation (line 229): Generate CSRs with unique keys per certificate
  3. Parallel Execution (lines 246-290): Submit CSRs across threads
  4. Statistics Calculation (lines 311-325): Calculate success/failure rates with history awareness

Combined Usage Examples

Example 1: Full Feature Set

stress --ca "MyCA" \
  --certificateprofile "ENDUSER" \
  --endentityprofile "MyProfile" \
  --threads 5 \
  --certs 50 \
  --subjectdn "CN=User,OU=Engineering,O=Keyfactor,C=US" \
  --san "dnsName=user.example.com,ipAddress=10.0.0.1" \
  --history 4 \
  --prefix "Test" \
  --postfix "v1"

Result:

  • 250 end entities (5 threads × 50 certs)
  • 1250 total certificates (250 entities × 5 certs per entity)
  • Subject DN: CN=Test_User_0_0_v1,OU=Engineering,O=Keyfactor,C=US
  • SAN: dnsName=Test_user.example.com_0_0_v1,ipAddress=10.0.0.1

Example 2: SAN-Only Certificates

stress --ca "MyCA" \
  --certificateprofile "ENDUSER" \
  --endentityprofile "MyProfile" \
  --threads 2 \
  --certs 10 \
  --san "dnsName=api.example.com"

Result:

  • 20 end entities with empty subject DN
  • Only SAN extension present
  • SAN: dnsName=ErceStressTest__api.example.com_0_0

Example 3: History Testing

stress --ca "MyCA" \
  --certificateprofile "ENDUSER" \
  --endentityprofile "MyProfile" \
  --threads 10 \
  --certs 100 \
  --history 2

Result:

  • 1000 end entities
  • 3000 total certificates (3 per entity)
  • Each entity has 3 certificates with unique keys
  • Perfect for testing certificate renewal workflows

Example 4: Revocation Testing

stress --ca "MyCA" \
  --certificateprofile "ENDUSER" \
  --endentityprofile "MyProfile" \
  --threads 10 \
  --certs 100 \
  --revoke

Result:

  • 1000 certificates issued
  • Each certificate is immediately revoked after issuance using current timestamp
  • Tests complete certificate lifecycle (issuance + revocation)
  • Measures both issuance and revocation throughput
  • Statistics separately report issuance success/failures and revocation success/failures

Example 4a: Backdated Revocation Testing

stress --ca "MyCA" \
  --certificateprofile "ENDUSER" \
  --endentityprofile "MyProfile" \
  --threads 10 \
  --certs 100 \
  --revoke \
  --backdaterevoke

Result:

  • 1000 certificates issued
  • Each certificate is revoked using its notBefore date (backdated revocation)
  • Useful for testing certificate profiles that allow backdated revocation
  • Prevents 422 errors for profiles that don't allow current-time revocation
  • Statistics separately track issuance and revocation operations

Sample Output:

Total execution time: 56.0 seconds.
40 certificates were successfully issued, with 0 issuance failures.
40 certificates were successfully revoked, with 0 revocation failures.
Total end entities: 10 (4 certificate(s) per entity).

6. Certificate Tracking and Bulk Revocation (--savecerts and --revokefile)

Line References: 108-109, 135-159, 207-210, 264-265, 349-351, 372, 397, 446, 491-494, 816-1001

Enables tracking of all issued certificates and performing mass revocation from a saved certificate list without enrollment.

Usage - Save certificates during issuance:

stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100 --savecerts issued_certs.txt

Usage - Bulk revocation from saved file:

stress --revokefile issued_certs.txt --threads 5

Behavior:

  • --savecerts <filename>: Saves serial number and issuer DN of all successfully issued certificates to file
  • File format: Simple pipe-delimited format (serialNumber|issuerDn)
  • Certificate tracking happens for ALL successful issuances, regardless of --revoke flag
  • --revokefile <filename>: Performs bulk revocation from previously saved certificate list
  • When --revokefile is used, NO enrollment occurs - only revocation
  • Bulk revocation uses parallel threads for performance (configurable via --threads)
  • --backdaterevoke can be used with --revokefile (uses current time as revocation date)

Implementation:

  • CertificateInfo inner class (lines 135-159) stores serial number and issuer DN
  • toString() method formats to serialNumber|issuerDn
  • fromString() method parses from file format
  • StressTestResult modified (line 133) to include List<CertificateInfo> issuedCertificates
  • Certificate tracking added to issuance loop (lines 397)
  • saveCertificatesToFile() method (lines 824-838) writes certificates to file
  • loadCertificatesFromFile() method (lines 843-863) reads certificates from file
  • performBulkRevocation() method (lines 868-1001) handles bulk revocation mode
  • Certificates saved after statistics are printed (lines 491-494)
  • Bulk revocation mode check at line 349-351

File Format Example:

1A2B3C4D5E6F|CN=Test CA,O=Keyfactor,C=US
7G8H9I0J1K2L|CN=Test CA,O=Keyfactor,C=US
3M4N5O6P7Q8R|CN=Test CA,O=Keyfactor,C=US

Statistics Output (Bulk Revocation Mode):

Starting bulk revocation from file: issued_certs.txt
Loaded 1000 certificate(s) for revocation
Using 5 thread(s) for parallel revocation

Weapons free. Fire for effect (revocation only).
Fire mission complete. Weapons hold.

Total execution time: 12.5 seconds.
Average revocation time: 0.0125 seconds.
Throughput: 80.0 certificates revoked per second.
1000 certificates were successfully revoked, with 0 revocation failures.

Example 4b: Save Certificates with Immediate Revocation

stress --ca "MyCA" \
  --certificateprofile "ENDUSER" \
  --endentityprofile "MyProfile" \
  --threads 10 \
  --certs 100 \
  --revoke \
  --savecerts issued_certs.txt

Result:

  • 1000 certificates issued
  • Each certificate is immediately revoked after issuance
  • Certificate information saved to issued_certs.txt for later reference
  • Useful for testing complete lifecycle and maintaining audit trail

Example 4c: Bulk Revocation Only

stress --revokefile issued_certs.txt --threads 10

Result:

  • No certificates issued (enrollment skipped entirely)
  • All certificates from file are revoked in parallel using 10 threads
  • Useful for mass revocation scenarios or cleanup after testing
  • Can re-run multiple times on the same file (already-revoked certs will show as failures)

Example 5: Combined Features

stress --ca "MyCA" \
  --certificateprofile "ENDUSER" \
  --endentityprofile "MyProfile" \
  --threads 5 \
  --certs 50 \
  --subjectdn "CN=User,OU=Engineering,O=Keyfactor,C=US" \
  --san "dnsName=user.example.com" \
  --history 2 \
  --revoke \
  --savecerts test_certs.txt

Result:

  • 250 end entities
  • 750 total certificates (3 per entity: 1 base + 2 history)
  • Each certificate is revoked immediately after issuance
  • All certificate information saved to test_certs.txt
  • Tests complete lifecycle with history, revocation, and certificate tracking

7. Real-time Progress Updates

Displays live progress updates during stress test execution showing certificate counts, success/failure rates, and throughput.

Usage:

# Default 5-second interval
stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100

# Custom 10-second interval
stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100 --progressinterval 10

# Disable progress updates
stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100 --progressinterval 0

Behavior:

  • Progress updates display every N seconds (configurable, default 5 seconds)
  • Shows current certificate count, successful issuances, failed issuances
  • When --revoke is used, also shows revocation counts (successful/failed)
  • Displays current throughput (certificates per second) based on interval
  • Shows progress as current/expected with percentage
  • Updates are non-blocking and run on a daemon thread
  • Works in both normal issuance mode and bulk revocation mode
  • Automatically stops when test completes

Sample Output (Normal Mode):

Weapons free. Fire for effect (enrollment + revocation).

Progress: 834/1000 certs (834 successful, 0 failed) - 83.40 certs/s
Progress: 1000/1000 certs (1000 successful, 0 failed) - 83.00 certs/s

Fire mission complete. Weapons hold.

Sample Output (With Revocation):

Weapons free. Fire for effect (enrollment + revocation).

Progress: 500/1000 certs (500 successful, 0 failed), 500 revoked (500 successful, 0 failed) - 50.00 certs/s
Progress: 1000/1000 certs (1000 successful, 0 failed), 1000 revoked (1000 successful, 0 failed) - 50.00 certs/s

Fire mission complete. Weapons hold.

Sample Output (Bulk Revocation Mode):

Starting bulk revocation from file: issued_certs.txt
Loaded 1000 certificate(s) for revocation

Progress: 450/1000 revocations (450 successful, 0 failed) - 90.00 rev/s
Progress: 1000/1000 revocations (1000 successful, 0 failed) - 90.00 rev/s

Parameters:

  • --progressinterval <seconds>: Progress update interval (default: 5 seconds, 0 to disable)

Implementation:

  • Volatile counters for thread-safe tracking in normal mode: totalIssuanceAttempts, totalSuccessfulIssuances, totalFailedIssuances, totalSuccessfulRevocations, totalFailedRevocations
  • Separate daemon threads for normal mode and bulk revocation mode
  • Synchronized blocks ensure thread-safe counter increments
  • Progress thread automatically terminates on test completion
  • Expected total calculated based on threads, certs per thread, and history count

8. CSV/Markdown Output Formats

Export stress test results to CSV or Markdown files for automated testing and reporting.

Usage:

# CSV output for automated testing (normal mode)
stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100 \
  --outputformat csv --outputfile results.csv

# Markdown output for human-readable reports (normal mode)
stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100 --revoke \
  --outputformat markdown --outputfile results.md

# CSV output for bulk revocation mode
stress --revokefile issued_certs.txt --threads 10 \
  --outputformat csv --outputfile revocation_results.csv

# Console output (default, no file created)
stress --ca "MyCA" --certificateprofile "ENDUSER" --endentityprofile "MyProfile" \
  --threads 10 --certs 100

CSV Format (Issuance Mode):

  • Single header row with column names
  • Single data row with test results
  • Includes timestamp for each test run
  • Easy to parse and import into spreadsheets or automated systems
  • Columns: Test Duration, Total Certs, Successful Issuances, Failed Issuances, Successful Revocations, Failed Revocations, Throughput, End Entities, Certs per Entity

Sample CSV (Issuance Mode):

Test Duration (s),Total Certs,Successful Issuances,Failed Issuances,Successful Revocations,Failed Revocations,Throughput (certs/s),End Entities,Certs per Entity,Timestamp
45.20,3000,3000,0,3000,0,66.37,1000,3,2025-12-31 14:30:00

CSV Format (Bulk Revocation Mode):

Test Duration (s),Total Revocations,Successful,Failed,Throughput (rev/s),Timestamp
12.50,1000,1000,0,80.00,2025-12-31 14:35:00

Markdown Format (Issuance Mode):

  • Human-readable formatted tables
  • Includes test configuration, performance metrics, and detailed results
  • Perfect for embedding in documentation or sharing with stakeholders
  • Includes timestamp and all test parameters
  • Shows revocation statistics when --revoke is used

Sample Markdown (Issuance Mode):

# X509 Stress Test Results

**Generated:** 2025-12-31 14:30:00

## Test Configuration

| Parameter | Value |
|-----------|-------|
| CA | MyCA |
| Certificate Profile | ENDUSER |
| End Entity Profile | MyProfile |
| Threads | 10 |
| Certificates per Thread | 100 |
| History Count | 2 |
| Revoke After Issuance | Yes |

## Performance Metrics

| Metric | Value |
|--------|-------|
| Total Execution Time | 45.20 s |
| Average Issuance Time | 0.015 s |
| Throughput | 66.37 certs/s |

## Issuance Results

| Metric | Count |
|--------|-------|
| Successful Issuances | 3000 |
| Failed Issuances | 0 |
| Total End Entities | 1000 |
| Certificates per Entity | 3 |

## Revocation Results

| Metric | Count |
|--------|-------|
| Successful Revocations | 3000 |
| Failed Revocations | 0 |

Markdown Format (Bulk Revocation Mode):

# Bulk Revocation Results

**Generated:** 2025-12-31 14:35:00

## Test Configuration

| Parameter | Value |
|-----------|-------|
| Revocation File | issued_certs.txt |
| Threads | 10 |

## Performance Metrics

| Metric | Value |
|--------|-------|
| Total Execution Time | 12.50 s |
| Average Revocation Time | 0.0125 s |
| Throughput | 80.00 rev/s |

## Revocation Results

| Metric | Count |
|--------|-------|
| Total Revocations | 1000 |
| Successful | 1000 |
| Failed | 0 |

Parameters:

  • --outputformat <format>: Output format - 'console' (default), 'csv', or 'markdown'
  • --outputfile <filename>: File path to save results (required when format is csv or markdown)

Implementation:

  • writeIssuanceResultsToCsv() method generates CSV output for issuance mode
  • writeIssuanceResultsToMarkdown() method generates Markdown output for issuance mode
  • writeRevocationResultsToCsv() method generates CSV output for bulk revocation mode
  • writeRevocationResultsToMarkdown() method generates Markdown output for bulk revocation mode
  • Both normal and bulk revocation modes support CSV and Markdown output
  • All methods use try-with-resources for safe file handling
  • Validation ensures output file is specified when format requires it
  • All statistics from console output are included in file outputs

Code Organization

Imports (Lines 15-18, 41-49)

File I/O for certificate tracking:

  • java.io.BufferedReader, BufferedWriter, FileReader, FileWriter

BouncyCastle ASN.1 classes for SAN extension support:

  • ASN1EncodableVector, DERSequence, DERSet
  • Extension, Extensions, ExtensionsGenerator
  • GeneralNames

Constants (Lines 78-85, 108-109)

All CLI argument definitions including:

  • SAVECERTS_ARG = "--savecerts"
  • REVOKEFILE_ARG = "--revokefile"

Inner Classes (Lines 123-159)

  • StressTestResult (lines 123-133): Aggregates issuance failures, revocation failures, and issued certificates
  • CertificateInfo (lines 135-159): Stores and serializes certificate serial number and issuer DN

Helper Methods (Lines 474-542, 816-863)

  • applyPrefixPostfixToCN() (lines 474-497)
  • applyPrefixPostfixToSAN() (lines 499-523)
  • extractCNForErrorMessage() (lines 525-542)
  • saveCertificatesToFile() (lines 824-838)
  • loadCertificatesFromFile() (lines 843-863)
  • performBulkRevocation() (lines 868-1001)

Main Logic (Lines 362-456)

Payload generation with history support


Build Status

All features compile successfully with Gradle

BUILD SUCCESSFUL in 7s
4 actionable tasks: 2 executed, 2 up-to-date

@svenska-primekey svenska-primekey linked an issue Dec 31, 2025 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants