Explorar o código

首次功能提交

完成版本v1.0.0
SongZihuan hai 2 meses
achega
dedc058ab8

+ 71 - 0
.gitattributes

@@ -0,0 +1,71 @@
+# 设置所有文本文件使用LF作为行尾
+* text=auto eol=lf
+
+# 排除图片资源文件(保持之前的配置)
+*.png binary
+*.jpg binary
+*.jpeg binary
+*.gif binary
+
+# 排除PDF文档(保持之前的配置)
+*.pdf binary
+
+# 排除视频资源文件(保持之前的配置)
+*.mp4 binary
+*.avi binary
+*.mkv binary
+*.mov binary
+*.wmv binary
+
+# 排除字体资源文件
+*.ttf binary
+*.otf binary
+*.woff binary
+*.woff2 binary
+*.eot binary
+
+# 排除音频文件
+*.mp3 binary
+*.wav binary
+*.aac binary
+*.flac binary
+
+# 排除压缩文件和存档
+*.zip binary
+*.tar.gz binary
+*.rar binary
+
+# 排除数据库文件
+*.db binary
+
+# 排除机器学习模型文件
+*.pb binary
+*.ckpt binary
+*.pth binary
+
+# 排除编译后的二进制可执行文件
+*.exe binary
+*.dll binary
+*.so binary
+*.dylib binary
+
+# 排除虚拟机和容器镜像
+*.dockerimage binary
+*.box binary
+*.vmdk binary
+*.vhd binary
+*.ova binary
+
+# 排除游戏资源文件
+*.obj binary
+*.fbx binary
+*.blend binary
+*.dds binary
+*.3ds binary
+
+# 排除日志文件
+*.log binary
+
+# 排除缓存和临时文件
+*.cache binary
+*.tmp binary

+ 15 - 0
.gitignore

@@ -0,0 +1,15 @@
+.idea
+etc
+tmp
+
+*.exe
+*.out
+
+.DS_Store
+
+testdata
+remote-testdata
+
+pkg
+
+go-remote.sh

+ 8 - 0
LICENSE

@@ -0,0 +1,8 @@
+The MIT License (MIT)
+Copyright © 2025 宋子桓(Song Zihuan)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 39 - 0
README.md

@@ -0,0 +1,39 @@
+# Huan-MyCA
+## 介绍
+这是一个简单的 CA 工具,可以生成自签名CA(根CA)、中间CA、终端证书(由CA签名)或自签名终端证书。
+其中,终端证书支持添加域名、IP。
+
+## 构建
+使用`go build`命令进行构建,具体如下:
+```shell
+$ go build github.com/SongZihuan/MyCA/src/cmd/myca/mycav1
+```
+
+当然,你可以添加参数来优化构建目标:
+```shell
+$ go build -trimpath -ldflags='-s -w' github.com/SongZihuan/MyCA/src/cmd/myca/mycav1
+```
+
+具体编译参数可参见`go`的相关文档。
+
+## 运行
+编译完成后,在对应平台执行可执行程序文件即可。支持下列参数:
+```text
+Usage:
+  -h    show help
+  -help
+        show help
+  -home string
+        set home directory (default "~/.myca")
+  -v    show version
+  -version
+        show version
+```
+
+- `-h`和`-help`可显示帮助信息。
+- `-v`和`-version`可显示版本信息。
+- `-home`设置项目根目录,默认为用户家目录下的`.myca`文件夹。
+
+## 协议
+本软件基于 [MIT LICENSE](/LICENSE) 发布。
+了解更多关于 MIT LICENSE , 请 [点击此处](https://mit-license.song-zh.com) 。

+ 13 - 0
REEPORT

@@ -0,0 +1,13 @@
+How to report of Huan-MyCA
+
+Author: 宋子桓(Song Zihuan)
+Author Github: https://github.com/SongZihuan
+Author Website: https://song-zh.com
+Author Email: contact@song-zh.com
+
+Github: https://github.com/SongZihuan/MyCA/
+Github Issues: https://github.com/SongZihuan/MyCA/issues
+
+Report: You can report issues and contact the author through Github Issues or Author Email.
+Quality Assurance: If you only have the license in the LICENSE file in the root directory of this project, you will not get any quality assurance. But the author is happy to solve the problem for you, unless this project has been archived as read-only.
+Other Fork versions: Please contact the author of the Fork version for assistance.

+ 1 - 0
VERSION

@@ -0,0 +1 @@
+v1.0.0

+ 11 - 0
go.mod

@@ -0,0 +1,11 @@
+module github.com/SongZihuan/MyCA
+
+go 1.22
+
+require github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78
+
+require (
+	golang.org/x/crypto v0.33.0 // indirect
+	golang.org/x/sys v0.30.0 // indirect
+	golang.org/x/term v0.29.0 // indirect
+)

+ 8 - 0
go.sum

@@ -0,0 +1,8 @@
+github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
+github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
+golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
+golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=

+ 14 - 0
resource.go

@@ -0,0 +1,14 @@
+package resource
+
+import (
+	_ "embed"
+)
+
+//go:embed VERSION
+var Version string
+
+//go:embed LICENSE
+var License string
+
+//go:embed REEPORT
+var Report string

+ 203 - 0
src/cert/cert.go

@@ -0,0 +1,203 @@
+package cert
+
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"fmt"
+	"github.com/SongZihuan/MyCA/src/utils"
+	"math/big"
+	"net"
+	"time"
+)
+
+// CreateCert 创建由CA签名的IP、域名证书
+func CreateCert(cryptoType utils.CryptoType, keyLength int, org string, domains []string, ips []net.IP, notBefore time.Time, notAfter time.Time, caSerialNumber *utils.FileTack[*big.Int], ca *x509.Certificate, caKey crypto.PrivateKey) (*x509.Certificate, crypto.PrivateKey, error) {
+	var privKey crypto.PrivateKey
+	var pubKey interface{}
+
+	cn := ""
+	if len(domains) == 0 && len(ips) == 0 {
+		return nil, nil, fmt.Errorf("no domains or IPs")
+	} else if len(domains) != 0 {
+		cn = domains[0]
+	} else if len(ips) != 0 {
+		cn = ips[0].String()
+	}
+
+	switch cryptoType {
+	case utils.CryptoTypeRsa:
+		if keyLength != 2048 && keyLength != 4096 {
+			return nil, nil, fmt.Errorf("unsupported RSA key length: %d", keyLength)
+		}
+
+		priv, err := rsa.GenerateKey(utils.Rander(), keyLength)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		privKey = priv
+		pubKey = &priv.PublicKey
+	case utils.CryptoTypeEcc:
+		fallthrough
+	case utils.CryptoTypeEcdsa:
+		var curve elliptic.Curve
+		switch keyLength {
+		case 256:
+			curve = elliptic.P256()
+		case 384:
+			curve = elliptic.P384()
+		case 521:
+			curve = elliptic.P521()
+		default:
+			return nil, nil, fmt.Errorf("unsupported ECC key length: %d", keyLength)
+		}
+		priv, err := ecdsa.GenerateKey(curve, utils.Rander())
+		if err != nil {
+			return nil, nil, err
+		}
+		privKey = priv
+		pubKey = &priv.PublicKey
+	default:
+		return nil, nil, fmt.Errorf("unsupported crypto type: %s", cryptoType)
+	}
+
+	if org == "" {
+		org = "MyOrg"
+	}
+
+	if notBefore.Equal(time.Time{}) {
+		notBefore = time.Now()
+	}
+
+	if notAfter.Equal(time.Time{}) {
+		notAfter = notBefore.Add(time.Hour * 24 * 365 * 5) // 5年
+	}
+
+	caSerialNumber.Value = new(big.Int).Add(caSerialNumber.Value, big.NewInt(1))
+
+	template := &x509.Certificate{
+		SerialNumber: caSerialNumber.Value,
+		Subject: pkix.Name{
+			Organization: []string{org},
+			CommonName:   cn,
+		},
+		NotBefore: notBefore,
+		NotAfter:  notAfter,
+
+		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, // 允许全部扩展用途
+		BasicConstraintsValid: true,
+		IsCA:                  false,
+		DNSNames:              domains,
+		IPAddresses:           ips,
+	}
+
+	derBytes, err := x509.CreateCertificate(utils.Rander(), template, ca, pubKey, caKey)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	cert, err := x509.ParseCertificate(derBytes)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return cert, privKey, nil
+}
+
+// CreateSelfCert 创建自签名域名、IP证书
+func CreateSelfCert(cryptoType utils.CryptoType, keyLength int, org string, domains []string, ips []net.IP, notBefore time.Time, notAfter time.Time) (*x509.Certificate, crypto.PrivateKey, error) {
+	var privKey crypto.PrivateKey
+	var pubKey interface{}
+
+	cn := ""
+	if len(domains) == 0 && len(ips) == 0 {
+		return nil, nil, fmt.Errorf("no domains or IPs")
+	} else if len(domains) != 0 {
+		cn = domains[0]
+	} else if len(ips) != 0 {
+		cn = ips[0].String()
+	}
+
+	switch cryptoType {
+	case utils.CryptoTypeRsa:
+		if keyLength != 2048 && keyLength != 4096 {
+			return nil, nil, fmt.Errorf("unsupported RSA key length: %d", keyLength)
+		}
+
+		priv, err := rsa.GenerateKey(utils.Rander(), keyLength)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		privKey = priv
+		pubKey = &priv.PublicKey
+	case utils.CryptoTypeEcc:
+		fallthrough
+	case utils.CryptoTypeEcdsa:
+		var curve elliptic.Curve
+		switch keyLength {
+		case 256:
+			curve = elliptic.P256()
+		case 384:
+			curve = elliptic.P384()
+		case 521:
+			curve = elliptic.P521()
+		default:
+			return nil, nil, fmt.Errorf("unsupported ECC key length: %d", keyLength)
+		}
+		priv, err := ecdsa.GenerateKey(curve, utils.Rander())
+		if err != nil {
+			return nil, nil, err
+		}
+		privKey = priv
+		pubKey = &priv.PublicKey
+	default:
+		return nil, nil, fmt.Errorf("unsupported crypto type: %s", cryptoType)
+	}
+
+	if org == "" {
+		org = "MyOrg"
+	}
+
+	if notBefore.Equal(time.Time{}) {
+		notBefore = time.Now()
+	}
+
+	if notAfter.Equal(time.Time{}) {
+		notAfter = notBefore.Add(time.Hour * 24 * 365 * 5) // 5年
+	}
+
+	template := &x509.Certificate{
+		SerialNumber: big.NewInt(1),
+		Subject: pkix.Name{
+			Organization: []string{org},
+			CommonName:   cn,
+		},
+		NotBefore: notBefore,
+		NotAfter:  notAfter,
+
+		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, // 允许全部扩展用途
+		BasicConstraintsValid: true,
+		IsCA:                  false,
+		DNSNames:              domains,
+		IPAddresses:           ips,
+	}
+
+	derBytes, err := x509.CreateCertificate(utils.Rander(), template, template, pubKey, privKey)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	cert, err := x509.ParseCertificate(derBytes)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return cert, privKey, nil
+}

