Skip to content

Commit a86b3af

Browse files
committed
docs: add cross-plugin analysis, certinext improvement plan, and API findings from sandbox exploration
1 parent 2ec5914 commit a86b3af

1 file changed

Lines changed: 346 additions & 0 deletions

File tree

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
# CERTInext API Findings — Postman Collection + Live Sandbox Exploration
2+
3+
Generated: 2026-04-22. Updated: 2026-04-22 (product management probe, IGTF order test, Private PKI auto-issuance investigation). Source: `~/Downloads/CERTInext APIs.postman_collection.json` + live calls against sandbox account `9374221333`.
4+
5+
---
6+
7+
## Product Codes Are Global Per Environment, Not Per-Account
8+
9+
Product codes are the same for all accounts within the same environment. The Postman collection is the authoritative reference.
10+
11+
### Sandbox Product Codes
12+
13+
| Product Name | Code |
14+
|---|---|
15+
| DV SSL Certificate 1 Year | **842** |
16+
| DV SSL Certificate Wildcard 1 Year | **843** |
17+
| DV SSL Certificate UCC 1 Year | **844** |
18+
| DV SSL Certificate Wildcard UCC 1 Year | **845** |
19+
| OV SSL Certificate 1 Year | **846** |
20+
| OV SSL Certificate Wildcard 1 Year | **847** |
21+
| OV SSL Certificate UCC 1 Year | **848** |
22+
| OV SSL Certificate Wildcard UCC 1 Year | **849** |
23+
| EV SSL Certificate 1 Year | **850** |
24+
| EV SSL Certificate UCC 1 Year | **851** |
25+
| emSign Intranet SSL 1 Year (Private PKI) | **104** |
26+
| IGTF Host Certificate 1 Year | **108** |
27+
| emSign S/MIME Simple MV-S 1 Year | **914** |
28+
| emSign Natural Person NonRepudiation 1/2/3 Year | **825 / 826 / 827** |
29+
| emSign Legal Person NonRepudiation 1/2/3 Year | **822 / 823 / 824** |
30+
| emSign Legal Entity NonRepudiation 1/2/3 Year | **819 / 820 / 821** |
31+
32+
### Production Product Codes
33+
34+
| Product Name | Code |
35+
|---|---|
36+
| DV SSL Certificate 1 Year | **838** |
37+
| DV SSL Certificate Wildcard 1 Year | **839** |
38+
| DV SSL Certificate UCC 1 Year | **840** |
39+
| DV SSL Certificate Wildcard UCC 1 Year | **841** |
40+
| OV SSL Certificate 1 Year | **842** |
41+
| OV SSL Certificate Wildcard 1 Year | **843** |
42+
| OV SSL Certificate UCC 1 Year | **844** |
43+
| OV SSL Certificate Wildcard UCC 1 Year | **845** |
44+
| EV SSL Certificate 1 Year | **846** |
45+
| EV SSL Certificate UCC 1 Year | **847** |
46+
| emSign Intranet SSL 1 Year (Private PKI) | **100** |
47+
| IGTF Host Certificate 1 Year | **104** |
48+
| emSign S/MIME Simple MV-S 1 Year | **894** |
49+
| emSign Natural Person NonRepudiation 1/2/3 Year | **825 / 826 / 827** |
50+
| emSign Legal Person NonRepudiation 1/2/3 Year | **822 / 823 / 824** |
51+
| emSign Legal Entity NonRepudiation 1/2/3 Year | **819 / 820 / 821** |
52+
53+
**Note**: Codes `819–827` (signing certificates) are the same in both environments.
54+
55+
**Implication for the plugin**: `DefaultProductCode` in `CERTInextConfig` and the `ProfileId` template parameter must use the code appropriate for the target environment. The plugin docs should reference this table rather than hard-coding any specific code.
56+
57+
---
58+
59+
## Endpoints Discovered from Postman Collection
60+
61+
All endpoints are `POST` with a JSON body containing a `meta` auth block.
62+
63+
### Order Lifecycle Endpoints
64+
65+
| Endpoint | Purpose | Notes |
66+
|---|---|---|
67+
| `GenerateOrderSSL` | Place a new DV/OV/EV SSL order | Includes CSR, agreement block, org details |
68+
| `GenerateOrderSMIME` | Place a new S/MIME order | |
69+
| `GenerateOrderSignature` | Place a signing certificate order | |
70+
| `GenerateOrderPrivatePKI` | Place a Private PKI / Intranet SSL order | Separate endpoint from `GenerateOrderSSL` — product 104/100 does NOT work via `GenerateOrderSSL` |
71+
| `SubmitCSR` | Submit CSR to an existing draft order | Used when `saveAndHold:"1"` at placement |
72+
| `SubmitDocument` | Submit validation documents | |
73+
| `TrackOrder` | Poll order/certificate status | Returns `certificateStatusId`, `domainVerification`, `subscriberAgreement` blocks |
74+
| `RejectOrder` | Cancel/reject an order by `orderNumber` | |
75+
| `RejectRequest` | Cancel/reject a request by `requestNumber` | For draft (on-hold) orders that have no `orderNumber` yet |
76+
| `AgreementAcceptance` | Submit subscriber agreement acceptance | See below |
77+
78+
### Certificate Endpoints
79+
80+
| Endpoint | Purpose |
81+
|---|---|
82+
| `GetCertificate` | Download issued certificate (PEM) |
83+
| `RevokeOrder` | Revoke by `orderNumber` + reason code |
84+
85+
### Account / Discovery Endpoints
86+
87+
| Endpoint | Purpose | Notes |
88+
|---|---|---|
89+
| `ValidateCredentials` | Ping / auth check | |
90+
| `GetProductDetails` | List available products | Requires `groupNumber` in `productDetails` block for some accounts |
91+
| `GetFieldDetails` | Get required fields per product code | Takes `groupNumber` + `categoryID` + `productCode` — use to discover required order fields |
92+
| `GetGroupDetails` | Get group info | |
93+
| `GetGroupDetailsV2` | Updated group info endpoint | |
94+
| `GetOrganizationDetails` | Get org info | |
95+
| `GetDomainDetails` | Get pre-validated domains | |
96+
| `GetOrderReport` | Paginated order/cert listing | Used for sync |
97+
98+
### DCV Endpoints
99+
100+
| Endpoint | Purpose |
101+
|---|---|
102+
| `GetDcv` | Get DCV token/instructions for a domain |
103+
| `VerifyDcv` | Trigger DCV verification |
104+
105+
**Important**: `dcvMethod` is a **numeric string**, not a word. The Postman collection uses `"3"`. The numeric codes are not yet fully mapped — ask eMudhra for the complete enum.
106+
107+
---
108+
109+
## AgreementAcceptance — How Subscriber Agreement Works
110+
111+
`AgreementAcceptance` is the endpoint for accepting the CERTInext subscriber agreement on a placed order.
112+
113+
**Request body:**
114+
```json
115+
{
116+
"meta": { ... },
117+
"agreementDetails": {
118+
"requestorEmail": "plugin-test@keyfactor.com",
119+
"orderNumber": "6655828778",
120+
"acceptAgreement": "1",
121+
"signerName": "Keyfactor Plugin Test",
122+
"signerPlace": "Gateway",
123+
"signerIP": "99.102.196.148" ← must be the real public IP, not 127.0.0.1
124+
}
125+
}
126+
```
127+
128+
**Key findings from live testing:**
129+
- The agreement is **automatically accepted** during `GenerateOrderSSL` when the order includes a populated `agreementDetails` block — the API returns `EMS-1082 Agreement already signed` if you call `AgreementAcceptance` afterwards.
130+
- `signerIP` must be the **real public IP** of the calling machine — `127.0.0.1` returns `EMS-1091 Invalid Signer IP`.
131+
- The `consentSentTo` email in `TrackOrder` is set to the **connector-level requestor email** (`sean.bailey@keyfactor.com` in testing), not the template-level email. The plugin should ensure the correct email is in the agreement block.
132+
- A `trackingUrl` is returned in `TrackOrder` — a public link the subscriber can use to review/accept the agreement manually if needed.
133+
134+
**Plugin implication**: The `agreementDetails` block in `GenerateOrderSSL` already handles acceptance. `AgreementAcceptance` is only needed for orders placed without an agreement block (e.g., draft orders without signer details). The `AutoApprove` template parameter in the plugin currently does nothing (`autoApprove` is passed to `BuildEnrollmentResult` but never used) — if it was intended to call `AgreementAcceptance`, that logic is missing.
135+
136+
---
137+
138+
## Product Management API — Does Not Exist
139+
140+
**Confirmed 2026-04-22**: The CERTInext REST API has no product creation, configuration, or management endpoints.
141+
142+
All 18 candidate endpoint names were probed via POST with a minimal meta block. All returned HTTP 404:
143+
144+
| Endpoint name | Result |
145+
|---|---|
146+
| `ConfigureProduct` | 404 — not found |
147+
| `CreateProduct` | 404 — not found |
148+
| `AddProduct` | 404 — not found |
149+
| `RegisterProduct` | 404 — not found |
150+
| `GetProductConfiguration` | 404 — not found |
151+
| `UpdateProduct` | 404 — not found |
152+
| `DeleteProduct` | 404 — not found |
153+
| `AddCertificateProfile` | 404 — not found |
154+
| `CreateCertificateProfile` | 404 — not found |
155+
| `ConfigureCertificate` | 404 — not found |
156+
| `AddCertificateTemplate` | 404 — not found |
157+
| `GetCAList` | 404 — not found |
158+
| `ListCAs` | 404 — not found |
159+
| `GetSubCAList` | 404 — not found |
160+
| `GetCADetails` | 404 — not found |
161+
| `GetPrivateCAList` | 404 — not found |
162+
| `ListSubCAs` | 404 — not found |
163+
| `GetIssuerList` | 404 — not found |
164+
165+
**Products and Sub-CA assignments must be configured via the portal UI** at `https://sandbox-us.certinext.io` under Account → Products → Configure Product.
166+
167+
The portal UI "Configure Product" form has the following fields (confirmed from the portal):
168+
- Product Name (required)
169+
- Subordinate CA (dropdown — only active Sub-CAs appear)
170+
- Validity In Days (required)
171+
- Key Algorithm (RSA 2048/3072/4096, ECC P256/P384, PQC variants)
172+
- Description (required)
173+
- Subject Attributes (OID → Request Field mapping)
174+
- SAN Attributes
175+
- Extensions
176+
- Advanced Settings → "Automatically approve the certificate requests"
177+
178+
To create a custom auto-approving Private PKI product, this must be done manually in the portal. The product code assigned by the portal can then be used with `GenerateOrderPrivatePKI` in the plugin.
179+
180+
---
181+
182+
## Sub-CA Listing — No API Endpoint
183+
184+
**Confirmed 2026-04-22**: There is no Sub-CA or CA listing endpoint in the CERTInext REST API. Sub-CA information must be obtained from the portal UI.
185+
186+
Sub-CAs visible in the sandbox portal for account `9374221333`:
187+
188+
| Name | Type | Status |
189+
|---|---|---|
190+
| Test CAk81 | Root CA | Active |
191+
| Test Root emCA1 | Root CA | Pending |
192+
| emSign Trusted Root CA - C5 | Root CA | Active |
193+
| emSign Sandbox Issuing CA - G1 | Subordinate CA | **Revoked** — likely cause of DV SSL issuance failures |
194+
| eMudhra Sandbox Private Root CA G1 | Root CA | Active |
195+
| **emSign Issuing Sand box CA IGTF - C6** | Subordinate CA | **Active** — only active Sub-CA |
196+
| emSign Trusted Sandbox Root CA - C6 | Root CA | Active |
197+
| Test CA | Root CA | Active |
198+
199+
The only active Sub-CA on this account is `emSign Issuing Sand box CA IGTF - C6`. Any new product created via the portal must use this Sub-CA until `emSign Sandbox Issuing CA - G1` is replaced or a new Sub-CA is provisioned.
200+
201+
---
202+
203+
## IGTF Product (108) — Not Provisioned on This Account
204+
205+
**Confirmed 2026-04-22**: Product 108 (IGTF Host Certificate) does not appear in `GetProductDetails` for this account, and `GetFieldDetails` with `categoryID=8, productCode=108` returns `EMS-1269: This product is not mapped to this group number`.
206+
207+
The Postman collection references product code `{{PrivatePKI_IGTF}}` for `GenerateOrderPrivatePKI`, suggesting this product exists on eMudhra's global product catalogue but has not been provisioned for group `2171775848`.
208+
209+
This is consistent with the earlier finding that product `104` (emSign Intranet SSL) was also not provisioned. Product `149` (Sandbox emSign Intranet SSL 1 Year) is the only Private PKI product on this account.
210+
211+
---
212+
213+
## Product 149 (Private PKI) — Auto-Issuance Status
214+
215+
**Confirmed 2026-04-22**: Product 149 (`Sandbox emSign Intranet SSL 1 Year`) accepts draft orders (`saveAndHold=1`) but **does NOT auto-issue**. Orders sit in "Pending for Approver" / "On Hold".
216+
217+
### Test results
218+
219+
All payloads tested with `GenerateOrderPrivatePKI`:
220+
221+
| Variant | `saveAndHold` | Result |
222+
|---|---|---|
223+
| Minimal (no agreement, no accountingModel) | `0` | `EMS-939: Something went Wrong` |
224+
| With `agreementDetails` | `0` | `EMS-939: Something went Wrong` |
225+
| With `delegationInformation` | `0` | `EMS-939: Something went Wrong` |
226+
| Minimal (Postman-style) | `1` | Success — `requestNumber=7314663138` |
227+
| Minimal | `1` | Success — `requestNumber=5668336671` |
228+
229+
**`saveAndHold=0` always fails with EMS-939 for product 149** regardless of payload shape. This is a server-side constraint, not a payload structure issue.
230+
231+
**Draft orders (`saveAndHold=1`) for product 149 land in `GetOrderReport` as:**
232+
```
233+
orderStatus: "On Hold"
234+
certificateStatus: "Pending for Approver"
235+
orderNumber: (blank — no orderNumber until formally submitted)
236+
issuerCA: (blank)
237+
```
238+
239+
This means auto-approval is **not** enabled for product 149 in the portal. The portal's "Automatically approve the certificate requests" toggle is off for this product. Orders cannot be auto-issued via the API until:
240+
1. The portal setting is enabled for product 149 by an account admin, OR
241+
2. A new product is created via the portal with auto-approval ON.
242+
243+
### Workaround
244+
245+
Use the portal at `https://sandbox-us.certinext.io` to:
246+
1. Locate product 149 under Account → Products.
247+
2. Edit it and enable "Automatically approve the certificate requests" under Advanced Settings.
248+
3. Re-run `make generate-order-igtf` or `make generate-order-private-pki` to verify auto-issuance.
249+
250+
Alternatively, create a new product via the portal (see "Product Management API — Does Not Exist" above) with auto-approval ON, backed by `emSign Issuing Sand box CA IGTF - C6`, and update `CERTINEXT_PRODUCT_CODE` in `~/.env_certinext`.
251+
252+
---
253+
254+
## Why DV SSL Orders Are Stuck on This Sandbox Account
255+
256+
All 8 "Pending for Approver" orders show:
257+
- `certificateStatusId: 24` = `PendingForApproverAutoApproval`
258+
- `domainVerification.status: "0"` — DCV not completed
259+
- `subscriberAgreement.status: "1"` — agreement already signed at order placement
260+
261+
The orders are blocked because `test-integration.example.com` is a non-real domain — DCV via DNS, HTTP file, or email cannot complete for it. The order cannot advance to issued state without DCV.
262+
263+
**To unblock integration tests**, one of the following is needed (in order of preference):
264+
265+
1. **Enable auto-approval on product 149** — log in to the portal as account admin, edit product 149, enable "Automatically approve the certificate requests" under Advanced Settings. Then `make generate-order-igtf` should auto-issue. This requires no eMudhra support involvement.
266+
267+
2. **Create a new Private PKI product via the portal** with auto-approval ON, backed by `emSign Issuing Sand box CA IGTF - C6`. Use the resulting product code in `~/.env_certinext` as `CERTINEXT_PRODUCT_CODE` and test with `make generate-order-private-pki PRIVATE_PKI_CODE=<new_code>`.
268+
269+
3. **Request IGTF product (108) provisioning** — ask eMudhra to add product `108` (IGTF Host Certificate) to group `2171775848`. If that product has auto-approval ON by default, it would immediately unblock the integration tests.
270+
271+
4. **Use a real domain you control** — place DV SSL orders (products 842–851) using a domain where you can create DNS records or serve HTTP files to complete DCV.
272+
273+
5. **Use the sandbox portal** to manually approve and issue certificates — the approver login at `https://sandbox-us.certinext.io` can advance orders for testing purposes.
274+
275+
**Product `104` (emSign Intranet SSL) is not provisioned on account `9374221333`** and product `108` (IGTF Host) is also not provisioned. Product `149` is provisioned but auto-approval is off. This is the most important configuration item to resolve, either via portal self-service or eMudhra support.
276+
277+
---
278+
279+
## AutoApprove Plugin Parameter — Currently Dead Code
280+
281+
`Constants.EnrollmentParam.AutoApprove` and `ep.AutoApprove` exist and are passed to `BuildEnrollmentResult(resp, ep.AutoApprove)`, but the `autoApprove` parameter is never used inside that method. It was presumably intended to call `AgreementAcceptance` after enrollment for accounts that require a separate acceptance step, but the implementation was never completed.
282+
283+
**To implement**: After a successful `GenerateOrderSSL` that returns `certificateStatusId: 24` (PendingForApproverAutoApproval), call `AgreementAcceptance` with the returned `orderNumber` and the signer details from the connector config. Only do this when `ep.AutoApprove == true`.
284+
285+
The `signerIP` must be the real public IP — consider auto-detecting via `https://api.ipify.org` (already referenced in the Makefile) or making it a connector config field.
286+
287+
---
288+
289+
## `GetProductDetails` Requires `groupNumber`
290+
291+
Calling `GetProductDetails` without a `groupNumber` in the `productDetails` block returns an empty list on some accounts. The fix (already in the plugin as of `fix/p1-p3-improvements`) passes `_config.GroupNumber` when set. `GroupNumber` is now a connector config field.
292+
293+
This appears to be account-specific behavior — some accounts require it, others don't. Always pass it when available.
294+
295+
---
296+
297+
## Makefile Targets Added (2026-04-22)
298+
299+
All targets are in `/Users/sbailey/RiderProjects/certinext-caplugin/Makefile` and load credentials from `~/.env_certinext`.
300+
301+
| Target | Description |
302+
|---|---|
303+
| `make list-cas` | Documents that no Sub-CA listing API exists; probes 3 endpoint names to confirm; prints known active Sub-CAs from portal UI |
304+
| `make create-product` | Documents that no product management API exists; probes 3 endpoint names; prints step-by-step portal instructions to create an auto-approving product |
305+
| `make generate-order-igtf` | Places a `GenerateOrderPrivatePKI` order for product 149 (IGTF-equivalent); `SAVE_AND_HOLD=0` submits, `SAVE_AND_HOLD=1` drafts |
306+
| `make generate-order-private-pki` | Same as above but accepts `PRIVATE_PKI_CODE=` for any product code |
307+
| `make probe-endpoints` | POSTs minimal meta to all 18 candidate endpoint names; reports 404 vs. any other response |
308+
| `make get-field-details [PRODUCT_CODE=149] [CATEGORY_ID=8]` | Calls `GetFieldDetails` for any product code to get field definitions |
309+
| `make show-postman-bodies [FILTER=keyword]` | Extracts request bodies from the Postman collection; filter by keyword |
310+
| `make probe-private-pki-payloads` | Tests 3 payload variants for `GenerateOrderPrivatePKI` to isolate EMS-939 root cause |
311+
312+
Supporting scripts (in `scripts/`):
313+
- `scripts/probe_endpoints.py` — backs `probe-endpoints`
314+
- `scripts/probe_private_pki.py` — standalone private PKI probe
315+
- `scripts/order_private_pki_minimal.py` — backs `probe-private-pki-payloads`
316+
- `scripts/get_field_details.py` — backs `get-field-details`
317+
- `scripts/extract_postman_bodies.py` — backs `show-postman-bodies`
318+
319+
---
320+
321+
## `GetProductDetails` — Provisioned Products for This Account
322+
323+
`GetProductDetails` with `groupNumber=2171775848` returns the following products (confirmed 2026-04-22):
324+
325+
| Category | Product Code | Product Name |
326+
|---|---|---|
327+
| Document Signer | 810 | Softnet Natural Person Certificate - Soft Token 1 Year |
328+
| S/MIME | 914 | emSign - SMIME - Simple MV-S 1 Year |
329+
| S/MIME | 915 | emSign - SMIME - Simple MV-S 2 Years |
330+
| S/MIME | 919–924 | emSign SMIME Personal/Professional/Corporate variants |
331+
| SSL/TLS | 842–851 | DV/OV/EV SSL (single, wildcard, UCC) |
332+
| eSign | 853, 854 | eSign Natural/Legal Person 10Min |
333+
| **Private PKI** | **149** | **Sandbox emSign Intranet SSL 1 Year** |
334+
335+
Product 149 is the only Private PKI product. Products 104 and 108 from the Postman collection (the "standard" Intranet SSL and IGTF products) are not provisioned.
336+
337+
---
338+
339+
## Questions Still Open for eMudhra Support
340+
341+
1. What are the numeric `dcvMethod` codes for `GetDcv` / `VerifyDcv`? (`"3"` appears in the Postman collection but the enum is undocumented.)
342+
2. Can IGTF product `108` be provisioned on account `9374221333` for automated testing? Or can product `149` have auto-approval enabled?
343+
3. Is there a sandbox environment where DV SSL auto-issues without real domain ownership?
344+
4. What is the `GetFieldDetails` `categoryID` enum? How do you look up required fields per product?
345+
5. Is `GetGroupDetailsV2` replacing `GetGroupDetails`? What changed?
346+
6. Why does `GenerateOrderPrivatePKI` with `saveAndHold=0` always return EMS-939 for product 149, while `saveAndHold=1` succeeds? Is immediate submission blocked for this product category?

0 commit comments

Comments
 (0)