-
Notifications
You must be signed in to change notification settings - Fork 24
SPIKE #1083 ODCA Certificate Validation for AMT
Issue Link: https://github.com/device-management-toolkit/rpc-go/issues/1083
For AMT 19+ activation, users often rely on --skip-amt-cert-check to get through TLS setup. This bypasses ODCA certificate verification logic that should be enforced in normal secure flows.
The feature request is to ensure ODCA certificate chains presented by AMT are validated. This is needed because the Intel AMT ODCA certificate is used as an Intel AMT TLS Server certificate for mutual TLS provisioning, assuring that the configuration software is communicating with authentic Intel AMT firmware.
Goal: make activation succeed securely by default without requiring -n or --skip-amt-cert-check in standard environments.
- ODCA is Intel's certificate chain used by AMT during provisioning TLS.
- During TLS handshake, AMT presents a certificate chain.
- Verification means: "Is this chain anchored to a trusted Intel ODCA root and structurally valid?"
- If not verified, activation can still encrypt traffic, but identity trust is missing.
-
--skip-amt-cert-checkcontrols certificate validation for the local TLS hop to the LMS endpoint (the chain represents AMT identity). - For direct local TLS, rpc-go builds TLS config in
internal/certs/lmsTls.goviaGetTLSConfig. - In pre-provisioning mode (
mode == 0), rpc-go callback invokes:VerifyCertificatesVerifyLeafCertificateVerifyROMODCACertificateVerifyFullChain
-
VerifyFullChaincurrently uses embedded roots (internal/certs/trustedstore/*.cer) viaLoadRootCAPool. - In cloud TLS tunnel mode, TLS to AMT is terminated by RPS
TLSTunnelManager; rpc-go forwards bytes and does not validate ODCA in that hop.
| Concern | File |
|---|---|
| TLS config and peer certificate checks | internal/certs/lmsTls.go |
| Embedded trusted root loading | internal/certs/embed.go |
| Insecure bypass flag definition | internal/cli/cli.go |
| Activation path (consumes TLS config) | internal/commands/activate/local.go |
- Customers pass
--skip-amt-cert-checkduring activation on AMT 19+ platforms. - This sets
InsecureSkipVerify = trueon the TLS config, disabling all certificate chain validation against trusted roots. - The transport (mTLS) remains secure, but the ODCA chain is never verified.
Is this feature for local (console) activation only, or also for cloud (RPS/MPS) flows?
Answer: both paths are affected whenever LocalTlsEnforced is active or LMS is
present on the device. The diagrams below show exactly where each TLS hop occurs and
where ODCA verification is triggered.
The operator runs rpc activate --local directly on the managed device.
No RPS or MPS is involved.
+------------------------------------------------------------------+
| Managed Device |
| |
| +-----------+ (1) WSMAN over TLS +--------------------+ |
| | | ----------------------> | | |
| | rpc-go | localhost:16993 | LMS (Local Mgmt | |
| | (console) | <---------------------- | Service) | |
| | | LMS presents chain | | |
| +-----------+ <-- VERIFY HERE --- +--------+-----------+ |
| | |
| (2) MEI/HECI |
| (in-kernel, no TLS) |
| | |
| +--------v-----------+ |
| | AMT firmware | |
| | (CSME) | |
| +--------------------+ |
+------------------------------------------------------------------+
| Hop | Protocol | TLS? | Certificate Presented |
|---|---|---|---|
| rpc-go → LMS | WSMAN/TLS | Yes | LMS presents chain representing AMT identity — verify here |
| LMS → AMT | MEI/HECI | No | — |
Code path: internal/commands/activate/local.go → SetupWsmanClient(tlsConfig)
|
The operator runs rpc activate -u wss://rps.example.com --profile myprofile.
RPS orchestrates activation; rpc-go relays commands between RPS and LMS/LME.
+-----------------------------------------------+
| Managed Device | +--------------------------------------+
| | | Cloud |
| +-----------+ (1) WSS (WebSocket + TLS) | | +-------------------------------+ |
| | | ----------------------------->-|--->| | RPS | |
| | rpc-go | wss://rps.example.com | | | (Remote Provisioning Server) | |
| | | <----------------------------- | | | | |
| | | provisioning commands | | | TLSTunnelManager | |
| | | [guarded by --skip-cert-check]| +--------------------------------------+
| | | |
| | | (2a) if LocalTlsEnforced: |
| | | raw tunnel bytes to LMS TLS |
| | | ----------------> +----------+ |
| | | localhost:16993 | LMS | |
| | | <---------------- | | |
| | | (rpc-go relays) +----+-----+ |
| | | | |
| | | MEI/HECI |
| | | (2b) if no TLS / LMS absent: |
| | | Direct MEI/HECI (LME) |
| | | ----------------> +----------+ |
| +-----------+ (no TLS, no | AMT fw | |
| ODCA check) | (CSME) | |
| +----------+ |
+-----------------------------------------------+
| Hop | Protocol | TLS? | Certificate Presented | Flag |
|---|---|---|---|---|
| rpc-go → RPS | WSS | Yes | RPS server cert (standard CA) |
--skip-cert-check / -n
|
| rpc-go → LMS | Plain TCP relay to TLS port (16993) when LocalTlsEnforced
|
No (at rpc-go layer) | — | — |
| RPS TLSTunnelManager ↔ AMT (via rpc-go relay) | TLS tunnel (tls_data) |
Yes | ODCA / configured CA chain — verify here | RPS tunnel policy |
| rpc-go → LME | MEI/HECI fallback (LMS absent) | No | — | — |
Code paths:
-
rpc-go/internal/rps/executor.go→lm.NewLMSConnection(... LocalTlsEnforced ...)for relay -
rpc-go/internal/lm/service.go→LMSConnection.Connect()dials plain TCP to LMS (16993in TLS tunnel mode) -
rpc-go/internal/rps/rps.go→Connect(skipCertCheck)validates only the RPS WSS cert -
rps/src/TLSTunnelManager.tsterminates TLS, captures peer chain, and verifies ODCA/custom CA - Falls back to
lm.NewLMEConnection()(direct MEI, no TLS) when LMS is absent
rpc-go RPS TLSTunnelManager LMS endpoint / AMT identity
| | |
|-- WSS (TLS) ---------------->| |
| (RPS cert checked here) | |
| |-- TLS ClientHello -----------> |
|<====== tls_data bytes ======>|<-- ServerHello + cert chain -- |
| (byte relay only) | (ODCA/custom CA verified) |
| |-- TLS Finished --------------> |
|<====== tunneled WSMAN data ================================> |
Notes:
- In this mode, rpc-go is a transport relay for AMT TLS bytes.
- Certificate chain verification for AMT TLS is performed in RPS (
TLSTunnelManager), not in rpc-goGetTLSConfig.
| Activation Path | Where TLS Terminates | ODCA Check Needed? | Controlled By |
|---|---|---|---|
Console local (--local) |
rpc-go ↔ LMS (16993) |
YES | --skip-amt-cert-check |
Cloud via RPS + LocalTlsEnforced=true
|
RPS TLSTunnelManager ↔ AMT (via rpc-go relay) |
YES | RPS TLS tunnel policy |
| Cloud via RPS, plain LMS (no TLS enforced) | No AMT TLS hop | No | — |
| Cloud via RPS, LMS absent (LME fallback) | No AMT TLS hop | No | — |
| Post-activation CIRA to MPS | AMT ↔ MPS (mTLS) | Not part of this rpc-go ODCA check | MPS/CIRA trust config |
The ODCA feature targets the first two rows. On AMT 19+, the ODCA root CA in those chains changed, causing trust failures unless the correct root is available to the active verifier (rpc-go in local direct mode, RPS in cloud TLS tunnel mode).
rpc-go LMS (localhost:16993)
| |
|---- TLS ClientHello ---------------------->|
| |
|<--- TLS ServerHello + Certificate chain ---|
| [0] leaf: iAMT CSME IDevID RCFG |
| [1] intermediate 1 |
| [2] intermediate 2 |
| [3] ODCA ROM CA <- level 3 |
| [4] ODCA intermediate |
| [5] Intel root CA <- CHANGED on 19+ |
| |
| VerifyPeerCertificate() fires |
| in GetTLSConfig() (lmsTls.go) |
| VerifyCertificates() |
| VerifyLeafCertificate() [index 0] |
| VerifyROMODCACertificate() [index 3] |
| VerifyFullChain() |
| LoadRootCAPool() |
| needs AMT 19+ root in |
| trustedstore/ to succeed |
| |
|---- TLS Finished ------------------------->|
|---- WSMAN activation commands ------------>|
Root cause on AMT 19+: The Intel root CA at index 5 changed. If that root is absent
from trustedstore/, VerifyFullChain() fails and customers fall back to
--skip-amt-cert-check. This is what the feature must fix.
This sequence applies to the local direct TLS validation path (rpc-go as TLS client).
For cloud TLS tunnel mode, equivalent trust-source improvements must be applied in RPS
TLSTunnelManager.
sequenceDiagram
autonumber
participant U as User/CLI
participant C as internal/cli
participant A as activate/local
participant T as GetTLSConfig
participant H as TLS Handshake
participant V as VerifyPeerCertificate
participant VC as VerifyCertificates
participant L as VerifyLeafCertificate
participant R as VerifyROMODCACertificate
participant F as VerifyFullChain
participant E as Embedded Roots (existing)
participant B as BuildTrustPool (new)
participant O as OS Trust Store (new)
participant CF as Optional CA File (new optional)
U->>C: rpc activate
C->>A: SkipAMTCertCheck=false
A->>T: GetTLSConfig(mode=0, amtCertInfo, false)
T-->>A: tls.Config(callback enabled)
A->>H: Open TLS to LMS endpoint
H->>V: Peer cert chain
V->>VC: VerifyCertificates(rawCerts,...)
VC->>L: Leaf CN/hash checks (existing)
L-->>VC: pass/fail
VC->>R: ROM ODCA CN/OU checks (existing)
R-->>VC: pass/fail
Note over VC,F: Existing chain verification call remains
VC->>F: VerifyFullChain(parsedCerts)
Note over F,E: Current: embedded roots only
F->>E: LoadRootCAPool()
E-->>F: Root pool
Note over F,B: Implement: replace/extend root loading with combined pool
F->>B: BuildTrustPool() (new)
B->>E: Load embedded roots
B->>O: Merge OS roots
opt enterprise override
B->>CF: Add CA file roots
end
B-->>F: Combined trust pool
F-->>VC: Valid chain or actionable trust error
VC-->>V: success/failure
V-->>H: handshake continue/fail
- Define trust-source policy for AMT 19+ default activation.
- Decide precedence: embedded + OS + optional file.
- Define error taxonomy/messages for trust failures.
- Add
BuildTrustPoolabstraction ininternal/certs. - Load and merge embedded root certs (existing source).
- Load and merge OS trusted roots (new source).
- Optionally support user-provided CA file roots.
- Update
VerifyFullChainto use combined trust pool. - Keep
VerifyLeafCertificateandVerifyROMODCACertificateas supplemental checks. - Add CLI/config/env input for enterprise CA bundle (--amt-ca-file, AMT_CA_FILE) and thread into TLS validation path
- Align cloud TLS tunnel verifier trust inputs in RPS (
TLSTunnelManager) with the same trust policy, so local and cloud behavior match. - Keep skip flags functional as explicit insecure override.
- Unit test: valid chain with embedded roots passes.
- Unit test: valid chain with OS roots passes.
- Unit test: untrusted chain fails closed.
- Unit test: malformed/expired cert fails with clear error.
- Command/integration test: activation works without skip flags in trusted setup.
- Command/integration test:
--skip-amt-cert-checkstill bypasses verification when explicitly used.