+ 10 - 0
src/cmd/myca/mycav1/main.go

@@ -0,0 +1,10 @@
+package main
+
+import (
+	"github.com/SongZihuan/MyCA/src/mainfunc/myca/mycav1"
+	"os"
+)
+
+func main() {
+	os.Exit(mycav1.MainV1())
+}

+ 63 - 0
src/flagparser/flagparser.go

@@ -0,0 +1,63 @@
+package flagparser
+
+import (
+	"flag"
+	"fmt"
+	resource "github.com/SongZihuan/MyCA"
+	"os"
+	"os/user"
+	"path"
+)
+
+var help bool
+var version bool
+var Home string
+
+var StopRun = fmt.Errorf("stop run")
+
+func InitFlagParser() error {
+	currentUser, err := user.Current()
+	if err != nil {
+		return err
+	}
+
+	flag.BoolVar(&help, "help", false, "show help")
+	flag.BoolVar(&help, "h", false, "show help")
+	flag.BoolVar(&version, "version", false, "show version")
+	flag.BoolVar(&version, "v", false, "show version")
+	flag.StringVar(&Home, "home", path.Join(currentUser.HomeDir, ".myca"), "set home directory")
+
+	flag.Parse()
+
+	if help {
+		flag.Usage()
+		return StopRun
+	}
+
+	if version {
+		fmt.Println("Version: ", resource.Version)
+		return StopRun
+	}
+
+	err = os.MkdirAll(Home, 0600)
+	if err != nil {
+		return err
+	}
+
+	err = os.MkdirAll(path.Join(Home, "rca"), 0600)
+	if err != nil {
+		return err
+	}
+
+	err = os.MkdirAll(path.Join(Home, "ica"), 0600)
+	if err != nil {
+		return err
+	}
+
+	err = os.MkdirAll(path.Join(Home, "cert"), 0600)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

+ 104 - 0
src/ica/ica.go

@@ -0,0 +1,104 @@
+package ica
+
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"fmt"
+	"github.com/SongZihuan/MyCA/src/utils"
+	"math/big"
+	"time"
+)
+
+// CreateICA 创建中间CA证书
+func CreateICA(cryptoType utils.CryptoType, keyLength int, org string, cn string, notBefore time.Time, notAfter time.Time, rootSerialNumber *utils.FileTack[*big.Int], rootCert *x509.Certificate, rootKey crypto.PrivateKey) (*x509.Certificate, crypto.PrivateKey, error) {
+	var privKey crypto.PrivateKey
+	var pubKey interface{}
+
+	switch cryptoType {
+	case utils.CryptoTypeRsa:
+		if keyLength != 2048 && keyLength != 4096 {
+			return nil, nil, fmt.Errorf("unsupported RSA key length: %d", keyLength)
+		}
+
+		priv, err := rsa.GenerateKey(utils.Rander(), keyLength)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		privKey = priv
+		pubKey = &priv.PublicKey
+	case utils.CryptoTypeEcc:
+		fallthrough
+	case utils.CryptoTypeEcdsa:
+		var curve elliptic.Curve
+		switch keyLength {
+		case 256:
+			curve = elliptic.P256()
+		case 384:
+			curve = elliptic.P384()
+		case 521:
+			curve = elliptic.P521()
+		default:
+			return nil, nil, fmt.Errorf("unsupported ECC key length: %d", keyLength)
+		}
+		priv, err := ecdsa.GenerateKey(curve, utils.Rander())
+		if err != nil {
+			return nil, nil, err
+		}
+		privKey = priv
+		pubKey = &priv.PublicKey
+	default:
+		return nil, nil, fmt.Errorf("unsupported crypto type: %s", cryptoType)
+	}
+
+	if org == "" {
+		org = "MyOrg"
+	}
+
+	if cn == "" {
+		cn = fmt.Sprintf("I%02d", utils.RandIntn(98)+1) // 数字范围1-99
+	}
+
+	if notBefore.Equal(time.Time{}) {
+		notBefore = time.Now()
+	}
+
+	if notAfter.Equal(time.Time{}) {
+		notAfter = notBefore.Add(time.Hour * 24 * 365 * 5) // 5年
+	}
+
+	rootSerialNumber.Value = new(big.Int).Add(rootSerialNumber.Value, big.NewInt(1))
+
+	template := &x509.Certificate{
+		SerialNumber: rootSerialNumber.Value,
+		Subject: pkix.Name{
+			Organization: []string{org},
+			CommonName:   cn,
+		},
+		NotBefore: notBefore,
+		NotAfter:  notAfter,
+
+		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, // 允许全部扩展用途
+		BasicConstraintsValid: true,
+		IsCA:                  true,
+		MaxPathLen:            -1,
+		MaxPathLenZero:        false,
+	}
+
+	derBytes, err := x509.CreateCertificate(utils.Rander(), template, rootCert, pubKey, rootKey)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	cert, err := x509.ParseCertificate(derBytes)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return cert, privKey, nil
+}

+ 721 - 0
src/mainfunc/myca/mycav1/action.go

