第一章:Go网络安全通信黄金标准全景概览
Go 语言凭借其原生并发模型、静态编译能力与精简的标准库,在构建高可信网络服务时展现出独特优势。其网络安全通信能力并非依赖第三方框架堆砌,而是由 crypto/tls、net/http、crypto/x509 等标准包协同构筑的端到端信任链——从证书验证、密钥交换、到应用层协议加固,均遵循现代密码学最佳实践。
TLS 配置的最小安全基线
生产环境必须禁用不安全协议与弱密码套件。以下代码强制启用 TLS 1.2+ 并排除已知脆弱算法:
config := &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.CurveP256, tls.X25519},
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
},
// 启用证书验证并校验主机名
ClientAuth: tls.RequireAndVerifyClientCert,
}
该配置确保前向保密(PFS)、抗量子预备曲线(X25519)及 AEAD 加密模式,避免使用 RSA 密钥交换或 CBC 模式套件。
证书生命周期管理原则
Go 不提供自动证书轮换,但可通过标准接口无缝集成 ACME 客户端(如 certmagic):
| 组件 | 推荐实践 |
|---|---|
| 私钥保护 | 使用 crypto/rsa.GenerateKey() 生成 2048+ 位密钥,存储于受控文件系统或 HSM |
| 证书签发 | 通过 Let’s Encrypt + certmagic.HTTPS() 实现零停机自动续期 |
| 信任锚管理 | 严格限制 RootCAs 字段加载的 CA 证书,禁用系统默认根证书池 |
HTTP 层安全加固要点
启用 HTTPS 重定向、HSTS 头、CSP 及安全 Cookie 属性是基础防线:
http.HandleFunc("/secure", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
http.SetCookie(w, &http.Cookie{
Name: "session",
HttpOnly: true,
Secure: true, // 仅 HTTPS 传输
SameSite: http.SameSiteStrictMode,
})
})
这些机制共同构成 Go 网络安全通信的黄金标准:可验证、可审计、可组合,且无需牺牲性能与开发效率。
第二章:TLS 1.3双向认证的Go原生实现
2.1 TLS 1.3协议核心特性与Go crypto/tls适配原理
TLS 1.3 精简握手流程,废除 RSA 密钥传输与静态 DH,强制前向安全,仅保留 ECDHE + AEAD 组合。Go 1.12 起完整支持,crypto/tls 在 Config 层面通过 MinVersion 和 CurvePreferences 显式约束。
握手阶段关键变更
- 0-RTT 数据需应用层显式启用(有重放风险)
- ServerHello 后立即发送 EncryptedExtensions,取消 ChangeCipherSpec
- 证书验证移至加密通道内,杜绝明文泄露
Go 中的典型配置
cfg := &tls.Config{
MinVersion: tls.VersionTLS13,
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
CipherSuites: []uint16{tls.TLS_AES_128_GCM_SHA256}, // 唯一允许的 AEAD 套件
}
MinVersion 强制协议版本下限;CurvePreferences 控制密钥交换优先级,X25519 性能优于 P256;CipherSuites 若为空则使用默认列表,但 TLS 1.3 下仅接受 AEAD 套件(如 _AES_128_GCM_SHA256),其余被忽略。
| 特性 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 典型握手延迟 | 2-RTT | 1-RTT(0-RTT 可选) |
| 密钥交换机制 | RSA / ECDHE / DHE | 仅 ECDHE(含 X25519) |
| 加密套件设计 | 分离 MAC + Cipher | AEAD 单一原语(如 AES-GCM) |
graph TD
A[ClientHello] --> B[ServerHello + EncryptedExtensions + Certificate + CertVerify + Finished]
B --> C[Application Data]
style A fill:#4a6fa5,stroke:#333
style C fill:#6b8e23,stroke:#333
2.2 基于x509证书链的客户端/服务端双向身份验证实践
双向TLS(mTLS)要求客户端与服务端均出示由可信CA签发的x509证书,并完整验证对方证书链。
证书链验证关键步骤
- 服务端加载自身证书+私钥,同时配置受信任的根CA证书(
ca.crt)用于验证客户端证书 - 客户端需携带完整证书链(
client.crt+ 中间CA证书),确保服务端可逐级向上校验至根CA - 双方均启用
VerifyPeer=true并设置ClientAuth: tls.RequireAndVerifyClientCert
核心配置示例(Go)
cfg := &tls.Config{
Certificates: []tls.Certificate{serverCert}, // 包含 leaf + intermediates
ClientCAs: rootCAPool, // 仅含根CA,用于验证客户端链
ClientAuth: tls.RequireAndVerifyClientCert,
}
serverCert须通过tls.LoadX509KeyPair("server.crt", "server.key")加载;rootCAPool需用x509.NewCertPool()显式添加根证书,不包含中间CA——这是链式验证的前提。
验证失败常见原因
| 现象 | 根本原因 |
|---|---|
x509: certificate signed by unknown authority |
客户端未发送中间证书,或服务端ClientCAs未包含对应根CA |
tls: client didn't provide a certificate |
客户端未配置tls.Config.Certificates或请求未携带证书 |
graph TD
A[客户端发起TLS握手] --> B[发送client.crt + intermediate.crt]
B --> C[服务端用rootCA.crt验证链完整性]
C --> D{验证通过?}
D -->|是| E[建立加密通道]
D -->|否| F[终止连接]
2.3 CertificateRequest定制与ClientAuth策略精细化控制
ClientAuth策略的三级控制粒度
- Disabled:完全跳过客户端证书验证
- Require:强制双向认证,无证书则拒绝连接
- Verify:尝试验证但允许无证书请求(需配合
clientAuth=want)
CertificateRequest消息定制关键字段
| 字段 | 作用 | 典型值 |
|---|---|---|
certificate_types |
指定可接受的证书类型 | rsa_sign, ecdsa_sign |
supported_signature_algorithms |
限制签名算法集 | ecdsa_secp256r1_sha256 |
certificate_authorities |
发送CA列表供客户端筛选 | DER编码的CA DN列表 |
// 构造定制化CertificateRequest
CertificateRequest req = new CertificateRequest(
new byte[]{0x01}, // rsa_sign
new SignatureScheme[]{SignatureScheme.ECDSA_SECP256R1_SHA256},
new DistinguishedName[]{caDN} // 仅信任指定CA
);
该代码显式约束客户端必须提供RSA或ECDSA签名证书,且仅接受由caDN签发的证书,避免泛化信任。SignatureScheme参数直接映射TLS 1.3签名算法标识,确保与服务端supported_groups协同生效。
graph TD
A[ClientHello] --> B{Server requires client auth?}
B -->|Yes| C[Send CertificateRequest]
C --> D[Filter by cert_types & signature_algorithms]
D --> E[Validate against certificate_authorities]
2.4 会话复用(PSK/0-RTT)在Go server中的安全启用与风险规避
Go 1.19+ 原生支持 TLS 1.3 PSK 与 0-RTT,但默认禁用 0-RTT 以规避重放攻击。
启用 PSK 复用(非 0-RTT)
config := &tls.Config{
GetSession: func(hello *tls.ClientHelloInfo) (*tls.SessionState, error) {
// 从安全缓存(如 Redis + AEAD 加密)按 ClientHello.SessionId 查找
return loadSessionFromCache(hello.SessionId)
},
SetSession: func(hello *tls.ClientHelloInfo, state *tls.SessionState) error {
return saveSessionToCache(hello.SessionId, state, 4*time.Hour)
},
}
GetSession 和 SetSession 协同实现服务端 PSK 存储;state 包含加密上下文与密钥材料,必须加密持久化,不可明文落盘。
0-RTT 的安全边界
- ✅ 允许幂等 GET /health
- ❌ 禁止用于 POST /api/transfer(资金操作)
- ⚠️ 必须在
http.HandlerFunc中调用r.TLS.NegotiatedProtocolIsMutual()并检查r.TLS.DidResume()与r.TLS.HandshakeComplete()
| 风险类型 | 缓解措施 |
|---|---|
| 重放攻击 | 应用层时间戳+单次 nonce 校验 |
| PSK 泄露 | 定期轮换 PSK 密钥,绑定 Client IP+UA |
graph TD
A[Client Hello with PSK] --> B{Server validates PSK & binder}
B -->|Valid| C[Resume handshake]
B -->|Invalid| D[Full 1-RTT handshake]
C --> E[0-RTT data accepted?]
E -->|Idempotent only| F[Apply application guard]
2.5 双向认证握手性能剖析与Wireshark抓包验证
双向TLS(mTLS)握手比单向认证多一轮证书交换与验证,显著增加RTT与CPU开销。
Wireshark关键过滤与字段
# 过滤完整mTLS握手流程
tls.handshake.type == 1 || tls.handshake.type == 11 || tls.handshake.type == 12 || tls.handshake.type == 16
type==1: ClientHellotype==11: Certificate (client/server)type==12: CertificateVerifytype==16: Finished
握手时序对比(单位:ms)
| 场景 | 平均耗时 | 主要瓶颈 |
|---|---|---|
| 单向TLS | 82 | ServerCert + ServerHelloDone |
| 双向TLS | 147 | ClientCert + Verify + 加密签名计算 |
TLS 1.3 mTLS精简流程
graph TD
A[ClientHello] --> B[ServerHello + EncryptedExtensions + Certificate + CertificateVerify + Finished]
B --> C[Certificate + CertificateVerify + Finished]
C --> D[Application Data]
客户端证书验证需同步访问CA吊销列表(OCSP Stapling可缓解),是延迟主因之一。
第三章:生产级证书轮换机制设计与落地
3.1 ACME协议集成与Let’s Encrypt自动化证书续期(Go实现)
ACME(Automatic Certificate Management Environment)是Let’s Encrypt采用的标准化协议,用于全自动证书签发与续期。Go 生态中,github.com/go-acme/lego/v4 是最成熟的客户端实现。
核心流程概览
graph TD
A[初始化ACME客户端] --> B[注册账户并接受ToS]
B --> C[发起域名授权挑战]
C --> D[完成HTTP-01或DNS-01验证]
D --> E[申请/续订证书]
初始化与账户注册
config := lego.NewConfig(&account)
config.CADirURL = "https://acme-v02.api.letsencrypt.org/directory"
config.UserAgent = "my-cert-manager/1.0"
client, err := lego.NewClient(config)
if err != nil { panic(err) }
// 注册账户(仅首次需调用)
reg, err := client.Registration.Register(registration.NewResource(), true)
CADirURL指向 Let’s Encrypt 生产环境;Register(..., true)自动同意服务条款;reg含账户密钥与URI,应持久化存储。
验证器选择对比
| 类型 | 适用场景 | 部署复杂度 | DNS支持 |
|---|---|---|---|
| HTTP-01 | Web服务器可访问 | 低 | ❌ |
| DNS-01 | 无公网Web服务 | 中 | ✅ |
续期逻辑需嵌入定时任务(如 time.Ticker),并在证书剩余有效期
3.2 零停机热加载证书:tls.Config.ReloadableCertificates实战封装
Go 1.19 引入 tls.Config.ReloadableCertificates,为 TLS 服务提供无中断证书更新能力。
核心机制
- 证书文件变更由
fsnotify监听 tls.Config动态替换GetCertificate回调中的证书链- 连接复用不受影响,新握手立即使用新证书
封装示例
type HotReloader struct {
cfg *tls.Config
loader *tls.ReloadableCertificates
}
func NewHotReloader(certPath, keyPath string) (*HotReloader, error) {
loader, err := tls.NewReloadableCertificates(certPath, keyPath)
if err != nil {
return nil, err // 证书格式/权限校验失败
}
return &HotReloader{
loader: loader,
cfg: &tls.Config{
GetCertificate: loader.GetCertificate,
},
}, nil
}
NewReloadableCertificates 自动监听文件系统事件;GetCertificate 是线程安全回调,内部已加锁同步证书读取与缓存。
| 特性 | 传统 reload | ReloadableCertificates |
|---|---|---|
| 连接中断 | 是(需重启 listener) | 否(平滑切换) |
| 实现复杂度 | 高(需信号+goroutine协调) | 低(开箱即用) |
graph TD
A[证书文件变更] --> B[fsnotify 事件]
B --> C[异步重载 PEM 解析]
C --> D[原子更新内存证书缓存]
D --> E[新 TLS 握手返回新证书]
3.3 证书生命周期监控与过期预警系统(基于time.Timer+Prometheus)
核心设计思路
采用轻量级 time.Timer 实现单次精准倒计时,避免 goroutine 泄漏;结合 Prometheus 暴露 ssl_cert_expires_in_seconds 指标,支持多维标签(domain, issuer, env)。
动态刷新机制
- 每次证书加载后,启动独立 Timer,到期触发告警并自动重载
- Timer 在证书更新时显式
Stop()并重建,确保时效性
关键代码片段
// 创建带标签的Gauge指标
certExpiryGauge := promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "ssl_cert_expires_in_seconds",
Help: "Seconds until TLS certificate expires",
},
[]string{"domain", "issuer", "env"},
)
// 启动倒计时(tTL为剩余秒数)
timer := time.NewTimer(time.Second * time.Duration(ttl))
go func() {
<-timer.C
certExpiryGauge.WithLabelValues(domain, issuer, env).Set(0) // 过期置0
triggerAlert(domain) // 触发企业微信/PagerDuty告警
}()
逻辑分析:
time.Timer单次触发语义明确,比time.Ticker更契合“证书仅需预警一次”的场景;Set(0)显式标记过期状态,便于 Prometheus 的alerting_rules中配置ssl_cert_expires_in_seconds == 0触发告警。标签维度支持按环境隔离监控。
预警响应等级
| 剩余时间 | 告警级别 | 通知渠道 |
|---|---|---|
| CRITICAL | 电话+钉钉群 | |
| 72h–7d | WARNING | 邮件+企业微信 |
| > 7d | INFO | 日志归档(无推送) |
graph TD
A[读取证书链] --> B{解析NotAfter}
B --> C[计算ttl = NotAfter - Now]
C --> D[启动time.Timer]
D --> E[到期:指标置0 + 推送告警]
E --> F[异步重载证书并重启Timer]
第四章:OCSP Stapling增强信任链验证能力
4.1 OCSP协议原理、响应结构及Go中crypto/x509/ocsp解析实现
OCSP(Online Certificate Status Protocol)是一种轻量级的X.509证书状态实时查询机制,替代CRL实现更及时的吊销验证。
核心交互流程
graph TD
A[客户端构造OCSP请求] --> B[发送HTTP POST至OCSP Responder]
B --> C[服务端返回DER编码的OCSPResponse]
C --> D[客户端解析并验证签名与有效期]
OCSP响应关键字段
| 字段 | 含义 | Go对应结构体字段 |
|---|---|---|
Status |
good/revoked/unknown |
Response.Status |
ThisUpdate |
响应签发时间 | Response.ThisUpdate |
NextUpdate |
下次更新建议时间 | Response.NextUpdate |
Go解析示例
resp, err := ocsp.ParseResponse(derBytes, cert.Certificate[0])
if err != nil {
log.Fatal("OCSP parse failed:", err) // 验证签名、时间戳、颁发者密钥ID
}
// resp.Status == ocsp.Good 表示证书有效
ocsp.ParseResponse 自动校验响应签名、时间有效性及颁发者绑定关系,底层调用 x509.VerifyOptions 进行链式信任验证。
4.2 服务端主动获取并缓存OCSP响应的并发安全策略
为避免 TLS 握手时同步请求 OCSP 带来的延迟与单点故障,现代服务端普遍采用后台异步预取 + 线程安全缓存策略。
并发安全缓存结构
使用 sync.Map(Go)或 ConcurrentHashMap(Java)实现键为证书序列号+颁发者哈希的响应映射,支持高并发读、低频写。
OCSP 获取调度逻辑
// 使用带限流的 goroutine 池避免对 OCSP 响应器造成洪泛
func fetchAndCache(ocspURL string, certDER []byte) {
resp, err := ocspClient.Do(ocsp.Request{
Certificate: certDER,
URL: ocspURL,
Timeout: 5 * time.Second,
})
if err == nil && resp.Status == ocsp.Good {
cache.Store(cacheKey(certDER), &OCSPCacheEntry{
Response: resp.Raw,
Expires: resp.NextUpdate,
Updated: time.Now(),
})
}
}
逻辑分析:
cacheKey()基于证书 DER 和 OCSP 响应器 URL 构建唯一键;OCSPCacheEntry.Expires决定是否触发下一轮预取;sync.Map.Store()保证写入原子性,无锁读性能优异。
缓存更新策略对比
| 策略 | 并发安全性 | 过期一致性 | 实现复杂度 |
|---|---|---|---|
| 全局互斥锁 | ✅ | ✅ | ⚠️ 中 |
| CAS 循环重试 | ✅ | ❌(ABA) | ⚠️ 高 |
| sync.Map + TTL 检查 | ✅ | ✅(读时校验) | ✅ 低 |
graph TD
A[定时器触发] --> B{缓存中存在?}
B -->|否| C[启动异步 fetch]
B -->|是| D[检查 NextUpdate]
D -->|已过期| C
D -->|有效| E[返回缓存响应]
C --> F[Store 到 sync.Map]
4.3 Stapling响应签名验证与时间戳校验(nonce+thisUpdate/nextUpdate)
OCSP Stapling 响应的可信性依赖于双重保障:签名完整性与时效性约束。
签名验证流程
使用 CA 证书公钥验证 OCSP 响应的 Signature 字段,确保未被篡改:
# 验证 stapled 响应签名(需 OpenSSL 1.1.1+)
openssl ocsp -respin stapled.der -CAfile issuer.pem -verify_other ca-bundle.pem -trust_certs root.pem
逻辑说明:
-respin指定 DER 编码响应;-CAfile提供签发该 OCSP 响应的 OCSP 签发者证书;-verify_other和-trust_certs共同构建信任链。失败则拒绝 TLS 握手。
时间戳校验关键字段
| 字段 | 含义 | 校验要求 |
|---|---|---|
nonce |
客户端随机数(防重放) | 必须与请求中 nonce 完全一致 |
thisUpdate |
响应生成时间 | ≤ 当前系统时间(允许5分钟偏移) |
nextUpdate |
下次更新截止时间 | > 当前时间(否则视为过期) |
时序校验逻辑(Mermaid)
graph TD
A[收到 stapled 响应] --> B{nonce 匹配?}
B -->|否| C[拒绝]
B -->|是| D{当前时间 ∈ [thisUpdate, nextUpdate]?}
D -->|否| C
D -->|是| E[接受证书状态]
4.4 客户端强制校验Stapling响应的TLS配置与兼容性兜底方案
当客户端启用 SSL_OP_NO_TICKET 并设置 SSL_VERIFY_PEER | SSL_VERIFY_POST_HANDSHAKE 时,必须同步开启 OCSP stapling 强制校验:
// 启用OCSP响应强制验证(OpenSSL 1.1.1+)
SSL_set_post_handshake_auth(ssl, 1);
SSL_set_verify(ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb);
SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp);
该配置要求服务端在 CertificateStatus 扩展中提供有效、未过期、签名可验证的 stapled OCSP 响应;否则握手将失败。
兼容性兜底策略
- 降级至传统 OCSP 查询(需客户端支持
OCSP_basic_verify) - 对已知不支持 stapling 的旧客户端(如 Android 4.x)启用
SSL_OP_NO_TLSv1_3临时规避 - 服务端预缓存多签发者 OCSP 响应以覆盖交叉证书链
| 客户端类型 | Stapling 支持 | 推荐 TLS 版本 | 校验行为 |
|---|---|---|---|
| Chrome 90+ | ✅ | TLS 1.3 | 强制验证 |
| iOS 15+ | ✅ | TLS 1.3 | 强制验证 |
| OpenSSL 1.0.2 | ❌ | TLS 1.2 | 回退至在线查询 |
graph TD
A[Client Hello] --> B{Stapling enabled?}
B -->|Yes| C[Require valid OCSP in CertificateStatus]
B -->|No| D[Proceed without stapling check]
C --> E[Verify OCSP signature & nonce & thisUpdate]
E -->|Fail| F[Abort handshake]
第五章:一站式安全通信工程化交付总结
核心交付物清单与版本管控实践
在某省级政务云安全通信平台项目中,我们定义了12类标准化交付物,包括TLS 1.3双向认证配置模板(v2.4.1)、国密SM4-GCM加密网关Ansible Role(v1.7.3)、零信任设备指纹采集SDK(Java/Go双语言,SHA256校验值嵌入CI流水线)。所有制品均通过Nexus Repository Manager 3.58+进行语义化版本管理,并强制关联Git Commit Hash与Jira需求ID(如SEC-COMM-892),确保任意环境回滚可追溯至具体代码行与安全策略变更点。
自动化流水线关键节点验证结果
下表为连续30天生产环境交付流水线的稳定性统计(数据源自Jenkins + Prometheus + Grafana监控栈):
| 阶段 | 平均耗时 | 失败率 | 主要失败原因 |
|---|---|---|---|
| 安全策略静态扫描(Checkov + custom OPA rego) | 42s | 0.8% | SM2证书DN字段长度超限(自动触发修复PR) |
| 国密算法兼容性测试(OpenSSL 3.0.12 + GMSSL 3.1.1) | 187s | 2.1% | TLS握手阶段SM4密钥派生参数协商失败(自动降级至SM4-CBC并告警) |
| 端到端通信压测(wrk + 自研mTLS流量注入器) | 315s | 0.3% | 2000并发下SM2签名验签延迟>150ms(触发熔断并标记性能基线偏差) |
混合加密通道故障自愈机制
当检测到SM2公钥加密层出现持续3次握手超时(阈值:800ms),系统自动执行三级响应:① 切换至RSA-2048备用密钥对(预置在HSM Partition 0x0A);② 向Kubernetes ClusterIP Service注入临时iptables规则,重定向至降级通信Pod组;③ 触发SOP-SEC-07流程,向运维群推送含kubectl get events --field-selector reason=SM2HandshakeFailure命令的可执行诊断卡片。该机制在2024年Q2支撑了17次区域性CA根证书轮换,平均恢复时间(MTTR)为48秒。
# 生产环境一键验证脚本片段(交付包内置)
curl -s https://api.gov-sec.example.com/v1/healthz \
--cert /etc/tls/sm2-client.crt \
--key /etc/tls/sm2-client.key \
--cacert /etc/tls/gmca-root-sm2.pem \
-H "X-Signature: $(echo "$DATE:$URI" | gmssl sm2sign -inkey /etc/tls/sm2-sign.key)" \
| jq '.status, .cipher, .sm2_curve'
多租户策略隔离落地细节
采用eBPF程序bpf_sec_comm.c在内核态实现租户级TLS扩展字段过滤:仅允许指定租户ID(X-Tenant-ID: gov-finance-2024)携带application/vnd.gov.sm2+json MIME类型请求头;非授权租户请求在TCP三次握手完成前即被tc filter add dev eth0 bpf da obj sec_comm.o sec丢弃,避免应用层解析开销。该方案使单节点QPS承载能力从12,000提升至28,500(实测于4核16GB KVM虚拟机)。
安全审计日志结构化规范
所有通信组件输出JSONL格式日志,强制包含event_id(UUIDv4)、tls_version、cipher_suite、sm2_pubkey_hash(SHA3-256)、peer_cert_subject_cn字段,并通过Fluent Bit的filter_kubernetes插件注入namespace与pod_name元数据。审计系统每日生成security-communication-$(date +%Y%m%d).parquet文件,支持Presto SQL按sm2_pubkey_hash IN (SELECT hash FROM threat_ioc WHERE source='CNCERT-2024-Q2')实时关联威胁情报。
工程化交付知识沉淀方式
每个交付版本配套生成delivery-runbook.md,内嵌Mermaid时序图说明密钥生命周期流转:
sequenceDiagram
participant D as DevOps Pipeline
participant H as HSM Cluster
participant K as Kubernetes API Server
D->>H: POST /v1/keys/sm2?tenant=gov-health
H-->>D: 201 {key_id: "sm2-20240517-003"}
D->>K: Apply Secret with key_id reference
K->>H: GET /v1/keys/sm2-20240517-003/public
H-->>K: 200 PEM public key 