feat(dev): add local TLS startup support
This commit is contained in:
@@ -97,6 +97,18 @@ Debug App 通过 USB 连接本机后先执行
|
|||||||
`http://127.0.0.1:8080`、BUYER 账号、设备 ID 和只显示一次的设备密钥登录。
|
`http://127.0.0.1:8080`、BUYER 账号、设备 ID 和只显示一次的设备密钥登录。
|
||||||
正式版后台地址只接受 HTTPS。App 每次由采购员点击“获取任务”,不会自动领单。
|
正式版后台地址只接受 HTTPS。App 每次由采购员点击“获取任务”,不会自动领单。
|
||||||
|
|
||||||
|
局域网 Debug 联调先生成本机 CA 和服务端证书,再启动服务:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\generate-local-tls.ps1 -IPAddress 192.168.0.224
|
||||||
|
.\start-backend.bat --migrate
|
||||||
|
adb push .\.local\cmroubao-tls\ca.crt /sdcard/Download/cmroubao-local-ca.crt
|
||||||
|
```
|
||||||
|
|
||||||
|
在手机系统设置中从下载目录安装 `cmroubao-local-ca.crt` 为 CA 证书;Debug APK 才会
|
||||||
|
信任该用户安装的 CA。App 后端地址填写 `https://192.168.0.224:8080`。证书与私钥均
|
||||||
|
位于被忽略的 `.local/`,不可提交或共享;Windows 防火墙还必须允许入站 TCP 8080。
|
||||||
|
|
||||||
## 文档入口
|
## 文档入口
|
||||||
|
|
||||||
- [AI 开发入口](docs/00-ai-start-here.md)
|
- [AI 开发入口](docs/00-ai-start-here.md)
|
||||||
|
|||||||
@@ -7,4 +7,9 @@
|
|||||||
<certificates src="system" />
|
<certificates src="system" />
|
||||||
</trust-anchors>
|
</trust-anchors>
|
||||||
</base-config>
|
</base-config>
|
||||||
|
<debug-overrides>
|
||||||
|
<trust-anchors>
|
||||||
|
<certificates src="user" />
|
||||||
|
</trust-anchors>
|
||||||
|
</debug-overrides>
|
||||||
</network-security-config>
|
</network-security-config>
|
||||||
|
|||||||
@@ -89,3 +89,8 @@ App 可每 30 秒 best-effort 滑动续期;过期后原设备 heartbeat 只同
|
|||||||
默认 loopback 可使用 HTTP 开发。局域网监听必须同时设置 certificate/private key,
|
默认 loopback 可使用 HTTP 开发。局域网监听必须同时设置 certificate/private key,
|
||||||
服务直接使用 TLS 启动,不会降级为明文。完整 API 合约见
|
服务直接使用 TLS 启动,不会降级为明文。完整 API 合约见
|
||||||
[`../docs/api.md`](../docs/api.md)。
|
[`../docs/api.md`](../docs/api.md)。
|
||||||
|
|
||||||
|
仓库根目录的 `generate-local-tls.ps1 -IPAddress <LAN-IP>` 可为 Debug 联调生成
|
||||||
|
`.local/cmroubao-tls/` 下的本地 CA 和带 IP SAN 的服务端证书;生成后
|
||||||
|
`start-backend.bat` 自动使用证书监听 `0.0.0.0:8080`。手机只安装 `ca.crt`,绝不
|
||||||
|
复制 `ca.key` 或 `server.key`。
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[System.Net.IPAddress]$IPAddress,
|
||||||
|
[string[]]$DnsName = @("localhost")
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$outputDirectory = Join-Path $PSScriptRoot ".local\cmroubao-tls"
|
||||||
|
$caCertificatePath = Join-Path $outputDirectory "ca.crt"
|
||||||
|
$caPrivateKeyPath = Join-Path $outputDirectory "ca.key"
|
||||||
|
$serverCertificatePath = Join-Path $outputDirectory "server.crt"
|
||||||
|
$serverPrivateKeyPath = Join-Path $outputDirectory "server.key"
|
||||||
|
|
||||||
|
if ((Test-Path -LiteralPath $serverCertificatePath -PathType Leaf) -or
|
||||||
|
(Test-Path -LiteralPath $serverPrivateKeyPath -PathType Leaf)) {
|
||||||
|
throw "TLS server certificate already exists: $outputDirectory. Rotate it explicitly before regenerating."
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -LiteralPath $outputDirectory)) {
|
||||||
|
New-Item -ItemType Directory -Path $outputDirectory | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Export-PemCertificate {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$Path
|
||||||
|
)
|
||||||
|
|
||||||
|
[System.IO.File]::WriteAllText(
|
||||||
|
$Path,
|
||||||
|
$Certificate.ExportCertificatePem(),
|
||||||
|
[System.Text.UTF8Encoding]::new($false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Export-PemPrivateKey {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$Path
|
||||||
|
)
|
||||||
|
|
||||||
|
$key = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate)
|
||||||
|
try {
|
||||||
|
[System.IO.File]::WriteAllText(
|
||||||
|
$Path,
|
||||||
|
$key.ExportPkcs8PrivateKeyPem(),
|
||||||
|
[System.Text.UTF8Encoding]::new($false)
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
$key.Dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$notBefore = [DateTimeOffset]::UtcNow.AddMinutes(-5)
|
||||||
|
$notAfter = $notBefore.AddYears(2)
|
||||||
|
$hashAlgorithm = [System.Security.Cryptography.HashAlgorithmName]::SHA256
|
||||||
|
$signaturePadding = [System.Security.Cryptography.RSASignaturePadding]::Pkcs1
|
||||||
|
|
||||||
|
$caKey = [System.Security.Cryptography.RSA]::Create(4096)
|
||||||
|
try {
|
||||||
|
$caRequest = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new(
|
||||||
|
"CN=cmroubao local development CA",
|
||||||
|
$caKey,
|
||||||
|
$hashAlgorithm,
|
||||||
|
$signaturePadding
|
||||||
|
)
|
||||||
|
$caRequest.CertificateExtensions.Add(
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension]::new($true, $false, 0, $true)
|
||||||
|
)
|
||||||
|
$caRequest.CertificateExtensions.Add(
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new(
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::KeyCertSign -bor
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::CrlSign,
|
||||||
|
$true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
$caRequest.CertificateExtensions.Add(
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension]::new($caRequest.PublicKey, $false)
|
||||||
|
)
|
||||||
|
$caCertificate = $caRequest.CreateSelfSigned($notBefore, $notAfter)
|
||||||
|
try {
|
||||||
|
Export-PemCertificate -Certificate $caCertificate -Path $caCertificatePath
|
||||||
|
Export-PemPrivateKey -Certificate $caCertificate -Path $caPrivateKeyPath
|
||||||
|
|
||||||
|
$serverKey = [System.Security.Cryptography.RSA]::Create(2048)
|
||||||
|
try {
|
||||||
|
$serverRequest = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new(
|
||||||
|
"CN=$IPAddress",
|
||||||
|
$serverKey,
|
||||||
|
$hashAlgorithm,
|
||||||
|
$signaturePadding
|
||||||
|
)
|
||||||
|
$serverRequest.CertificateExtensions.Add(
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension]::new($false, $false, 0, $false)
|
||||||
|
)
|
||||||
|
$serverRequest.CertificateExtensions.Add(
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new(
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::DigitalSignature -bor
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::KeyEncipherment,
|
||||||
|
$true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
$serverAuth = [System.Security.Cryptography.OidCollection]::new()
|
||||||
|
$serverAuth.Add([System.Security.Cryptography.Oid]::new("1.3.6.1.5.5.7.3.1")) | Out-Null
|
||||||
|
$serverRequest.CertificateExtensions.Add(
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension]::new($serverAuth, $false)
|
||||||
|
)
|
||||||
|
$subjectAlternativeNames = [System.Security.Cryptography.X509Certificates.SubjectAlternativeNameBuilder]::new()
|
||||||
|
$subjectAlternativeNames.AddIpAddress($IPAddress)
|
||||||
|
foreach ($name in $DnsName) {
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($name)) {
|
||||||
|
$subjectAlternativeNames.AddDnsName($name.Trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$serverRequest.CertificateExtensions.Add($subjectAlternativeNames.Build())
|
||||||
|
$serverRequest.CertificateExtensions.Add(
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension]::new($serverRequest.PublicKey, $false)
|
||||||
|
)
|
||||||
|
$serial = New-Object byte[] 16
|
||||||
|
[System.Security.Cryptography.RandomNumberGenerator]::Fill($serial)
|
||||||
|
$serverCertificate = $serverRequest.Create($caCertificate, $notBefore, $notAfter, $serial)
|
||||||
|
$serverCertificateWithKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::CopyWithPrivateKey(
|
||||||
|
$serverCertificate,
|
||||||
|
$serverKey
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
Export-PemCertificate -Certificate $serverCertificateWithKey -Path $serverCertificatePath
|
||||||
|
Export-PemPrivateKey -Certificate $serverCertificateWithKey -Path $serverPrivateKeyPath
|
||||||
|
} finally {
|
||||||
|
$serverCertificateWithKey.Dispose()
|
||||||
|
$serverCertificate.Dispose()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$serverKey.Dispose()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$caCertificate.Dispose()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$caKey.Dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Created local CA certificate: $caCertificatePath"
|
||||||
|
Write-Host "Created TLS server certificate: $serverCertificatePath"
|
||||||
|
Write-Host "Server SAN IP: $IPAddress"
|
||||||
|
Write-Host "Install only ca.crt on the Debug phone. Never copy ca.key or server.key to the phone."
|
||||||
+20
-1
@@ -3,6 +3,23 @@ setlocal EnableExtensions DisableDelayedExpansion
|
|||||||
|
|
||||||
set "PROJECT_ROOT=%~dp0"
|
set "PROJECT_ROOT=%~dp0"
|
||||||
set "BACKEND_ROOT=%PROJECT_ROOT%backend-api"
|
set "BACKEND_ROOT=%PROJECT_ROOT%backend-api"
|
||||||
|
set "LOCAL_TLS_DIR=%PROJECT_ROOT%.local\cmroubao-tls"
|
||||||
|
set "LOCAL_TLS_CERT=%LOCAL_TLS_DIR%\server.crt"
|
||||||
|
set "LOCAL_TLS_KEY=%LOCAL_TLS_DIR%\server.key"
|
||||||
|
|
||||||
|
if not defined CMROUBAO_HTTP_ADDR (
|
||||||
|
if exist "%LOCAL_TLS_CERT%" if exist "%LOCAL_TLS_KEY%" (
|
||||||
|
set "CMROUBAO_HTTP_ADDR=0.0.0.0:8080"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not defined CMROUBAO_TLS_CERT_FILE (
|
||||||
|
if not defined CMROUBAO_TLS_KEY_FILE (
|
||||||
|
if exist "%LOCAL_TLS_CERT%" if exist "%LOCAL_TLS_KEY%" (
|
||||||
|
set "CMROUBAO_TLS_CERT_FILE=%LOCAL_TLS_CERT%"
|
||||||
|
set "CMROUBAO_TLS_KEY_FILE=%LOCAL_TLS_KEY%"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if not "%~1"=="" if /i not "%~1"=="--migrate" (
|
if not "%~1"=="" if /i not "%~1"=="--migrate" (
|
||||||
echo Usage: %~nx0 [--migrate]
|
echo Usage: %~nx0 [--migrate]
|
||||||
@@ -52,7 +69,9 @@ if /i "%~1"=="--migrate" (
|
|||||||
if errorlevel 1 goto :startup_failed
|
if errorlevel 1 goto :startup_failed
|
||||||
)
|
)
|
||||||
|
|
||||||
if defined CMROUBAO_HTTP_ADDR (
|
if defined CMROUBAO_TLS_CERT_FILE (
|
||||||
|
echo Starting cmroubao backend with TLS at %CMROUBAO_HTTP_ADDR%...
|
||||||
|
) else if defined CMROUBAO_HTTP_ADDR (
|
||||||
echo Starting cmroubao backend with configured CMROUBAO_HTTP_ADDR...
|
echo Starting cmroubao backend with configured CMROUBAO_HTTP_ADDR...
|
||||||
) else (
|
) else (
|
||||||
echo Starting cmroubao backend at 127.0.0.1:8080...
|
echo Starting cmroubao backend at 127.0.0.1:8080...
|
||||||
|
|||||||
Reference in New Issue
Block a user