@@ -0,0 +1,721 @@
+package mycav1
+
+import (
+	"fmt"
+	"github.com/SongZihuan/MyCA/src/cert"
+	"github.com/SongZihuan/MyCA/src/ica"
+	"github.com/SongZihuan/MyCA/src/rootca"
+	"github.com/SongZihuan/MyCA/src/utils"
+	"math/big"
+	"net"
+	"os"
+	"path"
+	"time"
+)
+
+func ShowAllRCA() {
+	showAllRCA()
+}
+
+func ShowAllICA() {
+	showAllICA()
+}
+
+func CreateRCA() {
+	fmt.Println(cryptoMenu)
+	fmt.Printf(">>> ")
+
+	var cryptoType utils.CryptoType
+	var keyLength int
+
+	switch ReadNumber() {
+	case 1:
+		cryptoType = utils.CryptoTypeRsa
+		keyLength = 2048
+	case 2:
+		cryptoType = utils.CryptoTypeRsa
+		keyLength = 4096
+	case 0:
+		fallthrough
+	default:
+		fmt.Println("Warn: Use Default")
+		fallthrough
+	case 3:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 256
+	case 4:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 384
+	case 5:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 521
+	}
+
+	fmt.Println("Crypto: ", cryptoType, keyLength)
+
+	fmt.Printf("Org: ")
+	org := ReadString()
+
+	fmt.Printf("Common Name: ")
+	cn := ReadString()
+
+	fmt.Printf("Validity: ")
+	validity := ReadTimeDuration(time.Hour * 24 * 365 * 10)
+
+	notBefore := time.Now()
+	notAfter := notBefore.Add(validity)
+
+	caCert, key, err := rootca.CreateRCA(cryptoType, keyLength, org, cn, notBefore, notAfter)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	dirPath := path.Join(home, "rca", fmt.Sprintf("%s-%s", caCert.Subject.Organization[0], caCert.Subject.CommonName))
+	cert1Path := path.Join(dirPath, "cert.pem")
+	cert2Path := path.Join(dirPath, "cert.cer")
+	fullchain1Path := path.Join(dirPath, "fullchain.pem")
+	fullchain2Path := path.Join(dirPath, "fullchain.cer")
+	serialNumberPath := path.Join(dirPath, "serial.num")
+	keyPath := path.Join(dirPath, "key.pem")
+
+	if utils.IsExists(cert1Path) || utils.IsExists(cert2Path) || utils.IsExists(fullchain1Path) || utils.IsExists(fullchain2Path) || utils.IsExists(keyPath) {
+		fmt.Printf("There is a duplicate file, it will be overwritten. Do you confirm to save the certificate? [yes/no] ")
+		if !ReadYesMust() {
+			return
+		}
+	} else {
+		fmt.Printf("Do you confirm to save the certificate? [yes/no] ")
+		if !ReadYes() {
+			return
+		}
+	}
+
+	fmt.Printf("Set a password for private key: ")
+	password := ReadPassword()
+
+	err = os.MkdirAll(dirPath, 0600)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.SaveCertificate(caCert, []byte{}, cert1Path, cert2Path, fullchain1Path, fullchain2Path)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.SavePrivateKey(key, password, keyPath)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.WriteBigIntToFile(serialNumberPath, big.NewInt(0))
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	fmt.Println("Success, save directory: ", dirPath)
+}
+
+func CreateICAFromRCA() {
+	rcaCert, rcaKey, rcaFullchain, rcaSerialNumber, err := LoadRCA()
+	if err != nil {
+		fmt.Println("Error:", err.Error())
+		return
+	}
+
+	fmt.Println(cryptoMenu)
+	fmt.Printf(">>> ")
+
+	var cryptoType utils.CryptoType
+	var keyLength int
+
+	switch ReadNumber() {
+	case 1:
+		cryptoType = utils.CryptoTypeRsa
+		keyLength = 2048
+	case 2:
+		cryptoType = utils.CryptoTypeRsa
+		keyLength = 4096
+	case 0:
+		fallthrough
+	default:
+		fmt.Println("Warn: Use Default")
+		fallthrough
+	case 3:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 256
+	case 4:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 384
+	case 5:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 521
+	}
+
+	fmt.Println("Crypto: ", cryptoType, keyLength)
+
+	fmt.Printf("Org: ")
+	org := ReadString()
+
+	fmt.Printf("Common Name: ")
+	cn := ReadString()
+
+	fmt.Printf("Validity: ")
+	validity := ReadTimeDuration(time.Hour * 24 * 365 * 5)
+
+	notBefore := time.Now()
+	notAfter := notBefore.Add(validity)
+
+	caCert, key, err := ica.CreateICA(cryptoType, keyLength, org, cn, notBefore, notAfter, rcaSerialNumber, rcaCert, rcaKey)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	dirPath := path.Join(home, "ica", fmt.Sprintf("%s-%s", caCert.Subject.Organization[0], caCert.Subject.CommonName))
+	cert1Path := path.Join(dirPath, "cert.pem")
+	cert2Path := path.Join(dirPath, "cert.cer")
+	fullchain1Path := path.Join(dirPath, "fullchain.pem")
+	fullchain2Path := path.Join(dirPath, "fullchain.cer")
+	serialNumberPath := path.Join(dirPath, "serial.num")
+	keyPath := path.Join(dirPath, "key.pem")
+
+	if utils.IsExists(cert1Path) || utils.IsExists(cert2Path) || utils.IsExists(fullchain1Path) || utils.IsExists(fullchain2Path) || utils.IsExists(keyPath) {
+		fmt.Printf("There is a duplicate file, it will be overwritten. Do you confirm to save the certificate? [yes/no] ")
+		if !ReadYesMust() {
+			return
+		}
+	} else {
+		fmt.Printf("Do you confirm to save the certificate? [yes/no] ")
+		if !ReadYes() {
+			return
+		}
+	}
+
+	fmt.Printf("Set a password for private key: ")
+	password := ReadPassword()
+
+	err = os.MkdirAll(dirPath, 0600)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.SaveCertificate(caCert, rcaFullchain, cert1Path, cert2Path, fullchain1Path, fullchain2Path)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.SavePrivateKey(key, password, keyPath)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.WriteBigIntToFile(serialNumberPath, big.NewInt(0))
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = rcaSerialNumber.Save()
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	fmt.Println("Success, save directory: ", dirPath)
+}
+
+func CreateICAFromICA() {
+	icaCert, icaKey, icaFullchain, icaSerialNumber, err := LoadICA()
+	if err != nil {
+		fmt.Println("Error:", err.Error())
+		return
+	}
+
+	fmt.Println(cryptoMenu)
+	fmt.Printf(">>> ")
+
+	var cryptoType utils.CryptoType
+	var keyLength int
+
+	switch ReadNumber() {
+	case 1:
+		cryptoType = utils.CryptoTypeRsa
+		keyLength = 2048
+	case 2:
+		cryptoType = utils.CryptoTypeRsa
+		keyLength = 4096
+	case 0:
+		fallthrough
+	default:
+		fmt.Println("Warn: Use Default")
+		fallthrough
+	case 3:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 256
+	case 4:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 384
+	case 5:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 521
+	}
+
+	fmt.Println("Crypto: ", cryptoType, keyLength)
+
+	fmt.Printf("Org: ")
+	org := ReadString()
+
+	fmt.Printf("Common Name: ")
+	cn := ReadString()
+
+	fmt.Printf("Validity: ")
+	validity := ReadTimeDuration(time.Hour * 24 * 365 * 5)
+
+	notBefore := time.Now()
+	notAfter := notBefore.Add(validity)
+
+	caCert, key, err := ica.CreateICA(cryptoType, keyLength, org, cn, notBefore, notAfter, icaSerialNumber, icaCert, icaKey)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	dirPath := path.Join(home, "ica", fmt.Sprintf("%s-%s", caCert.Subject.Organization[0], caCert.Subject.CommonName))
+	cert1Path := path.Join(dirPath, "cert.pem")
+	cert2Path := path.Join(dirPath, "cert.cer")
+	fullchain1Path := path.Join(dirPath, "fullchain.pem")
+	fullchain2Path := path.Join(dirPath, "fullchain.cer")
+	serialNumberPath := path.Join(dirPath, "serial.num")
+	keyPath := path.Join(dirPath, "key.pem")
+
+	if utils.IsExists(cert1Path) || utils.IsExists(cert2Path) || utils.IsExists(fullchain1Path) || utils.IsExists(fullchain2Path) || utils.IsExists(keyPath) {
+		fmt.Printf("There is a duplicate file, it will be overwritten. Do you confirm to save the certificate? [yes/no] ")
+		if !ReadYesMust() {
+			return
+		}
+	} else {
+		fmt.Printf("Do you confirm to save the certificate? [yes/no] ")
+		if !ReadYes() {
+			return
+		}
+	}
+
+	fmt.Printf("Set a password for private key: ")
+	password := ReadPassword()
+
+	err = os.MkdirAll(dirPath, 0600)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.SaveCertificate(caCert, icaFullchain, cert1Path, cert2Path, fullchain1Path, fullchain2Path)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.SavePrivateKey(key, password, keyPath)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.WriteBigIntToFile(serialNumberPath, big.NewInt(0))
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = icaSerialNumber.Save()
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	fmt.Println("Success, save directory: ", dirPath)
+}
+
+func CreateUserCertFromRCA() {
+	rcaCert, rcaKey, rcaFullchain, rcaSerialNumber, err := LoadRCA()
+	if err != nil {
+		fmt.Println("Error:", err.Error())
+		return
+	}
+
+	fmt.Println(cryptoMenu)
+	fmt.Printf(">>> ")
+
+	var cryptoType utils.CryptoType
+	var keyLength int
+
+	switch ReadNumber() {
+	case 1:
+		cryptoType = utils.CryptoTypeRsa
+		keyLength = 2048
+	case 2:
+		cryptoType = utils.CryptoTypeRsa
+		keyLength = 4096
+	case 0:
+		fallthrough
+	default:
+		fmt.Println("Warn: Use Default")
+		fallthrough
+	case 3:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 256
+	case 4:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 384
+	case 5:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 521
+	}
+
+	fmt.Println("Crypto: ", cryptoType, keyLength)
+
+	fmt.Printf("Org: ")
+	org := ReadString()
+
+	fmt.Printf("Validity: ")
+	validity := ReadTimeDuration(time.Hour * 24 * 365 * 5)
+
+	notBefore := time.Now()
+	notAfter := notBefore.Add(validity)
+
+	domains := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your domain [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			domains = append(domains, res)
+		}
+	}
+
+	ips := make([]net.IP, 0, 10)
+	for {
+		fmt.Printf("Enter your ip [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			ip := net.ParseIP(res)
+			if ip == nil {
+				fmt.Println("Error: not a valid ip")
+			}
+
+			ips = append(ips, ip)
+		}
+	}
+
+	userCert, key, err := cert.CreateCert(cryptoType, keyLength, org, domains, ips, notBefore, notAfter, rcaSerialNumber, rcaCert, rcaKey)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	dirPath := path.Join(home, "cert", fmt.Sprintf("%s-%s-%s-%s", userCert.Subject.Organization[0], rcaCert.Subject.CommonName, userCert.Subject.CommonName, userCert.NotBefore.Format("2006-01-02-15-04-05")))
+	cert1Path := path.Join(dirPath, "cert.pem")
+	cert2Path := path.Join(dirPath, "cert.cer")
+	fullchain1Path := path.Join(dirPath, "fullchain.pem")
+	fullchain2Path := path.Join(dirPath, "fullchain.cer")
+	keyPath := path.Join(dirPath, "key.pem")
+
+	if utils.IsExists(cert1Path) || utils.IsExists(cert2Path) || utils.IsExists(fullchain1Path) || utils.IsExists(fullchain2Path) || utils.IsExists(keyPath) {
+		fmt.Printf("There is a duplicate file, it will be overwritten. Do you confirm to save the certificate? [yes/no] ")
+		if !ReadYesMust() {
+			return
+		}
+	} else {
+		fmt.Printf("Do you confirm to save the certificate? [yes/no] ")
+		if !ReadYes() {
+			return
+		}
+	}
+
+	fmt.Printf("Set a password for private key: ")
+	password := ReadPassword()
+
+	err = os.MkdirAll(dirPath, 0600)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.SaveCertificate(userCert, rcaFullchain, cert1Path, cert2Path, fullchain1Path, fullchain2Path)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.SavePrivateKey(key, password, keyPath)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = rcaSerialNumber.Save()
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	fmt.Println("Success, save directory: ", dirPath)
+}
+
+func CreateUserCertFromICA() {
+	icaCert, icaKey, icaFullchain, icaSerialNumber, err := LoadICA()
+	if err != nil {
+		fmt.Println("Error:", err.Error())
+		return
+	}
+
+	fmt.Println(cryptoMenu)
+	fmt.Printf(">>> ")
+
+	var cryptoType utils.CryptoType
+	var keyLength int
+
+	switch ReadNumber() {
+	case 1:
+		cryptoType = utils.CryptoTypeRsa
+		keyLength = 2048
+	case 2:
+		cryptoType = utils.CryptoTypeRsa
+		keyLength = 4096
+	case 0:
+		fallthrough
+	default:
+		fmt.Println("Warn: Use Default")
+		fallthrough
+	case 3:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 256
+	case 4:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 384
+	case 5:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 521
+	}
+
+	fmt.Println("Crypto: ", cryptoType, keyLength)
+
+	fmt.Printf("Org: ")
+	org := ReadString()
+
+	fmt.Printf("Validity: ")
+	validity := ReadTimeDuration(time.Hour * 24 * 365 * 5)
+
+	notBefore := time.Now()
+	notAfter := notBefore.Add(validity)
+
+	domains := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your domain [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			domains = append(domains, res)
+		}
+	}
+
+	ips := make([]net.IP, 0, 10)
+	for {
+		fmt.Printf("Enter your ip [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			ip := net.ParseIP(res)
+			if ip == nil {
+				fmt.Println("Error: not a valid ip")
+			}
+
+			ips = append(ips, ip)
+		}
+	}
+
+	userCert, key, err := cert.CreateCert(cryptoType, keyLength, org, domains, ips, notBefore, notAfter, icaSerialNumber, icaCert, icaKey)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	dirPath := path.Join(home, "cert", fmt.Sprintf("%s-%s-%s-%s", userCert.Subject.Organization[0], icaCert.Subject.CommonName, userCert.Subject.CommonName, userCert.NotBefore.Format("2006-01-02-15-04-05")))
+	cert1Path := path.Join(dirPath, "cert.pem")
+	cert2Path := path.Join(dirPath, "cert.cer")
+	fullchain1Path := path.Join(dirPath, "fullchain.pem")
+	fullchain2Path := path.Join(dirPath, "fullchain.cer")
+	keyPath := path.Join(dirPath, "key.pem")
+
+	if utils.IsExists(cert1Path) || utils.IsExists(cert2Path) || utils.IsExists(fullchain1Path) || utils.IsExists(fullchain2Path) || utils.IsExists(keyPath) {
+		fmt.Printf("There is a duplicate file, it will be overwritten. Do you confirm to save the certificate? [yes/no] ")
+		if !ReadYesMust() {
+			return
+		}
+	} else {
+		fmt.Printf("Do you confirm to save the certificate? [yes/no] ")
+		if !ReadYes() {
+			return
+		}
+	}
+
+	fmt.Printf("Set a password for private key: ")
+	password := ReadPassword()
+
+	err = os.MkdirAll(dirPath, 0600)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.SaveCertificate(userCert, icaFullchain, cert1Path, cert2Path, fullchain1Path, fullchain2Path)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.SavePrivateKey(key, password, keyPath)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = icaSerialNumber.Save()
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	fmt.Println("Success, save directory: ", dirPath)
+}
+
+func CreateUserCertSelf() {
+	fmt.Println(cryptoMenu)
+	fmt.Printf(">>> ")
+
+	var cryptoType utils.CryptoType
+	var keyLength int
+
+	switch ReadNumber() {
+	case 1:
+		cryptoType = utils.CryptoTypeRsa
+		keyLength = 2048
+	case 2:
+		cryptoType = utils.CryptoTypeRsa
+		keyLength = 4096
+	case 0:
+		fallthrough
+	default:
+		fmt.Println("Warn: Use Default")
+		fallthrough
+	case 3:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 256
+	case 4:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 384
+	case 5:
+		cryptoType = utils.CryptoTypeEcdsa
+		keyLength = 521
+	}
+
+	fmt.Println("Crypto: ", cryptoType, keyLength)
+
+	fmt.Printf("Org: ")
+	org := ReadString()
+
+	fmt.Printf("Validity: ")
+	validity := ReadTimeDuration(time.Hour * 24 * 365 * 5)
+
+	notBefore := time.Now()
+	notAfter := notBefore.Add(validity)
+
+	domains := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your domain [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			domains = append(domains, res)
+		}
+	}
+
+	ips := make([]net.IP, 0, 10)
+	for {
+		fmt.Printf("Enter your ip [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			ip := net.ParseIP(res)
+			if ip == nil {
+				fmt.Println("Error: not a valid ip")
+			}
+
+			ips = append(ips, ip)
+		}
+	}
+
+	userCert, key, err := cert.CreateSelfCert(cryptoType, keyLength, org, domains, ips, notBefore, notAfter)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	dirPath := path.Join(home, "cert", fmt.Sprintf("Self-%s-%s-%s-%s", userCert.Subject.Organization[0], userCert.Subject.CommonName, userCert.Subject.CommonName, userCert.NotBefore.Format("2006-01-02-15-04-05")))
+	cert1Path := path.Join(dirPath, "cert.pem")
+	cert2Path := path.Join(dirPath, "cert.cer")
+	fullchain1Path := path.Join(dirPath, "fullchain.pem")
+	fullchain2Path := path.Join(dirPath, "fullchain.cer")
+	keyPath := path.Join(dirPath, "key.pem")
+
+	if utils.IsExists(cert1Path) || utils.IsExists(cert2Path) || utils.IsExists(fullchain1Path) || utils.IsExists(fullchain2Path) || utils.IsExists(keyPath) {
+		fmt.Printf("There is a duplicate file, it will be overwritten. Do you confirm to save the certificate? [yes/no] ")
+		if !ReadYesMust() {
+			return
+		}
+	} else {
+		fmt.Printf("Do you confirm to save the certificate? [yes/no] ")
+		if !ReadYes() {
+			return
+		}
+	}
+
+	fmt.Printf("Set a password for private key: ")
+	password := ReadPassword()
+
+	err = os.MkdirAll(dirPath, 0600)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.SaveCertificate(userCert, []byte{}, cert1Path, cert2Path, fullchain1Path, fullchain2Path)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = utils.SavePrivateKey(key, password, keyPath)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	fmt.Println("Success, save directory: ", dirPath)
+}

