# 证书

BACEN mTLS 认证、XMLDSig 消息签名和 HTTPS TLS 终止的证书管理。

## 前提条件

- 了解 X.509 证书格式（PEM、PKCS#12）
- 可以访问 ICP-Brasil 证书颁发机构（用于 BACEN 集成）
- 证书管理工具（OpenSSL）

## 证书类型

| 证书 | 用途 | 格式 | 环境变量 |
|------|------|------|----------|
| CPIC | 与 BACEN 的 mTLS 客户端认证 | PEM | `BACEN_CLIENT_CERT_PATH` |
| CPIC 私钥 | mTLS 私钥 | PEM | `BACEN_CLIENT_KEY_PATH` |
| CPIA | XMLDSig 消息签名 (RSA-SHA256) | PEM | 由 CertificatePool 加载 |
| BACEN 根 CA | BACEN 服务器证书验证 | PEM | `BACEN_CA_CERT_PATH` |
| TLS 证书 | HTTPS 终止 | PEM | Ingress/LB 配置 |

## ICP-Brasil 证书

### 获取证书

1. 向 ICP-Brasil 认可的证书颁发机构 (AC) 注册
2. 申请 CPIC（认证）和 CPIA（签名）证书
3. 完成 BCB 的 SPI/DICT 访问注册流程

### 格式转换

Monetarie PIX 需要 PEM 格式。从 PKCS#12 转换：

```bash
# 提取客户端证书
openssl pkcs12 -in institution.p12 -clcerts -nokeys -out cpic.pem

# 提取私钥
openssl pkcs12 -in institution.p12 -nocerts -out cpic-key.pem

# 提取 CA 证书链
openssl pkcs12 -in institution.p12 -cacerts -nokeys -out ca-chain.pem

# 验证证书
openssl x509 -in cpic.pem -text -noout
```

### 证书验证

```bash
# 验证证书链
openssl verify -CAfile bacen-ca.pem cpic.pem

# 检查证书过期时间
openssl x509 -in cpic.pem -enddate -noout

# 测试到 BACEN（同源）的 mTLS 连接
openssl s_client -connect dict-h.pi.rsfn.net.br:16522 \
  -cert cpic.pem -key cpic-key.pem -CAfile bacen-ca.pem
```

## 证书池

`Shared.Crypto.CertificatePool` 是一个在内存中管理证书并自动刷新的 GenServer：

- 启动时加载证书
- 每 5 分钟刷新
- 为 `XmlSigner` 和 `JwsSigner` 提供签名密钥

## XMLDSig 签名规范

### SPI 消息

- 3 个 XML 引用：KeyInfo（按 ID）、AppHdr（空 URI）、Document（无 URI）
- 签名放置在 `<AppHdr>` 内的 `<Sgntr>` 元素中
- 规范化：Exclusive XML C14N (`xml-exc-c14n#`)
- 签名算法：RSA-SHA256
- 摘要算法：SHA-256

### DICT 消息

- 2 个 XML 引用：AppHdr + Document
- 与 SPI 相同的算法

## Kubernetes Secret 挂载

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: bacen-certs
  namespace: pix
type: Opaque
data:
  cpic.pem: <base64-encoded-cert>
  cpic-key.pem: <base64-encoded-key>
  bacen-ca.pem: <base64-encoded-ca>
---
# 在后端 deployment 中
spec:
  containers:
    - name: pix-backend
      volumeMounts:
        - name: bacen-certs
          mountPath: /certs
          readOnly: true
      env:
        - name: BACEN_CA_CERT_PATH
          value: /certs/bacen-ca.pem
        - name: BACEN_CLIENT_CERT_PATH
          value: /certs/cpic.pem
        - name: BACEN_CLIENT_KEY_PATH
          value: /certs/cpic-key.pem
  volumes:
    - name: bacen-certs
      secret:
        secretName: bacen-certs
```

## TLS 证书 (HTTPS)

### Kubernetes (cert-manager)

```yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: pix-tls
  namespace: pix
spec:
  secretName: monetarie-dev-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - pixapi-dev.fluxiq.com.br
    - pixadmin-dev.fluxiq.com.br
```

### 自签名（开发环境）

```bash
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key -out tls.crt \
  -subj "/CN=localhost"
```

## 证书轮换

### BACEN 证书

1. 在当前证书过期前获取新证书
2. 将新证书与现有证书一起挂载
3. 更新环境变量指向新证书
4. 重启后端 pods（滚动更新）
5. 验证 mTLS 连接
6. 移除旧证书

### TLS 证书

使用 cert-manager 时，轮换是自动的。手动管理时：

1. 获取新证书
2. 更新 K8s Secret 或 HAProxy 配置
3. 重载（大多数负载均衡器无停机）

## 预期结果

配置证书后：

- BACEN mTLS 连接已建立（如果 `BACEN_ENABLED=true`）
- XMLDSig 签名可用于 SPI 和 DICT 消息
- HTTPS 端点可通过有效 TLS 访问
- CertificatePool GenServer 中的证书刷新正在运行
