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
|
|||
|
|
}
|