+ 78 - 0
src/mainfunc/myca/mycav1/loadica.go

@@ -0,0 +1,78 @@
+package mycav1
+
+import (
+	"crypto"
+	"crypto/x509"
+	"fmt"
+	"github.com/SongZihuan/MyCA/src/utils"
+	"math/big"
+	"os"
+	"path"
+)
+
+func LoadICA() (cert *x509.Certificate, key crypto.PrivateKey, fullchain []byte, serialNumber *utils.FileTack[*big.Int], err error) {
+	c := showAllICA()
+	if len(c) == 0 {
+		return nil, nil, nil, nil, fmt.Errorf("no ICA available")
+	}
+
+	fmt.Printf("Select an ICA and enter its serial number: ")
+	i := ReadNumber() - 1 // 显示的列表是从1开始计数的
+
+	if i < 0 || i >= len(c) {
+		return nil, nil, nil, nil, fmt.Errorf("invalid serial number")
+	}
+
+	passwordFunc := func() string {
+		fmt.Printf("Entery the password of the private key: ")
+		return ReadPassword()
+	}
+
+	return loadICA(c[i], passwordFunc)
+}
+
+func loadICA(name string, passwordFunc func() string) (cert *x509.Certificate, key crypto.PrivateKey, fullchain []byte, serialNumber *utils.FileTack[*big.Int], err error) {
+	certPEM, err := utils.ReadPemBlock(path.Join(home, "ica", name, "cert.pem"))
+	if err != nil {
+		return nil, nil, nil, nil, err
+	} else if certPEM.Type != utils.PemTypeCertificate {
+		return nil, nil, nil, nil, fmt.Errorf("pem type of cert error")
+	}
+
+	keyPEM, err := utils.ReadPemBlock(path.Join(home, "ica", name, "key.pem"))
+	if err != nil {
+		return nil, nil, nil, nil, err
+	} else if keyPEM.Type != utils.PemTypePrivateKeyWithPassword && keyPEM.Type != utils.PemTypePrivateKeyNotPassword {
+		return nil, nil, nil, nil, fmt.Errorf("pem type of key error")
+	}
+
+	fullchain, err = os.ReadFile(path.Join(home, "ica", name, "fullchain.pem"))
+	if err != nil {
+		return nil, nil, nil, nil, err
+	}
+
+	cert, err = x509.ParseCertificate(certPEM.Bytes)
+	if err != nil {
+		return nil, nil, nil, nil, err
+	}
+
+	if keyPEM.Type == utils.PemTypePrivateKeyWithPassword {
+		password := passwordFunc()
+		key, _, err = utils.ParserPrivateKey(keyPEM.Bytes, password)
+		if err != nil {
+			return nil, nil, nil, nil, err
+		}
+	} else {
+		key, _, err = utils.ParserPrivateKey(keyPEM.Bytes)
+		if err != nil {
+			return nil, nil, nil, nil, err
+		}
+	}
+
+	serialNumber, err = utils.ReadBigIntFromFileWithFileStack(path.Join(home, "ica", name, "serial.num"))
+	if err != nil {
+		return nil, nil, nil, nil, err
+	}
+
+	return cert, key, fullchain, serialNumber, nil
+}

