Code Signing Certificates¶
Stentor supports Authenticode code signing for generated payloads. Code signing certificates are uploaded as PFX (PKCS#12) files, stored with encrypted passwords in the database, and used to sign payloads via the relay's osslsigncode tool. Signed payloads carry a valid Authenticode signature that satisfies Windows SmartScreen, application whitelisting policies, and user trust prompts.
Architecture¶
sequenceDiagram
participant Operator as Operator
participant API as Stentor API
participant DB as PostgreSQL
participant Relay as Relay (Kali)
Note over Operator,DB: Certificate Upload
Operator->>API: POST /certificates (multipart PFX + password)
API->>API: Parse PFX, extract metadata
API->>API: Encrypt password (AES-256-GCM)
API->>DB: Store PFX + encrypted password + metadata
API-->>Operator: CertificateResponse (no sensitive fields)
Note over Operator,Relay: Payload Signing
Operator->>API: POST /sign {payload_id, certificate_id}
API->>DB: Load payload binary + certificate PFX
API->>API: Decrypt certificate password
API->>Relay: sign_payload command (PFX + password + payload)
Relay->>Relay: osslsigncode sign
Relay-->>API: Signed payload bytes
API-->>Operator: {success, signed_size} Certificate Management¶
Upload a Certificate¶
POST /api/v1/certificates¶
Upload a PFX (PKCS#12) file containing a code signing certificate and private key. The API parses the PFX to extract certificate metadata (subject, issuer, validity period), encrypts the password with AES-256-GCM, and stores everything in the database.
Form fields:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Human-readable name for the certificate |
description | string | No | Optional description |
pfx_file | file | Yes | PFX (PKCS#12) file containing certificate and private key |
password | string | Yes | Password protecting the PFX file |
Size limit: 1 MB maximum for the PFX file.
curl -s -X POST https://stentor.app/api/v1/certificates \
-H "Authorization: Bearer $TOKEN" \
-F "name=Contoso Code Signing" \
-F "description=EV code signing cert for payload signing" \
-F "pfx_file=@/path/to/certificate.pfx" \
-F "password=pfx-password-here"
Response (201 Created):
{
"id": "b2c3d4e5-...",
"name": "Contoso Code Signing",
"description": "EV code signing cert for payload signing",
"subject": "CN=Contoso Ltd, O=Contoso Ltd, L=Redmond, ST=Washington, C=US",
"issuer": "CN=DigiCert SHA2 Extended Validation Code Signing CA, OU=www.digicert.com, O=DigiCert Inc, C=US",
"valid_from": "2025-01-15T00:00:00Z",
"valid_until": "2027-01-14T23:59:59Z",
"created_by": "USER_UUID",
"created_at": "2026-02-21T10:00:00Z"
}
PFX Validation
The API decodes the PFX using Go's pkcs12.Decode to verify the file is valid and the password is correct before storing. Invalid PFX files or incorrect passwords return a 400 Bad Request error with a descriptive message.
List Certificates¶
GET /api/v1/certificates¶
List all stored certificates. Sensitive fields (PFX binary data, encrypted password) are excluded from the response. Certificates are returned in reverse chronological order.
Response fields:
| Field | Type | Description |
|---|---|---|
id | string | Certificate UUID |
name | string | Certificate name |
description | string | Optional description |
subject | string | Certificate subject DN |
issuer | string | Certificate issuer DN |
valid_from | string | Validity start (ISO 8601) |
valid_until | string | Validity end (ISO 8601) |
created_by | string | UUID of the user who uploaded |
created_at | string | Upload timestamp (ISO 8601) |
Get Certificate¶
GET /api/v1/certificates/:id¶
Retrieve a single certificate by ID. Same response format as the list endpoint (no sensitive fields).
Delete Certificate¶
DELETE /api/v1/certificates/:id¶
Delete a certificate and all associated data (PFX binary, encrypted password) from the database.
curl -s -X DELETE https://stentor.app/api/v1/certificates/CERT_UUID \
-H "Authorization: Bearer $TOKEN"
Response: 204 No Content on success.
Signing Payloads¶
POST /api/v1/sign¶
Sign a generated payload with a stored code signing certificate. The signing operation is performed on the relay using osslsigncode, which supports Authenticode signatures for PE (EXE/DLL) files.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
payload_id | string (UUID) | Yes | UUID of the payload to sign |
certificate_id | string (UUID) | Yes | UUID of the code signing certificate |
description | string | No | Authenticode signature description (appears in file properties) |
url | string | No | URL embedded in the Authenticode signature |
timestamp_url | string | No | RFC 3161 timestamp server URL for countersigning |
Timestamp Countersigning
Providing a timestamp_url adds a trusted timestamp to the signature, which ensures the signature remains valid even after the certificate expires. Common timestamp servers: http://timestamp.digicert.com, http://timestamp.sectigo.com.
curl -s -X POST https://stentor.app/api/v1/sign \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"payload_id": "PAYLOAD_UUID",
"certificate_id": "CERT_UUID",
"description": "Contoso Updater Service",
"url": "https://contoso.com",
"timestamp_url": "http://timestamp.digicert.com"
}'
Response (200 OK):
Error responses:
| Status | Condition |
|---|---|
400 Bad Request | Missing required fields or invalid UUIDs |
404 Not Found | Payload or certificate not found |
503 Service Unavailable | No relay connected (signing requires relay) |
504 Gateway Timeout | Signing operation timed out (60 second limit) |
500 Internal Server Error | Signing failed on relay (error message included) |
Signing Flow¶
- The API validates the request and loads the payload binary and certificate PFX from the database.
- The certificate password is decrypted from AES-256-GCM storage.
- A
sign_payloadcommand is sent to the first connected relay with the payload bytes, PFX data, decrypted password, and signing options. - The relay executes
osslsigncodeto apply the Authenticode signature. - The signed payload bytes are returned to the API via a correlation-based request/response channel.
- The API returns the result to the operator.
Relay Requirement
Signing requires at least one connected relay. The osslsigncode tool runs on the Kali relay -- the Stentor API server does not perform signing directly. If no relay is connected, the endpoint returns 503 Service Unavailable.
Encrypted Storage¶
Certificate passwords are encrypted at rest using AES-256-GCM with a 32-byte server-side key configured via the C2_CERTIFICATE_ENCRYPTION_KEY environment variable.
| Component | Storage | Encryption |
|---|---|---|
| PFX binary data | certificates.pfx_data (BYTEA) | None (raw bytes in database) |
| PFX password | certificates.password_encrypted (BYTEA) | AES-256-GCM (12-byte nonce prepended) |
| Certificate metadata | certificates.* | None (plaintext -- not sensitive) |
The encryption scheme uses a random 12-byte nonce for each encryption operation. The nonce is prepended to the ciphertext, so the stored value is [12-byte nonce][ciphertext + GCM tag].
Key rotation is supported via the MigrateEncryptedPassword utility, which decrypts with the old key and re-encrypts with a new key using a fresh nonce.
Encryption Key Management
The C2_CERTIFICATE_ENCRYPTION_KEY must be exactly 32 bytes (256 bits). If the key is lost or changed without migrating existing passwords, all stored certificates become unusable (the PFX data is intact but the password cannot be decrypted). Store the key securely and include it in your backup procedures.
OPSEC Considerations¶
Detection Surface
Code-signed payloads are significantly more likely to bypass initial execution controls, but the certificate itself becomes an indicator of compromise (IOC) if the payload is captured and analyzed.
- MITRE ATT&CK: T1553.002 (Subvert Trust Controls: Code Signing)
| Concern | Details |
|---|---|
| SmartScreen bypass | Signed EXEs bypass the "Windows protected your PC" SmartScreen warning. EV certificates provide immediate reputation. Standard certificates build reputation over time. |
| Application whitelisting | Some application whitelisting policies allow execution of signed binaries. Signing with a trusted publisher certificate can bypass these controls. |
| Certificate as IOC | If a signed payload is captured, the certificate's serial number, subject, and thumbprint become IOCs. Defenders can block execution of all binaries signed with that certificate. |
| Certificate revocation | A compromised or detected certificate may be reported to the CA for revocation. Check CRL/OCSP status before relying on a certificate for ongoing operations. |
| Timestamp persistence | Countersigned payloads remain trusted even after the certificate expires or is revoked (depending on the revocation reason). Always use a timestamp server for long-running engagements. |
Certificate Sources
For red team engagements, code signing certificates can be obtained from:
- Legitimate purchase: Buy a standard or EV code signing certificate from a public CA. Provides the strongest trust signal but creates a paper trail.
- Self-signed: Generate a self-signed certificate for testing. Does not bypass SmartScreen but satisfies some application whitelisting policies that only check for a valid signature chain.
- Expired/stolen: Certificates found during post-exploitation can be extracted and used for signing. Time-stamp countersigning extends usability beyond expiration.