star/utils/cert.go

278 lines
18 KiB
Go
Raw Normal View History

package utils
import (
"b612.me/starcrypto"
"crypto"
"crypto/ecdh"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
2025-06-17 13:10:35 +08:00
"crypto/sha512"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
2025-06-17 13:10:35 +08:00
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"golang.org/x/crypto/pbkdf2"
2025-06-17 13:10:35 +08:00
"io"
"math/big"
"net"
2025-06-17 13:10:35 +08:00
"os"
"strings"
"time"
)
type GenerateCertParams struct {
Country string `json:"country"`
Province string `json:"province"`
City string `json:"city"`
Organization string `json:"organization"`
OrganizationUnit string `json:"organization_unit"`
CommonName string `json:"common_name"`
Dns []string `json:"dns"`
KeyUsage int `json:"key_usage"`
ExtendedKeyUsage []int `json:"extended_key_usage"`
IsCA bool `json:"is_ca"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
MaxPathLength int `json:"max_path_length"`
MaxPathLengthZero bool `json:"max_path_length_zero"`
Type string `json:"type"` // e.g., "x509", "ssh"
Bits int `json:"bits"` // e.g., 2048, 4096
CA *x509.Certificate
CAPriv any // private key for CA, if nil, use the generated key
}
func GenerateKeys(types string, bits int) (crypto.PublicKey, any, error) {
var priv any
var pub crypto.PublicKey
var err error
switch strings.ToLower(types) {
case "rsa":
priv, pub, err = starcrypto.GenerateRsaKey(bits)
if err != nil {
return nil, nil, err
}
case "ecdsa":
var cr elliptic.Curve
switch bits {
case 224:
cr = elliptic.P224()
case 256:
cr = elliptic.P256()
case 384:
cr = elliptic.P384()
case 521:
cr = elliptic.P521()
default:
return nil, nil, errors.New("invalid bits,should be 224,256,384,521")
}
priv, pub, err = starcrypto.GenerateEcdsaKey(cr)
if err != nil {
return nil, nil, err
}
case "ed25519":
pub, priv, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
case "x25519":
priv, err = ecdh.X25519().GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
pub = priv.(*ecdh.PrivateKey).Public()
case "ecdh":
switch bits {
case 256:
priv, err = ecdh.P256().GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
pub = priv.(*ecdh.PrivateKey).Public()
case 384:
priv, err = ecdh.P384().GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
pub = priv.(*ecdh.PrivateKey).Public()
case 521:
priv, err = ecdh.P521().GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
pub = priv.(*ecdh.PrivateKey).Public()
}
default:
return nil, nil, errors.New("invalid key type,only support rsa,ecdsa")
}
return pub, priv, nil
}
func getSlice(val string) []string {
if val == "" {
return nil
}
return []string{val}
}
func GenerateCert(params GenerateCertParams) ([]byte, []byte, error) {
var needAppendCa bool = true
pub, priv, err := GenerateKeys(params.Type, params.Bits)
if err != nil {
return nil, nil, err
}
var extKeyUsage []x509.ExtKeyUsage = nil
for _, usage := range params.ExtendedKeyUsage {
if extKeyUsage == nil {
extKeyUsage = make([]x509.ExtKeyUsage, 0, len(params.ExtendedKeyUsage))
}
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsage(usage))
}
var trueDNS []string
var trueIp []net.IP
for _, v := range params.Dns {
ip := net.ParseIP(v)
if ip == nil {
trueDNS = append(trueDNS, v)
continue
}
trueIp = append(trueIp, ip)
}
cert := &x509.Certificate{
Version: 3,
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
Country: getSlice(params.Country),
Province: getSlice(params.Province),
Locality: getSlice(params.City),
Organization: getSlice(params.Organization),
OrganizationalUnit: getSlice(params.OrganizationUnit),
CommonName: params.CommonName,
},
NotBefore: params.StartDate,
NotAfter: params.EndDate,
BasicConstraintsValid: true,
IsCA: params.IsCA,
MaxPathLenZero: params.MaxPathLengthZero,
ExtKeyUsage: extKeyUsage,
KeyUsage: x509.KeyUsage(params.KeyUsage),
MaxPathLen: params.MaxPathLength,
DNSNames: trueDNS,
IPAddresses: trueIp,
}
if params.CA == nil {
params.CA = cert
needAppendCa = false
}
if params.CAPriv == nil {
params.CAPriv = priv
}
certs, err := x509.CreateCertificate(rand.Reader, cert, params.CA, pub, params.CAPriv)
if err != nil {
return nil, nil, err
}
certBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: certs,
}
pemData := pem.EncodeToMemory(certBlock)
privData, err := starcrypto.EncodePrivateKey(priv, "")
if err != nil {
return nil, nil, err
}
if needAppendCa {
pemData = append(pemData, []byte("\n")...)
certBlock = &pem.Block{
Type: "CERTIFICATE",
Bytes: params.CA.Raw,
}
pemData = append(pemData, pem.EncodeToMemory(certBlock)...)
}
return pemData, privData, nil
}
func GenerateTlsCert(params GenerateCertParams) (tls.Certificate, error) {
certPem, privPem, err := GenerateCert(params)
if err != nil {
return tls.Certificate{}, err
}
return tls.X509KeyPair(certPem, privPem)
}
2025-06-17 13:10:35 +08:00
func ToolCert(aesKey string) (*x509.Certificate, any) {
if aesKey == "" {
aesKey = os.Getenv("B612KEY")
}
if aesKey == "" {
return nil, nil
}
certEncPem := `6af8053260579d345179c6313d99f18a6f85fa74d90b3cc61031211b3a7ced2037a4e317299ada633d7ca925481aba6bc814ee36330261d770040bcffb28d1c15be792b811bbd2d49476d8abc28f3f7e51c9c6c8a306e8443755e699aa98e49fafd886d0f5edec797d6c645fdde7a6f88fba13aeb5e4963d5abf642995577582999422f503740d8f961d03213bbf5b08529ff5a548ef4478b3ef23dfe007be109d17ebc653b34a26d3bef96d7e2085860ab7091863aa964239dcd806ca609fcba7fa9e725549bd42aabab4dd9aaae2e5e4e1427af58e6032cd1ee8ab845910a9fbb14fb4680883aa39d1aef3bcb2725c42ff150c90cb948f33f5d6be6ee99c2c76b900d182695c0150f46dbf43d589e268ddf5d89c855b24a6d9f8080502bdebb5f3e4b61839fe40158d73d7c44ef43c7c382ab3e739e6e0c36fd8e0adb4abdc81c61e01ccbdbc7995b14d7450ed56454e1e42268f516ed2d39af40bbdc6dc9ca97d55e87f187984646a499779466f8af389f15836d7967883aec5442d789ce26c5db9a407241d20fc40554f76289ee2008aa0a85ed3f112b8f36acda8226e72a63eb02943749b1f86b82311509b872db3c5cd99a40946cbf70b9a38385da1d743d58afa7697fc18f478f0cce854e37b4d6e837850bc6d16f951c41412ee2e2eb3113f48912b02c279425c4c3f90795736cb13a824918f744e747eb1677b89e641a3f109198d7375d741172b0cda449290c1d636267952ee006fae5a95163fe38d0a132f761490585a2a952f18f9e4a8a21864ec9831deda2685a182473eba812df82b23096c5fabaea2d6eae89751de9708706fe554aabbcb3ad90fa1fa3776a6cc6927340b404990aebb8fa2d26576db1861b6a057d0414986776d0e3a75c9d2eaed0bc530e41767599ec1c223f845f535d7195940516f7c921ec56d31f8d9f0ecd4b1b1fcf16d9659eef0f77db8636043dcdbd31de364e39ec3d0123f1959604144f32ce896274cd1e80900b64addd652d1167dfc136bfb669145aa5855679e8322ce5b17c272715c70150d8c542f8bdb2e2786e0d1bc79fe1b55510c1dd22d96abc9eef16ae95b52d4d0a935edd0bd1ee3fad9a4822ca4b6c0927d925d97b3843166707edb63fc3635250a93072a890299569f7c699685e29b9c2598891654283287801e20147096cabc37704bbd18d7e67c4d8735bcaeb3118a0dd665fcdda9906a86b94f1cc3d5f15b4d2137b4a7e849b82dd9d1d42947f4535f76ae90068a06b3223863a5773203469cb0fe5fbf326ec6fdeb4be28f829e96e3e35d56c8d53cad3e06222b53ec1d3d9663dfd8737ec73cadba04cd230000bb6dd8cc8d4521da52ee2831e1ed7ac1555b7a9cae1d46781cf262eec4940f9d0e53b63189495583d2b793e65aac806da0cb106f8443c782b96d60a2f7fcbcd459dfc622e374f7534b26817abb3aefcf672ed728e830e7ff095eb4dfc1ed1d9e2e6b864815b1c40e51adc7d767afd670c982d44318a03b25f6629f8082430c5b023b51873067b01a89b022213a5c3006d3bf1a2c5b5505926c8fb0ddf147470e40d56133e4a48b9eafb18b806f14419f8c5a145223872f700fb48601a9bc252f4b538d4e7641e93da13b0f945e6cdaa1927815e3183df589c641363f7623a9924f1af35eb61a91ac5fc19d25f850db067bd2c4218b149d19146bc13505cdc5b5e11fc69302e97ed9e50f75e22a2bb3580a04bc846e2d7230232ea9c7eb1d1b63672a303e6940a2323d636514e60e137129646dea9781b75e7b85639e6d0093e8a9934a4882342c77aff8d5d6be4b983dcd32c649461c07ecb14123b5cfa7b916ebea8c8d925f42efc2ed684644ffa266149df7ec8b11dedb1b80b2e43d9132ce111b31710e4e23db9d6d31351a2fc1fd46a822a71269725a47320155b38267935a622c0d2cacc7603531bf0324ea2fe316a708bf714e2775fd57ec8feac2d5657d6d97167d37050bee0721f8db39159bb50409bf082c12c025f236e20bd9e5f10e5168aba2474cfce478acae36157173e176e47f659bad53458442f2dd691a1c9e71f1dd4463bf5e7a8efc016cca5b6fc3319196a7216ee49721fcb3d5ae055e92f4d4ecfdd343c3ad9e32aaf9dcf7dfa519867c1a9c5cc583912453e52031b1b2caa97f517805d5797a2716ea1e7ff599a8cfe507360e8c8601b19779565bfd95e0dfc30ce7ce1213acec48427000cc2b3e66f0181634fa95e74626fee0cbe9a5f73241c98de079cb42d6eb6e1d2371503142d5193f22fbaab051fd57c7a55ea761be35451410d698a47e5bb0dba019696adbdfda67278d160d319aa7699afcfa9aef1c69751d0f70568537849d86e8775577874f605bcb4af6de5f90e427b0b2ac1decd9b0b88b18bdb7d002006ba3c3ccf9a99f65b82347ffdfef2d123eaba8aaa3df71ea57bae3cfb0773ee61cfe7aba4cb7979d3a01ed3fdf42b5add0c2ed2f5dd02d21ec3939a437d48ea06fc1d333f742e31c8a9335aca24679657a69c63f20cee19416d24b42991241a0186ccabd60cf418f4559a33641629d3101001cc1bebd63c306286fcbd36ae8628d02742160ceb38666a5a37f11862cd8686c512a32e87608328acc15c203a302ddb637963ee0d594b866b52f9fa4747d8b24970470b88fe4be0891222adf2a4f96ed79239cb91f5a9e614a77c3ccd95e99360a4a062f399fd9b446af3f3aae277d3223f4998c7aa1281cf2543a1e3c8e74e4c8b3c1c0e54596c5d2a651e6cab34053544977a3958908283e1ec7907aa938fa361fd8770641747dce451bf789dc02e80549ade940ed59cddeaf3c6931243dc362672c5e715514afb39d0c36f0a19398848e6e8a51919a441eb068c0e
privEncPem := `bcb7368d722f4fd05e0b79946520a106ed891ad31a4a02adcfe6a8ecf5d39db4d87f23d690ef5e31ae99a4210e1429404ec54cdae3a2967d61967bdfe7c97d17d4b359d5d1c28f46423e02073210ce4b90ade1c23c5398e82456ffb31b36a2968730e1a994f48b38a6f6eedcf76da2d99bcb9087701058ceaf1937cc47b6246cc42faf8b8dd44dc79b82f8facd6b400391fcc4c2d2252b469222151d4d0bd9e0c5a553f791b87e8a29517334e866a94421dfda20a2c937f200b414821fe97c6decac9dbee42d21c9224a3bb8e965e417cd2fa40aba60c2c7c84078e4cf4c8d18d196d1dbd51d9108616a8c33474c6ddd89e04aa53bf379ab09b639b8beb7160825642712e791b5b4e4da29f063f57e75bbb640bd8bd50c58652a787d3f299c1b275ab62cbba438d02cb24578ed0bde35f106c5d4b75f9b447f0f3f41cc6e82f779e1aa7bd08a555724f346ea810c8be47a455f8802ed7eee95268ac5ef4a5842ccd9dc0928c7bc3a9e8075af858e221eb93b1bd19b0d66e5058878896209d8f2f45d103521c77f7326c3fca4539ab821636b4de8199e5637dd8ef221c3dede621294a704eb30b613c9c803b16d27694066294cc4cf833a1ac5b0e5e3b27f08eca3d453ad0db99305621538f8f630c08902c8bd5cd0fa8d2651c11701e3ce66794257d75ac79c633d1bf5c92e8d21e2eb001ba1e79fe6ff56eebdc75301e8176398c5c35e831fb9925f2e16300fa6639ad248d2647c2ff82f9f47f776d7c578fc69eae2a54a4ab1516123e6a0aaeb85c881b1994af44c1e6d8f48dc8adcdeb03a80cc142ced5c064b94308ebf871e75784a1b80ec49d1224ed601fe7bc9101fab80b1cc6c9b818b1caea42be140f29b5f6e9e8a70fe3be1d8d208f565b16d1fe6dc3022f9acf437a605a6900fbb3775ce6eac4e55f67c1ab041c998df131c15d4fa0dcf525f0bdceb8f8d63426c315aadce0e61e28cb9bab18a8e88eb248d61deb81a7aa4ae636de0f44f0e4d12ba866bb857d189bf5759e190834b29cc521bff4903159b0cc2d5ee7e09147a7cc04d09a0c32d4f6c3344ecfc44958c055d7109f78798939f1d32ffbb33e1a1176d21250f3d419262babdec315a0e0443fd394210fab868cd57ad0527fa9b6452e14bedca1b61734e4215699116e902e3612f8d6731fa5b9b02a05f07a20602f67ba118bf7ab9059f545eac5719b15c7c0729fbbdcdca6605115a5e50e25d1f8e74b4a01918ca6c6d69eae3e6bb2cbf2104c271eda3688ee60fe2baabdb8a40caca13dbd51c9e35cd06944b3ed7ded769fe56ed4fc3e2fa26938140c666ce9c781944616dbfb5b2605fe4ae16884fb0c0fc92b6af42092261c60005a43bee4a92ecf44c9451c312d7ec719cde5f28220d6d9ca465376c95005756920abbb9ef84bcad3e89bf06e6e31db7111ef3b1c6f2f283be68c9c50a5fa6a0f8080c3707d580465a4d4caf64f4f932cfb550a1361a46611aa3ec16077fec8090080814acfa583a6de894d52125b62f80cca3f32f18845134ed859a0e4afae6634873c889c567afc33d7b4454ce77f83509d25652a189ecd8e9f6d916686069db191794f35c0e5405e850c66c3edb87ecbd69407ace5ef612519f44f7cce0ef205aa7faf514db5215c819f3c6e09fe50803cff110f55e915e264b98307c593b573c9d35f535ef3dd9e4eaead45909e0b2129a21578788ae7b74bea4e7af4fc68253dfd032f5b16e6f6447c3a161b4edde5f3d08be46ac4dd33c1d9323597a42e70efb5ce41469c2eb1e77ce9d59b8bbc85039499ee26d389a43bdec0e23a14f90db7c89020347aa16063caf434dffd93afdbcd0da20c3650c88e316fea148914d4a9d0423caf2c05cded3ca607f84b56771c5b63b61be6d5260e28634f46086e18c166583a763c6f92e268088f8bc0b99611efd17a67b9e4d60e6fff9082f20f001171a63c1e8d13391e360a98f61e9474308552abe190f7d9b74c08eb0adedd28b30b249d99ab4ca3d90ec0d042b1aeeaca58e54e37017ca56a5673d710ad52f3715165bf743961b50dcd5d49d67bec59412b4b9f908417090293232b0c1994bf2753b7fa31d7bd4fb47d266c7b2b36c94be58043eed7380f646a8e5bb453983d5355f5d7b8fd8e7df9721206b85ba4e7c458981e00f793005ec61e39e73e193282c8f42ccc02ad3d9666bf2e6e18e9e9c430fd2220003fe24f96559dc26d23ed90c73a6ed85f0d32b6fe46acffafdd792fb69bfd98d5019b06d4a3ec34f166430995a95b78da961974fe88456e20ad5aec4c5506abafe014eec11ab21017d6d9e7592c9d8cbf7a647278e1b3c0806e871aa42064706c0bc4b8a12dbeaa6f5b526c7b121677ba5f5a94792df20a79c94fd6070008454ba6cf94c63b314d264c2e9f6722b6b4257d4514399c4a7934f6690be810793953a7200269bec65a36aaed3d3fcf55e5f46d7976dc719c78be05a9276a654a905963ad5c1fe0d07d3cf9e96d8ba2f453ce99e2e04b59ae462d690891755b279d16050bf85d280842582463d7675e4068f31e2bf50f157dbbaadb36f1c09028d48f5bfb7bd89f00b5424448976467d3b5435d01163b7f6106dd2c1c948e3950f42fac6a051606b70ea3709b5a0b1826b3fabb9c5feaf72244bf9c24b727b7e6ec3391b0bf3d833cf791976ded63609f731ff0aa050abce73274c6833b81b37ced46ad8d205aa07f561f4d4ee5e0fe19df88b926751369b013e24df12ded98bd5b5b02c1fd1b6b6f92fed1a5ceff3dcc06361ea03f2698e9c307d680a085d4dc5bc3e02a6da7ccc56fb69923f6a99140e02390a4e558b2905ad029281fa1fb779fa57ec6014737326984aa9a836347775e7f4682e999f3eb495732
tmp, _ := hex.DecodeString(certEncPem)
certPem, err := Decode(tmp, aesKey)
if err != nil {
return nil, nil
}
tmp, _ = hex.DecodeString(privEncPem)
privPem, err := Decode(tmp, aesKey)
if err != nil {
return nil, nil
}
block, _ := pem.Decode([]byte(certPem))
if block == nil || block.Type != "CERTIFICATE" {
return nil, nil
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, nil
}
priv, err := starcrypto.DecodePrivateKey([]byte(privPem), "")
if err != nil {
fmt.Println(err)
return nil, nil
}
return cert, priv
}
2025-06-17 13:10:35 +08:00
func Encode(data []byte, key string) ([]byte, error) {
if len(data) == 0 {
return nil, errors.New("data is empty")
}
if key == "" {
return nil, errors.New("key is empty")
}
aesKey := pbkdf2.Key([]byte(key), []byte("b612.me"), 923876, 32, sha512.New)
2025-06-17 13:10:35 +08:00
var iv = make([]byte, 16)
_, err := io.ReadFull(rand.Reader, iv)
2025-06-17 13:10:35 +08:00
if err != nil {
return nil, fmt.Errorf("failed to generate IV: %w", err)
}
ciphertext, err := starcrypto.CustomEncryptAesCFBNoBlock(data, aesKey, iv)
if err != nil {
return nil, fmt.Errorf("failed to encrypt data: %w", err)
}
ciphertext = append(iv, ciphertext...)
return ciphertext, nil
}
func Decode(data []byte, key string) ([]byte, error) {
if len(data) < 16 {
return nil, errors.New("data is too short")
}
if key == "" {
return nil, errors.New("key is empty")
}
aesKey := pbkdf2.Key([]byte(key), []byte("b612.me"), 923876, 32, sha512.New)
2025-06-17 13:10:35 +08:00
iv := data[:16]
ciphertext := data[16:]
plaintext, err := starcrypto.CustomDecryptAesCFBNoBlock(ciphertext, aesKey, iv)
if err != nil {
return nil, fmt.Errorf("failed to decrypt data: %w", err)
}
return plaintext, nil
}