+ 78 - 0
src/mainfunc/myca/mycav1/loadrca.go

@@ -0,0 +1,78 @@
+package mycav1
+
+import (
+	"crypto"
+	"crypto/x509"
+	"fmt"
+	"github.com/SongZihuan/MyCA/src/utils"
+	"math/big"
+	"os"
+	"path"
+)
+
+func LoadRCA() (cert *x509.Certificate, key crypto.PrivateKey, fullchain []byte, serialNumber *utils.FileTack[*big.Int], err error) {
+	c := showAllRCA()
+	if len(c) == 0 {
+		return nil, nil, nil, nil, fmt.Errorf("no RCA available")
+	}
+
+	fmt.Printf("Select an RCA and enter its serial number: ")
+	i := ReadNumber() - 1 // 显示的列表是从1开始计数的
+
+	if i < 0 || i >= len(c) {
+		return nil, nil, nil, nil, fmt.Errorf("invalid serial number")
+	}
+
+	passwordFunc := func() string {
+		fmt.Printf("Entery the password of the private key: ")
+		return ReadPassword()
+	}
+
+	return loadRCA(c[i], passwordFunc)
+}
+
+func loadRCA(name string, passwordFunc func() string) (cert *x509.Certificate, key crypto.PrivateKey, fullchain []byte, serialNumber *utils.FileTack[*big.Int], err error) {
+	certPEM, err := utils.ReadPemBlock(path.Join(home, "rca", name, "cert.pem"))
+	if err != nil {
+		return nil, nil, nil, nil, err
+	} else if certPEM.Type != utils.PemTypeCertificate {
+		return nil, nil, nil, nil, fmt.Errorf("pem type of cert error")
+	}
+
+	keyPEM, err := utils.ReadPemBlock(path.Join(home, "rca", name, "key.pem"))
+	if err != nil {
+		return nil, nil, nil, nil, err
+	} else if keyPEM.Type != utils.PemTypePrivateKeyWithPassword && keyPEM.Type != utils.PemTypePrivateKeyNotPassword {
+		return nil, nil, nil, nil, fmt.Errorf("pem type of key error")
+	}
+
+	fullchain, err = os.ReadFile(path.Join(home, "rca", name, "fullchain.pem"))
+	if err != nil {
+		return nil, nil, nil, nil, err
+	}
+
+	cert, err = x509.ParseCertificate(certPEM.Bytes)
+	if err != nil {
+		return nil, nil, nil, nil, err
+	}
+
+	if keyPEM.Type == utils.PemTypePrivateKeyWithPassword {
+		password := passwordFunc()
+		key, _, err = utils.ParserPrivateKey(keyPEM.Bytes, password)
+		if err != nil {
+			return nil, nil, nil, nil, err
+		}
+	} else {
+		key, _, err = utils.ParserPrivateKey(keyPEM.Bytes)
+		if err != nil {
+			return nil, nil, nil, nil, err
+		}
+	}
+
+	serialNumber, err = utils.ReadBigIntFromFileWithFileStack(path.Join(home, "rca", name, "serial.num"))
+	if err != nil {
+		return nil, nil, nil, nil, err
+	}
+
+	return cert, key, fullchain, serialNumber, nil
+}

+ 88 - 0
src/mainfunc/myca/mycav1/mycav1.go

@@ -0,0 +1,88 @@
+package mycav1
+
+import (
+	"bufio"
+	"errors"
+	"fmt"
+	"github.com/SongZihuan/MyCA/src/flagparser"
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+)
+
+func MainV1() (exitcode int) {
+	err := flagparser.InitFlagParser()
+	if err != nil {
+		if errors.Is(err, flagparser.StopRun) {
+			return 0
+		}
+
+		fmt.Println("Error:", err)
+		return 1
+	}
+
+	home = flagparser.Home
+	stdinReader = bufio.NewReader(os.Stdin)
+
+	stopChan := make(chan int, 2)
+	sigChan := make(chan os.Signal, 10)
+	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+
+	fmt.Println("Welcome to MyCA")
+	fmt.Println("Home Directory: ", home)
+
+	go MainCycle(stopChan)
+
+	select {
+	case exitcode = <-stopChan:
+		return exitcode
+	case <-sigChan:
+		return 0
+	}
+}
+
+func MainCycle(stopchan chan int) {
+MainCycle:
+	for {
+		res := func() bool {
+			PrintMenu()
+			fmt.Printf("%s >>> ", time.Now().Format("15:04:05"))
+
+			m := ReadNumber()
+
+			switch m {
+			case 0:
+				// pass
+			case 1:
+				ShowAllRCA()
+			case 2:
+				ShowAllICA()
+			case 3:
+				CreateRCA()
+			case 4:
+				CreateICAFromRCA()
+			case 5:
+				CreateICAFromICA()
+			case 6:
+				CreateUserCertFromRCA()
+			case 7:
+				CreateUserCertFromICA()
+			case 8:
+				CreateUserCertSelf()
+			case 9:
+				stopchan <- 0
+				close(stopchan)
+				return false
+			default:
+				fmt.Println("Error: Unknown Command")
+			}
+
+			fmt.Printf("\n")
+			return true
+		}()
+		if !res {
+			break MainCycle
+		}
+	}
+}

