143 lines
4.0 KiB
Go
143 lines
4.0 KiB
Go
package main
|
||
|
||
import (
|
||
"bytes"
|
||
"crypto/ed25519"
|
||
"crypto/rand"
|
||
"crypto/x509"
|
||
"encoding/pem"
|
||
"errors"
|
||
"flag"
|
||
"fmt"
|
||
"go/format"
|
||
"os"
|
||
"path/filepath"
|
||
"runtime"
|
||
"strings"
|
||
)
|
||
|
||
func main() {
|
||
privateOut := flag.String("private-out", "", "根私钥 PEM 输出路径")
|
||
sdkPublicOut := flag.String("sdk-public-out", "", "SDK 根公钥 Go 源码输出路径")
|
||
flag.Parse()
|
||
|
||
if *privateOut == "" || *sdkPublicOut == "" {
|
||
flag.Usage()
|
||
os.Exit(2)
|
||
}
|
||
if sameOutputPath(*privateOut, *sdkPublicOut) {
|
||
fmt.Fprintln(os.Stderr, "根私钥和 SDK 根公钥不能写入同一路径")
|
||
os.Exit(2)
|
||
}
|
||
|
||
publicKey, privateKey, err := generateRoot()
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "生成根密钥失败: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
if err := writePrivateKey(*privateOut, privateKey); err != nil {
|
||
fmt.Fprintf(os.Stderr, "写入根私钥失败: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
if err := writePublicGo(*sdkPublicOut, publicKey); err != nil {
|
||
if cleanupErr := os.Remove(*privateOut); cleanupErr != nil && !errors.Is(cleanupErr, os.ErrNotExist) {
|
||
err = errors.Join(err, fmt.Errorf("清理本次创建的根私钥失败: %w", cleanupErr))
|
||
}
|
||
fmt.Fprintf(os.Stderr, "写入 SDK 根公钥失败: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
}
|
||
|
||
func sameOutputPath(firstPath string, secondPath string) bool {
|
||
firstAbsolute, firstErr := filepath.Abs(firstPath)
|
||
secondAbsolute, secondErr := filepath.Abs(secondPath)
|
||
if firstErr != nil || secondErr != nil {
|
||
return firstPath == secondPath
|
||
}
|
||
|
||
if runtime.GOOS == "windows" {
|
||
return strings.EqualFold(firstAbsolute, secondAbsolute)
|
||
}
|
||
return firstAbsolute == secondAbsolute
|
||
}
|
||
|
||
func generateRoot() (ed25519.PublicKey, ed25519.PrivateKey, error) {
|
||
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
||
if err != nil {
|
||
return nil, nil, fmt.Errorf("生成 Ed25519 根密钥: %w", err)
|
||
}
|
||
return publicKey, privateKey, nil
|
||
}
|
||
|
||
func writePrivateKey(filePath string, privateKey ed25519.PrivateKey) error {
|
||
der, err := x509.MarshalPKCS8PrivateKey(privateKey)
|
||
if err != nil {
|
||
return fmt.Errorf("编码 PKCS#8 私钥: %w", err)
|
||
}
|
||
|
||
pemData := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: der})
|
||
if pemData == nil {
|
||
return errors.New("编码 PEM 私钥失败")
|
||
}
|
||
return writeNewFile(filePath, pemData, 0o600)
|
||
}
|
||
|
||
func writePublicGo(filePath string, publicKey ed25519.PublicKey) error {
|
||
if len(publicKey) != ed25519.PublicKeySize {
|
||
return fmt.Errorf("根公钥长度为 %d,期望 %d", len(publicKey), ed25519.PublicKeySize)
|
||
}
|
||
|
||
var source bytes.Buffer
|
||
source.WriteString("package licence\n\nimport \"crypto/ed25519\"\n\nvar rootPublicKey = [ed25519.PublicKeySize]byte{")
|
||
for index, value := range publicKey {
|
||
if index%8 == 0 {
|
||
source.WriteString("\n\t")
|
||
}
|
||
fmt.Fprintf(&source, "0x%02x, ", value)
|
||
}
|
||
source.WriteString("\n}\n")
|
||
|
||
formatted, err := format.Source(source.Bytes())
|
||
if err != nil {
|
||
return fmt.Errorf("格式化 SDK 根公钥源码: %w", err)
|
||
}
|
||
return writeNewFile(filePath, formatted, 0o644)
|
||
}
|
||
|
||
func writeNewFile(filePath string, data []byte, mode os.FileMode) (resultErr error) {
|
||
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
closed := false
|
||
completed := false
|
||
defer func() {
|
||
if completed {
|
||
return
|
||
}
|
||
if !closed {
|
||
if closeErr := file.Close(); closeErr != nil {
|
||
resultErr = errors.Join(resultErr, fmt.Errorf("关闭未完成的输出文件: %w", closeErr))
|
||
}
|
||
}
|
||
if removeErr := os.Remove(filePath); removeErr != nil && !errors.Is(removeErr, os.ErrNotExist) {
|
||
resultErr = errors.Join(resultErr, fmt.Errorf("清理未完成的输出文件: %w", removeErr))
|
||
}
|
||
}()
|
||
|
||
written, err := file.Write(data)
|
||
if err != nil {
|
||
return fmt.Errorf("写入输出文件: %w", err)
|
||
}
|
||
if written != len(data) {
|
||
return fmt.Errorf("写入输出文件不完整: 已写入 %d 字节,期望 %d 字节", written, len(data))
|
||
}
|
||
if err := file.Close(); err != nil {
|
||
closed = true
|
||
return fmt.Errorf("关闭输出文件: %w", err)
|
||
}
|
||
closed = true
|
||
completed = true
|
||
return nil
|
||
}
|