+ 104 - 0
src/mainfunc/myca/mycav1/read.go

@@ -0,0 +1,104 @@
+package mycav1
+
+import (
+	"fmt"
+	"github.com/SongZihuan/MyCA/src/utils"
+	"golang.org/x/term"
+	"os"
+	"strconv"
+	"strings"
+	"time"
+)
+
+func ReadNumber() int {
+	if stdinReader == nil {
+		fmt.Println("Error: stdinReader is nil")
+		return 0
+	}
+
+	input, err := stdinReader.ReadString('\n')
+	if err != nil {
+		fmt.Println("Error:", err)
+		return 0
+	}
+
+	input = strings.TrimSuffix(input, "\n")
+	input = strings.TrimSpace(input)
+
+	if input == "" {
+		return 0
+	}
+
+	m, err := strconv.ParseInt(input, 10, 64)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return 0
+	}
+
+	return int(m)
+}
+
+func ReadString() string {
+	if stdinReader == nil {
+		fmt.Println("Error: stdinReader is nil")
+		return ""
+	}
+
+	input, err := stdinReader.ReadString('\n')
+	if err != nil {
+		fmt.Println("Error:", err)
+		return ""
+	}
+
+	input = strings.TrimSuffix(input, "\n")
+	input = strings.TrimSpace(input)
+
+	return input
+}
+
+func ReadPassword() string {
+	state, err := term.MakeRaw(int(os.Stdin.Fd()))
+	if err != nil {
+		return ReadString()
+	}
+	defer func() {
+		_ = term.Restore(int(os.Stdin.Fd()), state)
+		fmt.Printf("\n")
+	}()
+
+	fmt.Printf("[note: the password you type will not be echoed] ")
+	pw, err := term.ReadPassword(int(os.Stdin.Fd()))
+	if err != nil {
+		fmt.Println("Error:", err)
+	}
+
+	password := string(pw)
+	password = strings.TrimSuffix(password, "\n")
+	password = strings.TrimSpace(password)
+
+	return password
+}
+
+func ReadTimeDuration(defaultVal time.Duration) time.Duration {
+	input := ReadString()
+	if input == "" {
+		return defaultVal
+	}
+
+	res := utils.ReadTimeDuration(input)
+	if res == 0 {
+		return defaultVal
+	}
+
+	return res
+}
+
+func ReadYes() bool {
+	input := strings.ToLower(ReadString())
+	return input == "" || input == "yes" || input == "y"
+}
+
+func ReadYesMust() bool {
+	input := strings.ToLower(ReadString())
+	return input == "yes" || input == "y"
+}

+ 41 - 0
src/mainfunc/myca/mycav1/show.go

@@ -0,0 +1,41 @@
+package mycav1
+
+import (
+	"fmt"
+	"github.com/SongZihuan/MyCA/src/utils"
+	"path"
+)
+
+func PrintMenu() {
+	fmt.Println(menu)
+}
+
+func showAllRCA() []string {
+	rca, err := utils.ReadDirOnlyDir(path.Join(home, "rca"))
+	if err != nil {
+		fmt.Println("Error:", err)
+	}
+
+	fmt.Println("总计: ", len(rca))
+
+	for i, v := range rca {
+		fmt.Printf(" %d. %s\n", i+1, v)
+	}
+
+	return rca
+}
+
+func showAllICA() []string {
+	ica, err := utils.ReadDirOnlyDir(path.Join(home, "ica"))
+	if err != nil {
+		fmt.Println("Error:", err)
+	}
+
+	fmt.Println("总计: ", len(ica))
+
+	for i, v := range ica {
+		fmt.Printf(" %d. %s\n", i+1, v)
+	}
+
+	return ica
+}

+ 19 - 0
src/mainfunc/myca/mycav1/string.go

@@ -0,0 +1,19 @@
+package mycav1
+
+const menu = `Menu:
+  1) Show All RCA
+  2) Show All ICA
+  3) Create RCA (Self Signed)
+  4) Create ICA From RCA
+  5) Create ICA From Another ICA
+  6) Create User Certificate From RCA
+  7) Create User Certificate From ICA
+  8) Create User Certificate (Self Signed)
+  9) Exit`
+
+const cryptoMenu = `Crypto Menu:
+ 1) RSA 2048
+ 2) RSA 4096
+ 3) ECDSA P256
+ 4) ECDSA P384
+ 5) ECDSA P521`

+ 6 - 0
src/mainfunc/myca/mycav1/variable.go

@@ -0,0 +1,6 @@
+package mycav1
+
+import "bufio"
+
+var stdinReader *bufio.Reader
+var home string

+ 102 - 0
src/rootca/rootca.go

@@ -0,0 +1,102 @@
+package rootca
+
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"fmt"
+	"github.com/SongZihuan/MyCA/src/utils"
+	"math/big"
+	"time"
+)
+
+// CreateRCA 创建根CA证书
+func CreateRCA(cryptoType utils.CryptoType, keyLength int, org string, cn string, notBefore time.Time, notAfter time.Time) (*x509.Certificate, crypto.PrivateKey, error) {
+	var privKey crypto.PrivateKey
+	var pubKey interface{}
+
+	switch cryptoType {
+	case utils.CryptoTypeRsa:
+		if keyLength != 2048 && keyLength != 4096 {
+			return nil, nil, fmt.Errorf("unsupported RSA key length: %d", keyLength)
+		}
+
+		priv, err := rsa.GenerateKey(utils.Rander(), keyLength)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		privKey = priv
+		pubKey = &priv.PublicKey
+	case utils.CryptoTypeEcc:
+		fallthrough
+	case utils.CryptoTypeEcdsa:
+		var curve elliptic.Curve
+		switch keyLength {
+		case 256:
+			curve = elliptic.P256()
+		case 384:
+			curve = elliptic.P384()
+		case 521:
+			curve = elliptic.P521()
+		default:
+			return nil, nil, fmt.Errorf("unsupported ECC key length: %d", keyLength)
+		}
+		priv, err := ecdsa.GenerateKey(curve, utils.Rander())
+		if err != nil {
+			return nil, nil, err
+		}
+		privKey = priv
+		pubKey = &priv.PublicKey
+	default:
+		return nil, nil, fmt.Errorf("unsupported crypto type: %s", cryptoType)
+	}
+
+	if org == "" {
+		org = "MyOrg"
+	}
+
+	if cn == "" {
+		cn = fmt.Sprintf("R%02d", utils.RandIntn(98)+1) // 数字范围1-99
+	}
+
+	if notBefore.Equal(time.Time{}) {
+		notBefore = time.Now()
+	}
+
+	if notAfter.Equal(time.Time{}) {
+		notAfter = notBefore.Add(time.Hour * 24 * 365 * 10) // 10年
+	}
+
+	template := &x509.Certificate{
+		SerialNumber: big.NewInt(1),
+		Subject: pkix.Name{
+			Organization: []string{org},
+			CommonName:   cn,
+		},
+		NotBefore: notBefore,
+		NotAfter:  notAfter,
+
+		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, // 允许全部扩展用途
+		BasicConstraintsValid: true,
+		IsCA:                  true,
+		MaxPathLen:            -1,
+		MaxPathLenZero:        false,
+	}
+
+	derBytes, err := x509.CreateCertificate(utils.Rander(), template, template, pubKey, privKey)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	cert, err := x509.ParseCertificate(derBytes)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return cert, privKey, nil
+}

+ 28 - 0
src/utils/big.go

@@ -0,0 +1,28 @@
+package utils
+
+import (
+	"math/big"
+)
+
+func WriteBigIntToFile(filename string, value *big.Int) error {
+	f := NewFileTackWithValue(filename, value)
+	err := f.Save()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func ReadBigIntFromFile(filepath string) (*big.Int, error) {
+	f, err := ReadBigIntFromFileWithFileStack(filepath)
+	if err != nil {
+		return nil, err
+	}
+
+	return f.Value, nil
+}
+
+func ReadBigIntFromFileWithFileStack(filepath string) (*FileTack[*big.Int], error) {
+	return NewFileTackWithDefault[*big.Int](filepath, big.NewInt(0))
+}

+ 67 - 0
src/utils/file.go

@@ -0,0 +1,67 @@
+package utils
+
+import (
+	"errors"
+	"os"
+)
+
+func IsExists(path string) bool {
+	_, err := os.Stat(path)
+	if err != nil && errors.Is(err, os.ErrNotExist) {
+		return false
+	}
+	return true
+}
+
+func ReadDir(dirPath string) ([]string, error) {
+	entry, err := os.ReadDir(dirPath)
+	if err != nil {
+		return nil, err
+	}
+
+	res := make([]string, 0, len(entry))
+
+	for _, e := range entry {
+		res = append(res, e.Name())
+	}
+
+	return res, nil
+}
+
+func ReadDirOnlyFile(dirPath string) ([]string, error) {
+	entry, err := os.ReadDir(dirPath)
+	if err != nil {
+		return nil, err
+	}
+
+	res := make([]string, 0, len(entry))
+
+	for _, e := range entry {
+		if e.IsDir() {
+			continue
+		}
+
+		res = append(res, e.Name())
+	}
+
+	return res, nil
+}
+
+func ReadDirOnlyDir(dirPath string) ([]string, error) {
+	entry, err := os.ReadDir(dirPath)
+	if err != nil {
+		return nil, err
+	}
+
+	res := make([]string, 0, len(entry))
+
+	for _, e := range entry {
+		if !e.IsDir() {
+			continue
+		}
+
+		res = append(res, e.Name())
+	}
+
+	return res, nil
+}

+ 63 - 0
src/utils/filetack.go

@@ -0,0 +1,63 @@
+package utils
+
+import (
+	"encoding/gob"
+	"os"
+)
+
+type FileTack[T any] struct {
+	Value    T
+	FilePath string
+}
+
+func NewFileTack[T any](filePath string) (*FileTack[T], error) {
+	file, err := os.Open(filePath)
+	if err != nil {
+		return nil, err
+	}
+	defer func() {
+		_ = file.Close()
+	}()
+
+	var value T
+	decoder := gob.NewDecoder(file)
+	err = decoder.Decode(&value)
+	if err != nil {
+		return nil, err
+	}
+
+	return NewFileTackWithValue(filePath, value), nil
+}
+
+func NewFileTackWithDefault[T any](filePath string, defaultVal T) (*FileTack[T], error) {
+	if !IsExists(filePath) {
+		return NewFileTackWithValue(filePath, defaultVal), nil
+	}
+
+	return NewFileTack[T](filePath)
+}
+
+func NewFileTackWithValue[T any](filePath string, value T) *FileTack[T] {
+	return &FileTack[T]{
+		Value:    value,
+		FilePath: filePath,
+	}
+}
+
+func (f *FileTack[T]) Save() error {
+	file, err := os.Create(f.FilePath)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		_ = file.Close()
+	}()
+
+	encoder := gob.NewEncoder(file)
+	err = encoder.Encode(f.Value)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

+ 28 - 0
src/utils/rand.go

@@ -0,0 +1,28 @@
+package utils
+
+import (
+	"math/rand"
+	"time"
+)
+
+var _rander *rand.Rand = nil
+
+func init() {
+	_rander = rand.New(rand.NewSource(time.Now().UnixNano()))
+}
+
+func Rander() *rand.Rand {
+	if _rander == nil {
+		panic("nil rander")
+	}
+
+	return _rander
+}
+
+func RandIntn(n int) int {
+	if n <= 0 {
+		return 0
+	}
+
+	return _rander.Intn(n)
+}

+ 367 - 0
src/utils/string.go

@@ -0,0 +1,367 @@
+package utils
+
+import (
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+	"unicode"
+)
+
+const BASE_CHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+func RandStr(length int) string {
+	bytes := []byte(BASE_CHAR)
+
+	var result []byte
+	for i := 0; i < length; i++ {
+		result = append(result, bytes[Rander().Intn(len(bytes))])
+	}
+
+	return string(result)
+}
+
+func InvalidPhone(phone string) bool {
+	pattern := `^1[3-9]\d{9}$`
+	matched, _ := regexp.MatchString(pattern, phone)
+	return matched
+}
+
+func IsValidEmail(email string) bool {
+	pattern := `^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`
+	matched, _ := regexp.MatchString(pattern, email)
+	return matched
+}
+
+const NormalConsoleWidth = 80
+
+func FormatTextToWidth(text string, width int) string {
+	return FormatTextToWidthAndPrefix(text, 0, width)
+}
+
+func FormatTextToWidthAndPrefix(text string, prefixWidth int, overallWidth int) string {
+	var result strings.Builder
+
+	width := overallWidth - prefixWidth
+	if width <= 0 {
+		panic("bad width")
+	}
+
+	text = strings.ReplaceAll(text, "\r\n", "\n")
+
+	for _, line := range strings.Split(text, "\n") {
+		result.WriteString(strings.Repeat(" ", prefixWidth))
+
+		if line == "" {
+			result.WriteString("\n")
+			continue
+		}
+
+		spaceCount := CountSpaceInStringPrefix(line) % width
+		newLineLength := 0
+		if spaceCount < 80 {
+			result.WriteString(strings.Repeat(" ", spaceCount))
+			newLineLength = spaceCount
+		}
+
+		for _, word := range strings.Fields(line) {
+			if newLineLength+len(word) >= width {
+				result.WriteString("\n")
+				result.WriteString(strings.Repeat(" ", prefixWidth))
+				newLineLength = 0
+			}
+
+			// 不是第一个词时,添加空格
+			if newLineLength != 0 {
+				result.WriteString(" ")
+				newLineLength += 1
+			}
+
+			result.WriteString(word)
+			newLineLength += len(word)
+		}
+
+		if newLineLength != 0 {
+			result.WriteString("\n")
+			newLineLength = 0
+		}
+	}
+
+	return strings.TrimRight(result.String(), "\n")
+}
+
+func CountSpaceInStringPrefix(str string) int {
+	var res int
+	for _, r := range str {
+		if r == ' ' {
+			res += 1
+		} else {
+			break
+		}
+	}
+
+	return res
+}
+
+func IsValidURLPath(path string) bool {
+	if path == "" {
+		return true
+	} else if path == "/" {
+		return false
+	}
+
+	pattern := `^\/[a-zA-Z0-9\-._~:/?#\[\]@!$&'()*+,;%=]+$`
+	matched, _ := regexp.MatchString(pattern, path)
+	return matched
+}
+
+func IsValidDomain(domain string) bool {
+	pattern := `^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$`
+	matched, _ := regexp.MatchString(pattern, domain)
+	return matched
+}
+
+func StringToOnlyPrint(str string) string {
+	runeLst := []rune(str)
+	res := make([]rune, 0, len(runeLst))
+
+	for _, r := range runeLst {
+		if unicode.IsPrint(r) {
+			res = append(res, r)
+		}
+	}
+
+	return string(res)
+}
+
+func IsGoodQueryKey(key string) bool {
+	pattern := `^[a-zA-Z0-9\-._~]+$`
+	matched, _ := regexp.MatchString(pattern, key)
+	return matched
+}
+
+func IsValidHTTPHeaderKey(key string) bool {
+	pattern := `^[a-zA-Z0-9!#$%&'*+.^_` + "`" + `|~-]+$`
+	matched, _ := regexp.MatchString(pattern, key)
+	return matched
+}
+
+func ReadTimeDuration(str string) time.Duration {
+	if str == "forever" || str == "none" {
+		return -1
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "Y") {
+		numStr := str[:len(str)-1]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Hour * 24 * 365 * time.Duration(num)
+	} else if strings.HasSuffix(strings.ToLower(str), "year") {
+		numStr := str[:len(str)-4]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Hour * 24 * 365 * time.Duration(num)
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "M") {
+		numStr := str[:len(str)-1]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Hour * 24 * 31 * time.Duration(num)
+	} else if strings.HasSuffix(strings.ToLower(str), "month") {
+		numStr := str[:len(str)-5]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Hour * 24 * 31 * time.Duration(num)
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "W") {
+		numStr := str[:len(str)-1]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Hour * 24 * 7 * time.Duration(num)
+	} else if strings.HasSuffix(strings.ToLower(str), "week") {
+		numStr := str[:len(str)-4]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Hour * 24 * 7 * time.Duration(num)
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "D") {
+		numStr := str[:len(str)-1]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Hour * 24 * time.Duration(num)
+	} else if strings.HasSuffix(strings.ToLower(str), "day") {
+		numStr := str[:len(str)-3]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Hour * 24 * time.Duration(num)
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "H") {
+		numStr := str[:len(str)-1]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Hour * time.Duration(num)
+	} else if strings.HasSuffix(strings.ToLower(str), "hour") {
+		numStr := str[:len(str)-4]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Hour * time.Duration(num)
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "Min") { // 不能用M,否则会和 Month 冲突
+		numStr := str[:len(str)-3]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Minute * time.Duration(num)
+	} else if strings.HasSuffix(strings.ToLower(str), "minute") {
+		numStr := str[:len(str)-6]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Minute * time.Duration(num)
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "S") {
+		numStr := str[:len(str)-1]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Second * time.Duration(num)
+	} else if strings.HasSuffix(strings.ToLower(str), "second") {
+		numStr := str[:len(str)-6]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Second * time.Duration(num)
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "MS") {
+		numStr := str[:len(str)-2]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Millisecond * time.Duration(num)
+	} else if strings.HasSuffix(strings.ToLower(str), "millisecond") {
+		numStr := str[:len(str)-11]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Millisecond * time.Duration(num)
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "MiS") { // 不能用 MS , 否则会和 millisecond 冲突
+		numStr := str[:len(str)-3]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Microsecond * time.Duration(num)
+	} else if strings.HasSuffix(strings.ToUpper(str), "MicroS") {
+		numStr := str[:len(str)-6]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Microsecond * time.Duration(num)
+	} else if strings.HasSuffix(strings.ToLower(str), "microsecond") {
+		numStr := str[:len(str)-11]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Microsecond * time.Duration(num)
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "NS") {
+		numStr := str[:len(str)-2]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Nanosecond * time.Duration(num)
+	} else if strings.HasSuffix(strings.ToLower(str), "nanosecond") {
+		numStr := str[:len(str)-10]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return time.Nanosecond * time.Duration(num)
+	}
+
+	num, _ := strconv.ParseUint(str, 10, 64)
+	return time.Duration(num) * time.Second
+}
+
+func ReadBytes(str string) uint64 {
+	if strings.HasSuffix(strings.ToUpper(str), "TB") {
+		numStr := str[:len(str)-2]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024 * 1024 * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "tbytes") {
+		numStr := str[:len(str)-6]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024 * 1024 * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "tbyte") {
+		numStr := str[:len(str)-5]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024 * 1024 * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "terabytes") {
+		numStr := str[:len(str)-9]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024 * 1024 * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "terabyte") {
+		numStr := str[:len(str)-8]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024 * 1024 * 1024
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "GB") {
+		numStr := str[:len(str)-2]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024 * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "gbytes") {
+		numStr := str[:len(str)-6]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024 * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "gbyte") {
+		numStr := str[:len(str)-5]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024 * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "gigabytes") {
+		numStr := str[:len(str)-9]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024 * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "gigabyte") {
+		numStr := str[:len(str)-8]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024 * 1024
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "MB") {
+		numStr := str[:len(str)-2]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "mbytes") {
+		numStr := str[:len(str)-6]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "mbyte") {
+		numStr := str[:len(str)-5]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "megabytes") {
+		numStr := str[:len(str)-9]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "megabyte") {
+		numStr := str[:len(str)-8]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024 * 1024
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "KB") {
+		numStr := str[:len(str)-2]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "kbytes") {
+		numStr := str[:len(str)-6]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "kbyte") {
+		numStr := str[:len(str)-5]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024
+	} else if strings.HasSuffix(strings.ToLower(str), "kilobytes") {
+		numStr := str[:len(str)-9]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num * 1024
+	} else if strings.HasSuffix(strings.ToUpper(str), "kilobyte") {
+		numStr := str[:len(str)-8]
+		num, _ := strconv.ParseUint(numStr, 9, 64)
+		return num * 1024
+	}
+
+	if strings.HasSuffix(strings.ToUpper(str), "B") {
+		numStr := str[:len(str)-1]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num
+	} else if strings.HasSuffix(strings.ToLower(str), "bytes") {
+		numStr := str[:len(str)-5]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num
+	} else if strings.HasSuffix(strings.ToLower(str), "byte") {
+		numStr := str[:len(str)-4]
+		num, _ := strconv.ParseUint(numStr, 10, 64)
+		return num
+	}
+
+	num, _ := strconv.ParseUint(str, 10, 64)
+	return num
+}

+ 143 - 0
src/utils/x509.go

@@ -0,0 +1,143 @@
+package utils
+
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
+	"fmt"
+	"github.com/youmark/pkcs8"
+	"os"
+	"regexp"
+	"strings"
+)
+
+type CryptoType string
+
+const (
+	CryptoTypeRsa   CryptoType = "RSA"
+	CryptoTypeEcdsa CryptoType = "ECDSA"
+	CryptoTypeEcc   CryptoType = "ECC"
+)
+
+const (
+	PemTypePrivateKeyNotPassword  = "PRIVATE KEY"
+	PemTypePrivateKeyWithPassword = "ENCRYPTED " + PemTypePrivateKeyNotPassword
+	PemTypeCertificate            = "CERTIFICATE"
+)
+
+func SaveCertificate(cert *x509.Certificate, caFullchain []byte, cert1SavePath, cert2SavePath, fullchain1SavePath, fullchain2SavePath string) error {
+	// 将证书转换为 PEM 格式
+	certPEM := pem.EncodeToMemory(&pem.Block{
+		Type:  PemTypeCertificate,
+		Bytes: cert.Raw,
+	})
+
+	fullchain := make([]byte, len(certPEM), len(certPEM)+len(caFullchain))
+	copy(fullchain, certPEM)
+	fullchain = append(fullchain, caFullchain...)
+
+	// 写入文件
+	err := os.WriteFile(cert1SavePath, certPEM, 0600)
+	if err != nil {
+		return err
+	}
+
+	err = os.WriteFile(cert2SavePath, certPEM, 0600)
+	if err != nil {
+		return err
+	}
+
+	err = os.WriteFile(fullchain1SavePath, fullchain, 0600)
+	if err != nil {
+		return err
+	}
+
+	err = os.WriteFile(fullchain2SavePath, fullchain, 0600)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func SavePrivateKey(key crypto.PrivateKey, pwStr string, savePath string) error {
+	pwStr = strings.TrimSpace(pwStr)
+
+	var password []byte = nil
+	var pemType = PemTypePrivateKeyNotPassword
+	if len(pwStr) != 0 {
+		if !isValidPassword(pwStr) {
+			return fmt.Errorf("password is invalid")
+		}
+
+		pemType = PemTypePrivateKeyWithPassword
+		password = []byte(pwStr)
+	}
+
+	data, err := pkcs8.MarshalPrivateKey(key, password, pkcs8.DefaultOpts)
+	if err != nil {
+		return err
+	}
+
+	pemBlock := &pem.Block{
+		Type:  pemType,
+		Bytes: data,
+	}
+
+	err = os.WriteFile(savePath, pem.EncodeToMemory(pemBlock), 0600)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func ReadPemBlock(filePath string) (*pem.Block, error) {
+	data, err := os.ReadFile(filePath)
+	if err != nil {
+		return nil, err
+	}
+
+	block, _ := pem.Decode(data)
+	if block == nil {
+		return nil, fmt.Errorf("not pem found")
+	}
+
+	return block, nil
+}
+
+func IsPrivateKeyPemBlockNeedPassword(block *pem.Block) bool {
+	if block != nil && block.Type == PemTypePrivateKeyWithPassword {
+		return true
+	} else {
+		return false
+	}
+}
+
+func ParserPrivateKey(derData []byte, pwVargs ...string) (key crypto.PrivateKey, keyType CryptoType, err error) {
+	if len(pwVargs) == 0 || strings.TrimSpace(pwVargs[0]) == "" {
+		key, err = pkcs8.ParsePKCS8PrivateKey(derData)
+	} else {
+		password := []byte(strings.TrimSpace(pwVargs[0]))
+		key, err = pkcs8.ParsePKCS8PrivateKey(derData, password)
+	}
+	if err != nil {
+		return nil, "", err
+	}
+
+	switch key.(type) {
+	case *rsa.PrivateKey:
+		return key, CryptoTypeRsa, nil
+	case *ecdsa.PrivateKey:
+		return key, CryptoTypeEcdsa, nil
+	default:
+		return nil, "", fmt.Errorf("unknown crypto type")
+	}
+}
+
+func isValidPassword(password string) bool {
+	matched, _ := regexp.MatchString(`^[a-zA-Z0-9!@#$%^&*_]+$`, password)
+	return matched
+}