浏览代码

添加创建和加载证书功能

新增了创建自签名证书、中间CA证书和根CA证书的功能,并更新了加载ICA和RCA的方法以支持新的证书信息结构。同时,引入了系统信息相关的辅助函数来生成证书的主题信息。
SongZihuan 1 月之前
父节点
当前提交
cdebb6abc8

+ 86 - 111
src/cert/cert.go

@@ -7,144 +7,116 @@ import (
 	"crypto/rsa"
 	"crypto/x509"
 	"crypto/x509/pkix"
+	"encoding/gob"
 	"fmt"
+	"github.com/SongZihuan/MyCA/src/sysinfo"
 	"github.com/SongZihuan/MyCA/src/utils"
 	"math/big"
 	"net"
 	"net/url"
+	"os"
 	"time"
 )
 
-// CreateCert 创建由CA签名的IP、域名证书
-func CreateCert(cryptoType utils.CryptoType, keyLength int, org string, cn string, domains []string, ips []net.IP, emails []string, urls []*url.URL, 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{}
-
-	if cn == "" {
-		if len(domains) != 0 {
-			cn = domains[0]
-		} else if len(ips) != 0 {
-			cn = ips[0].String()
-		} else if len(emails) != 0 {
-			cn = emails[0]
-		} else if len(urls) != 0 {
-			cn = urls[0].String()
-		} else {
-			cn = utils.RandStr(6 + utils.RandIntn(3)) // 6-8位随机字符串
-		}
-	}
-
-	switch cryptoType {
-	case utils.CryptoTypeRsa:
-		if keyLength != 2048 && keyLength != 4096 {
-			return nil, nil, fmt.Errorf("unsupported RSA key length: %d", keyLength)
-		}
+type CAInfo interface {
+	NewCert() *big.Int
+	GetIssuingCertificateURL() []string
+	GetOCSPServer() []string
+	GetCRLDistributionPoints() []string
+}
 
-		priv, err := rsa.GenerateKey(utils.Rander(), keyLength)
-		if err != nil {
-			return nil, nil, err
-		}
+type CertInfo struct {
+	SerialNumber       *big.Int
+	SelfCertificateURL []string
+	CA                 CAInfo
+	FilePath           string `gob:"-"`
+}
 
-		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)
+func NewCertInfo(filepath string, ca CAInfo) (*CertInfo, error) {
+	info := &CertInfo{
+		SerialNumber: big.NewInt(0),
+		FilePath:     filepath,
+		CA:           ca,
 	}
 
-	if org == "" {
-		org = "MyOrg"
-	}
+	return info, nil
+}
 
-	if notBefore.Equal(time.Time{}) {
-		notBefore = time.Now()
-	}
+func init() {
+	gob.Register(CertInfo{})
+}
 
-	if notAfter.Equal(time.Time{}) {
-		notAfter = notBefore.Add(time.Hour * 24 * 365 * 5) // 5年
+func GetCertInfo(filepath string) (*CertInfo, error) {
+	file, err := os.Open(filepath)
+	if err != nil {
+		return nil, err
 	}
+	defer func() {
+		_ = file.Close()
+	}()
 
-	caSerialNumber.Value = new(big.Int).Add(caSerialNumber.Value, big.NewInt(1))
+	var res CertInfo
+	decoder := gob.NewDecoder(file)
+	err = decoder.Decode(&res)
+	if err != nil {
+		return nil, err
+	}
 
-	template := &x509.Certificate{
-		SerialNumber: caSerialNumber.Value,
-		Subject: pkix.Name{
-			Organization: []string{org},
-			CommonName:   cn,
-		},
-		NotBefore: notBefore,
-		NotAfter:  notAfter,
+	res.FilePath = filepath
 
-		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
-		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, // 允许全部扩展用途
-		BasicConstraintsValid: true,
-		IsCA:                  false,
-		DNSNames:              domains,
-		IPAddresses:           ips,
-		EmailAddresses:        emails,
-		URIs:                  urls,
-	}
+	return &res, nil
+}
 
-	derBytes, err := x509.CreateCertificate(utils.Rander(), template, ca, pubKey, caKey)
+func (info *CertInfo) SaveCertInfo() error {
+	file, err := os.OpenFile(info.FilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
 	if err != nil {
-		return nil, nil, err
+		return err
 	}
+	defer func() {
+		_ = file.Close()
+	}()
 
-	cert, err := x509.ParseCertificate(derBytes)
+	encoder := gob.NewEncoder(file)
+	err = encoder.Encode(*info) // 不需要以指针形式出现
 	if err != nil {
-		return nil, nil, err
+		return err
 	}
 
-	return cert, privKey, nil
+	return nil
+}
+
+func (info *CertInfo) GetSerialNumber() *big.Int {
+	info.SerialNumber = info.CA.NewCert()
+	return info.SerialNumber
+}
+
+func (info *CertInfo) NewCert() *big.Int {
+	panic("Not a CA!")
 }
 
-// CreateSelfCert 创建自签名域名、IP证书
-func CreateSelfCert(cryptoType utils.CryptoType, keyLength int, org string, cn string, domains []string, ips []net.IP, emails []string, urls []*url.URL, notBefore time.Time, notAfter time.Time) (*x509.Certificate, crypto.PrivateKey, error) {
+func (info *CertInfo) GetIssuingCertificateURL() []string {
+	panic("Not a CA!")
+}
+
+// CreateCert 创建由CA签名的IP、域名证书
+func CreateCert(infoFilePath string, caInfo CAInfo, cryptoType utils.CryptoType, keyLength int, org string, cn string, domains []string, ips []net.IP, emails []string, urls []*url.URL, notBefore time.Time, notAfter time.Time, ca *x509.Certificate, caKey crypto.PrivateKey) (*x509.Certificate, crypto.PrivateKey, *CertInfo, error) {
 	var privKey crypto.PrivateKey
 	var pubKey interface{}
 
-	if cn == "" {
-		if len(domains) != 0 {
-			cn = domains[0]
-		} else if len(ips) != 0 {
-			cn = ips[0].String()
-		} else if len(emails) != 0 {
-			cn = emails[0]
-		} else if len(urls) != 0 {
-			cn = urls[0].String()
-		} else {
-			cn = utils.RandStr(6 + utils.RandIntn(3)) // 6-8位随机字符串
-		}
+	info, err := NewCertInfo(infoFilePath, caInfo)
+	if err != nil {
+		return nil, nil, nil, err
 	}
 
 	switch cryptoType {
 	case utils.CryptoTypeRsa:
 		if keyLength != 2048 && keyLength != 4096 {
-			return nil, nil, fmt.Errorf("unsupported RSA key length: %d", keyLength)
+			return nil, nil, nil, fmt.Errorf("unsupported RSA key length: %d", keyLength)
 		}
 
 		priv, err := rsa.GenerateKey(utils.Rander(), keyLength)
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, nil, err
 		}
 
 		privKey = priv
@@ -161,20 +133,16 @@ func CreateSelfCert(cryptoType utils.CryptoType, keyLength int, org string, cn s
 		case 521:
 			curve = elliptic.P521()
 		default:
-			return nil, nil, fmt.Errorf("unsupported ECC key length: %d", keyLength)
+			return nil, nil, nil, fmt.Errorf("unsupported ECC key length: %d", keyLength)
 		}
 		priv, err := ecdsa.GenerateKey(curve, utils.Rander())
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, nil, err
 		}
 		privKey = priv
 		pubKey = &priv.PublicKey
 	default:
-		return nil, nil, fmt.Errorf("unsupported crypto type: %s", cryptoType)
-	}
-
-	if org == "" {
-		org = "MyOrg"
+		return nil, nil, nil, fmt.Errorf("unsupported crypto type: %s", cryptoType)
 	}
 
 	if notBefore.Equal(time.Time{}) {
@@ -185,8 +153,10 @@ func CreateSelfCert(cryptoType utils.CryptoType, keyLength int, org string, cn s
 		notAfter = notBefore.Add(time.Hour * 24 * 365 * 5) // 5年
 	}
 
+	org, cn = sysinfo.CreateCASubjectLong(org, cn, domains, ips, emails, urls)
+
 	template := &x509.Certificate{
-		SerialNumber: big.NewInt(1),
+		SerialNumber: info.GetSerialNumber(),
 		Subject: pkix.Name{
 			Organization: []string{org},
 			CommonName:   cn,
@@ -202,17 +172,22 @@ func CreateSelfCert(cryptoType utils.CryptoType, keyLength int, org string, cn s
 		IPAddresses:           ips,
 		EmailAddresses:        emails,
 		URIs:                  urls,
+
+		// 此处CA证书和终端证书不同,CA显示自己的吊销列表,终端证书显示对于CA的吊销列表
+		OCSPServer:            info.CA.GetOCSPServer(),
+		IssuingCertificateURL: info.CA.GetIssuingCertificateURL(),
+		CRLDistributionPoints: info.CA.GetCRLDistributionPoints(),
 	}
 
-	derBytes, err := x509.CreateCertificate(utils.Rander(), template, template, pubKey, privKey)
+	derBytes, err := x509.CreateCertificate(utils.Rander(), template, ca, pubKey, caKey)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, nil, err
 	}
 
 	cert, err := x509.ParseCertificate(derBytes)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, nil, err
 	}
 
-	return cert, privKey, nil
+	return cert, privKey, info, nil
 }

+ 198 - 0
src/cert/selfcert.go

@@ -0,0 +1,198 @@
+// Copyright 2025 MyCA Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package cert
+
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/gob"
+	"fmt"
+	"github.com/SongZihuan/MyCA/src/sysinfo"
+	"github.com/SongZihuan/MyCA/src/utils"
+	"math/big"
+	"net"
+	"net/url"
+	"os"
+	"time"
+)
+
+type SelfCertInfo struct {
+	OCSPServer            []string
+	IssuingCertificateURL []string
+	CRLDistributionPoints []string
+
+	FilePath string `gob:"-"`
+}
+
+func init() {
+	gob.Register(SelfCertInfo{})
+}
+
+func NewSelfCertInfo(filepath string, ocsp []string, issuerURL []string, crlURL []string) (*SelfCertInfo, error) {
+	info := &SelfCertInfo{
+		OCSPServer:            ocsp,
+		IssuingCertificateURL: issuerURL,
+		CRLDistributionPoints: crlURL,
+		FilePath:              filepath,
+	}
+
+	return info, nil
+}
+
+func GetRCAInfo(filepath string) (*SelfCertInfo, error) {
+	file, err := os.Open(filepath)
+	if err != nil {
+		return nil, err
+	}
+	defer func() {
+		_ = file.Close()
+	}()
+
+	var res SelfCertInfo
+	decoder := gob.NewDecoder(file)
+	err = decoder.Decode(&res)
+	if err != nil {
+		return nil, err
+	}
+
+	res.FilePath = filepath
+
+	return &res, nil
+}
+
+func (info *SelfCertInfo) SaveSelfCert() error {
+	file, err := os.OpenFile(info.FilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		_ = file.Close()
+	}()
+
+	encoder := gob.NewEncoder(file)
+	err = encoder.Encode(*info) // 不需要以指针形式出现
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (info *SelfCertInfo) NewCert() *big.Int {
+	return big.NewInt(1)
+}
+
+func (info *SelfCertInfo) GetIssuingCertificateURL() []string {
+	return info.IssuingCertificateURL
+}
+
+func (info *SelfCertInfo) GetOCSPServer() []string {
+	return info.OCSPServer
+}
+
+func (info *SelfCertInfo) GetCRLDistributionPoints() []string {
+	return info.CRLDistributionPoints
+}
+
+// CreateSelfCert 创建自签名域名、IP证书
+func CreateSelfCert(infoFilePath string, cryptoType utils.CryptoType, keyLength int, org string, cn string, domains []string, ips []net.IP, emails []string, urls []*url.URL, ocsp []string, selfURL []string, crlURL []string, notBefore time.Time, notAfter time.Time) (*x509.Certificate, crypto.PrivateKey, *SelfCertInfo, error) {
+	var privKey crypto.PrivateKey
+	var pubKey interface{}
+
+	org, cn = sysinfo.CreateCASubjectLong(org, cn, domains, ips, emails, urls)
+
+	info, err := NewSelfCertInfo(infoFilePath, ocsp, selfURL, crlURL)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	switch cryptoType {
+	case utils.CryptoTypeRsa:
+		if keyLength != 2048 && keyLength != 4096 {
+			return nil, nil, nil, fmt.Errorf("unsupported RSA key length: %d", keyLength)
+		}
+
+		priv, err := rsa.GenerateKey(utils.Rander(), keyLength)
+		if err != nil {
+			return nil, 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, nil, fmt.Errorf("unsupported ECC key length: %d", keyLength)
+		}
+		priv, err := ecdsa.GenerateKey(curve, utils.Rander())
+		if err != nil {
+			return nil, nil, nil, err
+		}
+		privKey = priv
+		pubKey = &priv.PublicKey
+	default:
+		return nil, 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,
+		EmailAddresses:        emails,
+		URIs:                  urls,
+
+		OCSPServer:            info.OCSPServer,
+		IssuingCertificateURL: info.IssuingCertificateURL,
+		CRLDistributionPoints: info.CRLDistributionPoints,
+	}
+
+	derBytes, err := x509.CreateCertificate(utils.Rander(), template, template, pubKey, privKey)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	cert, err := x509.ParseCertificate(derBytes)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	return cert, privKey, info, nil
+}

+ 109 - 19
src/ica/ica.go

@@ -7,26 +7,119 @@ import (
 	"crypto/rsa"
 	"crypto/x509"
 	"crypto/x509/pkix"
+	"encoding/gob"
 	"fmt"
+	"github.com/SongZihuan/MyCA/src/sysinfo"
 	"github.com/SongZihuan/MyCA/src/utils"
 	"math/big"
+	"os"
 	"time"
 )
 
+type UpstreamCAInfo interface {
+	GetIssuingCertificateURL() []string
+}
+
+type ICAInfo struct {
+	SerialNumber          *big.Int
+	OCSPServer            []string
+	IssuingCertificateURL []string
+	CRLDistributionPoints []string
+	CA                    UpstreamCAInfo
+	FilePath              string `gob:"-"`
+}
+
+func init() {
+	gob.Register(ICAInfo{})
+}
+
+func NewICAInfo(filepath string, ca UpstreamCAInfo, ocsp []string, issuerURL []string, crlURL []string) (*ICAInfo, error) {
+	info := &ICAInfo{
+		SerialNumber:          big.NewInt(0),
+		OCSPServer:            ocsp,
+		IssuingCertificateURL: issuerURL,
+		CRLDistributionPoints: crlURL,
+		CA:                    ca,
+		FilePath:              filepath,
+	}
+
+	return info, nil
+}
+
+func GetICAInfo(filepath string) (*ICAInfo, error) {
+	file, err := os.Open(filepath)
+	if err != nil {
+		return nil, err
+	}
+	defer func() {
+		_ = file.Close()
+	}()
+
+	var res ICAInfo
+	decoder := gob.NewDecoder(file)
+	err = decoder.Decode(&res)
+	if err != nil {
+		return nil, err
+	}
+
+	res.FilePath = filepath
+
+	return &res, nil
+}
+
+func (info *ICAInfo) SaveICAInfo() error {
+	file, err := os.OpenFile(info.FilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		_ = file.Close()
+	}()
+
+	encoder := gob.NewEncoder(file)
+	err = encoder.Encode(*info) // 不需要以指针形式出现
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (info *ICAInfo) NewCert() *big.Int {
+	return info.SerialNumber.Add(info.SerialNumber, big.NewInt(1))
+}
+
+func (info *ICAInfo) GetIssuingCertificateURL() []string {
+	return info.IssuingCertificateURL
+}
+
+func (info *ICAInfo) GetOCSPServer() []string {
+	return info.OCSPServer
+}
+
+func (info *ICAInfo) GetCRLDistributionPoints() []string {
+	return info.CRLDistributionPoints
+}
+
 // 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) {
+func CreateICA(infoFilePath string, caInfo UpstreamCAInfo, cryptoType utils.CryptoType, keyLength int, org string, cn string, selfOSCP []string, selfURL []string, crlURL []string, notBefore time.Time, notAfter time.Time, rootCert *x509.Certificate, rootKey crypto.PrivateKey) (*x509.Certificate, crypto.PrivateKey, *ICAInfo, error) {
 	var privKey crypto.PrivateKey
 	var pubKey interface{}
 
+	info, err := NewICAInfo(infoFilePath, caInfo, selfOSCP, selfURL, crlURL)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
 	switch cryptoType {
 	case utils.CryptoTypeRsa:
 		if keyLength != 2048 && keyLength != 4096 {
-			return nil, nil, fmt.Errorf("unsupported RSA key length: %d", keyLength)
+			return nil, nil, nil, fmt.Errorf("unsupported RSA key length: %d", keyLength)
 		}
 
 		priv, err := rsa.GenerateKey(utils.Rander(), keyLength)
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, nil, err
 		}
 
 		privKey = priv
@@ -43,25 +136,19 @@ func CreateICA(cryptoType utils.CryptoType, keyLength int, org string, cn string
 		case 521:
 			curve = elliptic.P521()
 		default:
-			return nil, nil, fmt.Errorf("unsupported ECC key length: %d", keyLength)
+			return nil, nil, nil, fmt.Errorf("unsupported ECC key length: %d", keyLength)
 		}
 		priv, err := ecdsa.GenerateKey(curve, utils.Rander())
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, nil, err
 		}
 		privKey = priv
 		pubKey = &priv.PublicKey
 	default:
-		return nil, nil, fmt.Errorf("unsupported crypto type: %s", cryptoType)
+		return nil, 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
-	}
+	org, cn = sysinfo.CreateCASubject(org, cn)
 
 	if notBefore.Equal(time.Time{}) {
 		notBefore = time.Now()
@@ -71,10 +158,8 @@ func CreateICA(cryptoType utils.CryptoType, keyLength int, org string, cn string
 		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,
+		SerialNumber: info.NewCert(),
 		Subject: pkix.Name{
 			Organization: []string{org},
 			CommonName:   cn,
@@ -88,17 +173,22 @@ func CreateICA(cryptoType utils.CryptoType, keyLength int, org string, cn string
 		IsCA:                  true,
 		MaxPathLen:            -1,
 		MaxPathLenZero:        false,
+
+		// 此处CA证书和终端证书不同,CA显示自己的吊销列表,终端证书显示对于CA的吊销列表
+		OCSPServer:            info.OCSPServer,
+		IssuingCertificateURL: info.CA.GetIssuingCertificateURL(),
+		CRLDistributionPoints: info.CRLDistributionPoints,
 	}
 
 	derBytes, err := x509.CreateCertificate(utils.Rander(), template, rootCert, pubKey, rootKey)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, nil, err
 	}
 
 	cert, err := x509.ParseCertificate(derBytes)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, nil, err
 	}
 
-	return cert, privKey, nil
+	return cert, privKey, info, nil
 }

+ 376 - 127
src/mainfunc/myca/mycav1/action.go

@@ -5,8 +5,8 @@ import (
 	"github.com/SongZihuan/MyCA/src/cert"
 	"github.com/SongZihuan/MyCA/src/ica"
 	"github.com/SongZihuan/MyCA/src/rootca"
+	"github.com/SongZihuan/MyCA/src/sysinfo"
 	"github.com/SongZihuan/MyCA/src/utils"
-	"math/big"
 	"net"
 	"net/mail"
 	"net/url"
@@ -67,54 +67,116 @@ func CreateRCA() {
 	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
+	ocspURLs := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your OCSP Server URL [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			u, err := url.Parse(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid URL (%s)\n", err.Error())
+				break
+			} else if u.Scheme != "http" && u.Scheme != "https" {
+				fmt.Println("Error: not a valid HTTP/HTTPS URL")
+				break
+			}
+
+			ocspURLs = append(ocspURLs, u.String())
+		}
+	}
+
+	issurURLs := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your Issuing Certificate URL [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			u, err := url.Parse(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid URL (%s)\n", err.Error())
+				break
+			} else if u.Scheme != "http" && u.Scheme != "https" {
+				fmt.Println("Error: not a valid HTTP/HTTPS URL")
+				break
+			}
+
+			issurURLs = append(issurURLs, u.String())
+		}
+	}
+
+	crlURLs := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your CRL Distribution Points (URL) [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			u, err := url.Parse(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid URL (%s)\n", err.Error())
+				break
+			} else if u.Scheme != "http" && u.Scheme != "https" {
+				fmt.Println("Error: not a valid HTTP/HTTPS URL")
+				break
+			}
+
+			crlURLs = append(crlURLs, u.String())
+		}
 	}
 
-	dirPath := path.Join(home, "rca", fmt.Sprintf("%s-%s", caCert.Subject.Organization[0], caCert.Subject.CommonName))
+	fmt.Printf("Set a password for private key [empty is no password]: ")
+	password := ReadPassword()
+
+	org, cn = sysinfo.CreateCASubject(org, cn)
+
+	dirPath := path.Join(home, "rca", fmt.Sprintf("%s-%s", org, cn))
+	infoFile := path.Join(dirPath, "rca-info.gob")
 	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() {
+	if utils.IsExists(dirPath) {
+		fmt.Printf("There is a duplicate file, it will be overwritten. Do you confirm to save the certificate?")
+		if !ReadBoolDefaultNoPrint() {
 			return
 		}
 	} else {
-		fmt.Printf("Do you confirm to save the certificate? [yes/no] ")
-		if !ReadYes() {
+		fmt.Printf("Do you confirm to save the certificate?")
+		if !ReadBoolDefaultYesPrint() {
 			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 = os.MkdirAll(dirPath, 0600)
+	caCert, key, rcaInfo, err := rootca.CreateRCA(infoFile, cryptoType, keyLength, org, cn, ocspURLs, issurURLs, crlURLs, notBefore, notAfter)
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
 	}
 
-	err = utils.SaveCertificate(caCert, []byte{}, cert1Path, cert2Path, fullchain1Path, fullchain2Path)
+	err = rcaInfo.SaveRCAInfo()
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
 	}
 
-	err = utils.SavePrivateKey(key, password, keyPath)
+	err = utils.SaveCertificate(caCert, []byte{}, cert1Path, cert2Path, fullchain1Path, fullchain2Path)
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
 	}
 
-	err = utils.WriteBigIntToFile(serialNumberPath, big.NewInt(0))
+	err = utils.SavePrivateKey(key, password, keyPath)
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
@@ -124,7 +186,7 @@ func CreateRCA() {
 }
 
 func CreateICAFromRCA() {
-	rcaCert, rcaKey, rcaFullchain, rcaSerialNumber, err := LoadRCA()
+	rcaCert, rcaKey, rcaFullchain, rcaInfo, err := LoadRCA()
 	if err != nil {
 		fmt.Println("Error:", err.Error())
 		return
@@ -167,66 +229,122 @@ func CreateICAFromRCA() {
 	fmt.Printf("Common Name: ")
 	cn := ReadString()
 
+	org, cn = sysinfo.CreateCASubject(org, cn)
+
 	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
+	selfOcspURLs := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your OCSP Server URL [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			u, err := url.Parse(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid URL (%s)\n", err.Error())
+				break
+			} else if u.Scheme != "http" && u.Scheme != "https" {
+				fmt.Println("Error: not a valid HTTP/HTTPS URL")
+				break
+			}
+
+			selfOcspURLs = append(selfOcspURLs, u.String())
+		}
+	}
+
+	selfIssurURLs := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your Issuing Certificate URL [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			u, err := url.Parse(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid URL (%s)\n", err.Error())
+				break
+			} else if u.Scheme != "http" && u.Scheme != "https" {
+				fmt.Println("Error: not a valid HTTP/HTTPS URL")
+				break
+			}
+
+			selfIssurURLs = append(selfIssurURLs, u.String())
+		}
+	}
+
+	crlURLs := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your CRL Distribution Points (URL) [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			u, err := url.Parse(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid URL (%s)\n", err.Error())
+				break
+			} else if u.Scheme != "http" && u.Scheme != "https" {
+				fmt.Println("Error: not a valid HTTP/HTTPS URL")
+				break
+			}
+
+			crlURLs = append(crlURLs, u.String())
+		}
 	}
 
-	dirPath := path.Join(home, "ica", fmt.Sprintf("%s-%s", caCert.Subject.Organization[0], caCert.Subject.CommonName))
+	fmt.Printf("Set a password for private key: ")
+	password := ReadPassword()
+
+	dirPath := path.Join(home, "ica", fmt.Sprintf("%s-%s", org, cn))
+	infoFile := path.Join(dirPath, "rca-info.gob")
 	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() {
+	if utils.IsExists(dirPath) {
+		fmt.Printf("There is a duplicate file, it will be overwritten. Do you confirm to save the certificate?")
+		if !ReadBoolDefaultNoPrint() {
 			return
 		}
 	} else {
-		fmt.Printf("Do you confirm to save the certificate? [yes/no] ")
-		if !ReadYes() {
+		fmt.Printf("Do you confirm to save the certificate?")
+		if !ReadBoolDefaultYesPrint() {
 			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)
+	caCert, key, icaInfo, err := ica.CreateICA(infoFile, rcaInfo, cryptoType, keyLength, org, cn, selfOcspURLs, selfIssurURLs, crlURLs, notBefore, notAfter, rcaCert, rcaKey)
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
 	}
 
-	err = utils.SavePrivateKey(key, password, keyPath)
+	err = icaInfo.SaveICAInfo()
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
 	}
 
-	err = utils.WriteBigIntToFile(serialNumberPath, big.NewInt(0))
+	err = utils.SaveCertificate(caCert, rcaFullchain, cert1Path, cert2Path, fullchain1Path, fullchain2Path)
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
 	}
 
-	err = rcaSerialNumber.Save()
+	err = utils.SavePrivateKey(key, password, keyPath)
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
@@ -236,7 +354,7 @@ func CreateICAFromRCA() {
 }
 
 func CreateICAFromICA() {
-	icaCert, icaKey, icaFullchain, icaSerialNumber, err := LoadICA()
+	icaCert, icaKey, icaFullchain, icaInfo, err := LoadICA()
 	if err != nil {
 		fmt.Println("Error:", err.Error())
 		return
@@ -279,66 +397,122 @@ func CreateICAFromICA() {
 	fmt.Printf("Common Name: ")
 	cn := ReadString()
 
+	org, cn = sysinfo.CreateCASubject(org, cn)
+
 	fmt.Printf("Validity: ")
 	validity := ReadTimeDuration(time.Hour * 24 * 365 * 5)
 
-	notBefore := time.Now()
-	notAfter := notBefore.Add(validity)
+	ocspURLs := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your OCSP Server URL [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			u, err := url.Parse(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid URL (%s)\n", err.Error())
+				break
+			} else if u.Scheme != "http" && u.Scheme != "https" {
+				fmt.Println("Error: not a valid HTTP/HTTPS URL")
+				break
+			}
 
-	caCert, key, err := ica.CreateICA(cryptoType, keyLength, org, cn, notBefore, notAfter, icaSerialNumber, icaCert, icaKey)
-	if err != nil {
-		fmt.Println("Error:", err)
-		return
+			ocspURLs = append(ocspURLs, u.String())
+		}
+	}
+
+	issurURLs := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your Issuing Certificate URL [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			u, err := url.Parse(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid URL (%s)\n", err.Error())
+				break
+			} else if u.Scheme != "http" && u.Scheme != "https" {
+				fmt.Println("Error: not a valid HTTP/HTTPS URL")
+				break
+			}
+
+			issurURLs = append(issurURLs, u.String())
+		}
 	}
 
-	dirPath := path.Join(home, "ica", fmt.Sprintf("%s-%s", caCert.Subject.Organization[0], caCert.Subject.CommonName))
+	crlURLs := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your CRL Distribution Points (URL) [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			u, err := url.Parse(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid URL (%s)\n", err.Error())
+				break
+			} else if u.Scheme != "http" && u.Scheme != "https" {
+				fmt.Println("Error: not a valid HTTP/HTTPS URL")
+				break
+			}
+
+			crlURLs = append(crlURLs, u.String())
+		}
+	}
+
+	fmt.Printf("Set a password for private key: ")
+	password := ReadPassword()
+
+	notBefore := time.Now()
+	notAfter := notBefore.Add(validity)
+
+	dirPath := path.Join(home, "ica", fmt.Sprintf("%s-%s", org, cn))
+	infoFile := path.Join(dirPath, "ica-info.gob")
 	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() {
+		fmt.Printf("There is a duplicate file, it will be overwritten. Do you confirm to save the certificate?")
+		if !ReadBoolDefaultNoPrint() {
 			return
 		}
 	} else {
-		fmt.Printf("Do you confirm to save the certificate? [yes/no] ")
-		if !ReadYes() {
+		fmt.Printf("Do you confirm to save the certificate?")
+		if !ReadBoolDefaultYesPrint() {
 			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)
+	caCert, key, newIcaInfo, err := ica.CreateICA(infoFile, icaInfo, cryptoType, keyLength, org, cn, ocspURLs, issurURLs, crlURLs, notBefore, notAfter, icaCert, icaKey)
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
 	}
 
-	err = utils.SavePrivateKey(key, password, keyPath)
+	err = newIcaInfo.SaveICAInfo()
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
 	}
 
-	err = utils.WriteBigIntToFile(serialNumberPath, big.NewInt(0))
+	err = utils.SaveCertificate(caCert, icaFullchain, cert1Path, cert2Path, fullchain1Path, fullchain2Path)
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
 	}
 
-	err = icaSerialNumber.Save()
+	err = utils.SavePrivateKey(key, password, keyPath)
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
@@ -348,7 +522,7 @@ func CreateICAFromICA() {
 }
 
 func CreateUserCertFromRCA() {
-	rcaCert, rcaKey, rcaFullchain, rcaSerialNumber, err := LoadRCA()
+	rcaCert, rcaKey, rcaFullchain, rcaInfo, err := LoadRCA()
 	if err != nil {
 		fmt.Println("Error:", err.Error())
 		return
@@ -427,12 +601,12 @@ func CreateUserCertFromRCA() {
 		}
 	}
 
-	fmt.Printf("Now we need to add your email (if you have), do you want to check it from DNS? [yes/no]")
-	checkEmail := ReadYes()
+	fmt.Printf("Now we need to add your email (if you have), do you want to check it from DNS? ")
+	checkEmail := ReadBoolDefaultYesPrint()
 	StillAddEmail := true
 	if checkEmail {
-		fmt.Printf("Now we will check the email when you add it, do you want to still add it when dns check failed? [yes/no]")
-		StillAddEmail = ReadYes()
+		fmt.Printf("Now we will check the email when you add it, do you want to still add it when dns check failed? ")
+		StillAddEmail = ReadBoolDefaultYesPrint()
 	}
 
 	emails := make([]string, 0, 10)
@@ -517,10 +691,10 @@ func CreateUserCertFromRCA() {
 		ipsR = append(ipsR, ipsN...)
 	}
 
-	fmt.Printf("Add the domain in cert? [yes/no]")
-	if ReadYes() {
-		fmt.Printf("Add the all of the domain (include which the resolve failed) in cert? [yes/no]")
-		if ReadYes() {
+	fmt.Printf("Add the domain in cert? ")
+	if ReadBoolDefaultYesPrint() {
+		fmt.Printf("Add the all of the domain (include which the resolve failed) in cert? ")
+		if ReadBoolDefaultYesPrint() {
 			domains = append(domains, domainsR...)
 		} else {
 			domains = append(domains, domainsRS...)
@@ -529,13 +703,13 @@ func CreateUserCertFromRCA() {
 
 	ips = append(ips, ipsR...)
 
-	userCert, key, err := cert.CreateCert(cryptoType, keyLength, org, cn, domains, ips, emails, urls, notBefore, notAfter, rcaSerialNumber, rcaCert, rcaKey)
-	if err != nil {
-		fmt.Println("Error:", err)
-		return
-	}
+	org, cn = sysinfo.CreateCASubjectLong(org, cn, domains, ips, emails, urls)
 
-	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")))
+	fmt.Printf("Set a password for private key: ")
+	password := ReadPassword()
+
+	dirPath := path.Join(home, "cert", fmt.Sprintf("%s-%s-%s-%s-%s", rcaCert.Subject.Organization[0], rcaCert.Subject.CommonName, org, cn, notBefore.Format("2006-01-02-15-04-05")))
+	infoPath := path.Join(dirPath, "cert-info.gob")
 	cert1Path := path.Join(dirPath, "cert.pem")
 	cert2Path := path.Join(dirPath, "cert.cer")
 	fullchain1Path := path.Join(dirPath, "fullchain.pem")
@@ -543,39 +717,42 @@ func CreateUserCertFromRCA() {
 	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() {
+		fmt.Printf("There is a duplicate file, it will be overwritten. Do you confirm to save the certificate?")
+		if !ReadBoolDefaultNoPrint() {
 			return
 		}
 	} else {
-		fmt.Printf("Do you confirm to save the certificate? [yes/no] ")
-		if !ReadYes() {
+		fmt.Printf("Do you confirm to save the certificate?")
+		if !ReadBoolDefaultYesPrint() {
 			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)
+	userCert, key, certInfo, err := cert.CreateCert(infoPath, rcaInfo, cryptoType, keyLength, org, cn, domains, ips, emails, urls, notBefore, notAfter, rcaCert, rcaKey)
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
 	}
 
-	err = utils.SavePrivateKey(key, password, keyPath)
+	err = certInfo.SaveCertInfo()
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
 	}
 
-	err = rcaSerialNumber.Save()
+	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
@@ -585,7 +762,7 @@ func CreateUserCertFromRCA() {
 }
 
 func CreateUserCertFromICA() {
-	icaCert, icaKey, icaFullchain, icaSerialNumber, err := LoadICA()
+	icaCert, icaKey, icaFullchain, icaInfo, err := LoadICA()
 	if err != nil {
 		fmt.Println("Error:", err.Error())
 		return
@@ -661,12 +838,12 @@ func CreateUserCertFromICA() {
 		}
 	}
 
-	fmt.Printf("Now we need to add your email (if you have), do you want to check it from DNS? [yes/no]")
-	checkEmail := ReadYes()
+	fmt.Printf("Now we need to add your email (if you have), do you want to check it from DNS? ")
+	checkEmail := ReadBoolDefaultYesPrint()
 	StillAddEmail := true
 	if checkEmail {
-		fmt.Printf("Now we will check the email when you add it, do you want to still add it when dns check failed? [yes/no]")
-		StillAddEmail = ReadYes()
+		fmt.Printf("Now we will check the email when you add it, do you want to still add it when dns check failed? ")
+		StillAddEmail = ReadBoolDefaultYesPrint()
 	}
 
 	emails := make([]string, 0, 10)
@@ -751,10 +928,10 @@ func CreateUserCertFromICA() {
 		ipsR = append(ipsR, ipsN...)
 	}
 
-	fmt.Printf("Add the domain in cert? [yes/no]")
-	if ReadYes() {
-		fmt.Printf("Add the all of the domain (include which the resolve failed) in cert? [yes/no]")
-		if ReadYes() {
+	fmt.Printf("Add the domain in cert? ")
+	if ReadBoolDefaultYesPrint() {
+		fmt.Printf("Add the all of the domain (include which the resolve failed) in cert? ")
+		if ReadBoolDefaultYesPrint() {
 			domains = append(domains, domainsR...)
 		} else {
 			domains = append(domains, domainsRS...)
@@ -763,13 +940,13 @@ func CreateUserCertFromICA() {
 
 	ips = append(ips, ipsR...)
 
-	userCert, key, err := cert.CreateCert(cryptoType, keyLength, org, cn, domains, ips, emails, urls, notBefore, notAfter, icaSerialNumber, icaCert, icaKey)
-	if err != nil {
-		fmt.Println("Error:", err)
-		return
-	}
+	org, cn = sysinfo.CreateCASubjectLong(org, cn, domains, ips, emails, urls)
+
+	fmt.Printf("Set a password for private key: ")
+	password := ReadPassword()
 
-	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")))
+	dirPath := path.Join(home, "cert", fmt.Sprintf("%s-%s-%s-%s-%s", icaCert.Subject.Organization[0], icaCert.Subject.CommonName, org, cn, notBefore.Format("2006-01-02-15-04-05")))
+	infoPath := path.Join(dirPath, "cert-info.gob")
 	cert1Path := path.Join(dirPath, "cert.pem")
 	cert2Path := path.Join(dirPath, "cert.cer")
 	fullchain1Path := path.Join(dirPath, "fullchain.pem")
@@ -777,39 +954,42 @@ func CreateUserCertFromICA() {
 	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() {
+		fmt.Printf("There is a duplicate file, it will be overwritten. Do you confirm to save the certificate?")
+		if !ReadBoolDefaultNoPrint() {
 			return
 		}
 	} else {
-		fmt.Printf("Do you confirm to save the certificate? [yes/no] ")
-		if !ReadYes() {
+		fmt.Printf("Do you confirm to save the certificate?")
+		if !ReadBoolDefaultYesPrint() {
 			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)
+	userCert, key, certInfo, err := cert.CreateCert(infoPath, icaInfo, cryptoType, keyLength, org, cn, domains, ips, emails, urls, notBefore, notAfter, icaCert, icaKey)
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
 	}
 
-	err = utils.SavePrivateKey(key, password, keyPath)
+	err = certInfo.SaveCertInfo()
 	if err != nil {
 		fmt.Println("Error:", err)
 		return
 	}
 
-	err = icaSerialNumber.Save()
+	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
@@ -892,12 +1072,12 @@ func CreateUserCertSelf() {
 		}
 	}
 
-	fmt.Printf("Now we need to add your email (if you have), do you want to check it from DNS? [yes/no]")
-	checkEmail := ReadYes()
+	fmt.Printf("Now we need to add your email (if you have), do you want to check it from DNS? ")
+	checkEmail := ReadBoolDefaultYesPrint()
 	StillAddEmail := true
 	if checkEmail {
-		fmt.Printf("Now we will check the email when you add it, do you want to still add it when dns check failed? [yes/no]")
-		StillAddEmail = ReadYes()
+		fmt.Printf("Now we will check the email when you add it, do you want to still add it when dns check failed? ")
+		StillAddEmail = ReadBoolDefaultYesPrint()
 	}
 
 	emails := make([]string, 0, 10)
@@ -982,10 +1162,10 @@ func CreateUserCertSelf() {
 		ipsR = append(ipsR, ipsN...)
 	}
 
-	fmt.Printf("Add the domain in cert? [yes/no]")
-	if ReadYes() {
-		fmt.Printf("Add the all of the domain (include which the resolve failed) in cert? [yes/no]")
-		if ReadYes() {
+	fmt.Printf("Add the domain in cert? ")
+	if ReadBoolDefaultYesPrint() {
+		fmt.Printf("Add the all of the domain (include which the resolve failed) in cert? ")
+		if ReadBoolDefaultYesPrint() {
 			domains = append(domains, domainsR...)
 		} else {
 			domains = append(domains, domainsRS...)
@@ -994,13 +1174,73 @@ func CreateUserCertSelf() {
 
 	ips = append(ips, ipsR...)
 
-	userCert, key, err := cert.CreateSelfCert(cryptoType, keyLength, org, cn, domains, ips, emails, urls, notBefore, notAfter)
-	if err != nil {
-		fmt.Println("Error:", err)
-		return
+	org, cn = sysinfo.CreateCASubjectLong(org, cn, domains, ips, emails, urls)
+
+	ocspURLs := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your OCSP Server URL [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			u, err := url.Parse(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid URL (%s)\n", err.Error())
+				break
+			} else if u.Scheme != "http" && u.Scheme != "https" {
+				fmt.Println("Error: not a valid HTTP/HTTPS URL")
+				break
+			}
+
+			ocspURLs = append(ocspURLs, u.String())
+		}
+	}
+
+	issurURLs := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your Issuing Certificate URL [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			u, err := url.Parse(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid URL (%s)\n", err.Error())
+				break
+			} else if u.Scheme != "http" && u.Scheme != "https" {
+				fmt.Println("Error: not a valid HTTP/HTTPS URL")
+				break
+			}
+
+			issurURLs = append(issurURLs, u.String())
+		}
 	}
 
-	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")))
+	crlURLs := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your CRL Distribution Points (URL) [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			u, err := url.Parse(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid URL (%s)\n", err.Error())
+				break
+			} else if u.Scheme != "http" && u.Scheme != "https" {
+				fmt.Println("Error: not a valid HTTP/HTTPS URL")
+				break
+			}
+
+			crlURLs = append(crlURLs, u.String())
+		}
+	}
+
+	fmt.Printf("Set a password for private key: ")
+	password := ReadPassword()
+
+	dirPath := path.Join(home, "cert", fmt.Sprintf("Self-%s-%s-%s", cn, org, notBefore.Format("2006-01-02-15-04-05")))
+	infoPath := path.Join(dirPath, "cert-info.gob")
 	cert1Path := path.Join(dirPath, "cert.pem")
 	cert2Path := path.Join(dirPath, "cert.cer")
 	fullchain1Path := path.Join(dirPath, "fullchain.pem")
@@ -1008,21 +1248,30 @@ func CreateUserCertSelf() {
 	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() {
+		fmt.Printf("There is a duplicate file, it will be overwritten. Do you confirm to save the certificate?")
+		if !ReadBoolDefaultNoPrint() {
 			return
 		}
 	} else {
-		fmt.Printf("Do you confirm to save the certificate? [yes/no] ")
-		if !ReadYes() {
+		fmt.Printf("Do you confirm to save the certificate?")
+		if !ReadBoolDefaultYesPrint() {
 			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 = os.MkdirAll(dirPath, 0600)
+	userCert, key, certInfo, err := cert.CreateSelfCert(infoPath, cryptoType, keyLength, org, cn, domains, ips, emails, urls, ocspURLs, issurURLs, crlURLs, notBefore, notAfter)
+	if err != nil {
+		fmt.Println("Error:", err)
+		return
+	}
+
+	err = certInfo.SaveSelfCert()
 	if err != nil {
 		fmt.Println("Error:", err)
 		return

+ 5 - 5
src/mainfunc/myca/mycav1/loadica.go

@@ -4,13 +4,13 @@ import (
 	"crypto"
 	"crypto/x509"
 	"fmt"
+	"github.com/SongZihuan/MyCA/src/ica"
 	"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) {
+func LoadICA() (cert *x509.Certificate, key crypto.PrivateKey, fullchain []byte, icaInfo *ica.ICAInfo, err error) {
 	c := showAllICA()
 	if len(c) == 0 {
 		return nil, nil, nil, nil, fmt.Errorf("no ICA available")
@@ -31,7 +31,7 @@ func LoadICA() (cert *x509.Certificate, key crypto.PrivateKey, fullchain []byte,
 	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) {
+func loadICA(name string, passwordFunc func() string) (cert *x509.Certificate, key crypto.PrivateKey, fullchain []byte, icaInfo *ica.ICAInfo, err error) {
 	certPEM, err := utils.ReadPemBlock(path.Join(home, "ica", name, "cert.pem"))
 	if err != nil {
 		return nil, nil, nil, nil, err
@@ -69,10 +69,10 @@ func loadICA(name string, passwordFunc func() string) (cert *x509.Certificate, k
 		}
 	}
 
-	serialNumber, err = utils.ReadBigIntFromFileWithFileStack(path.Join(home, "ica", name, "serial.num"))
+	icaInfo, err = ica.GetICAInfo(path.Join(home, "ica", name, "ica-info.gob"))
 	if err != nil {
 		return nil, nil, nil, nil, err
 	}
 
-	return cert, key, fullchain, serialNumber, nil
+	return cert, key, fullchain, icaInfo, nil
 }

+ 5 - 5
src/mainfunc/myca/mycav1/loadrca.go

@@ -4,13 +4,13 @@ import (
 	"crypto"
 	"crypto/x509"
 	"fmt"
+	"github.com/SongZihuan/MyCA/src/rootca"
 	"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) {
+func LoadRCA() (cert *x509.Certificate, key crypto.PrivateKey, fullchain []byte, rcaInfo *rootca.RCAInfo, err error) {
 	c := showAllRCA()
 	if len(c) == 0 {
 		return nil, nil, nil, nil, fmt.Errorf("no RCA available")
@@ -31,7 +31,7 @@ func LoadRCA() (cert *x509.Certificate, key crypto.PrivateKey, fullchain []byte,
 	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) {
+func loadRCA(name string, passwordFunc func() string) (cert *x509.Certificate, key crypto.PrivateKey, fullchain []byte, rcaInfo *rootca.RCAInfo, err error) {
 	certPEM, err := utils.ReadPemBlock(path.Join(home, "rca", name, "cert.pem"))
 	if err != nil {
 		return nil, nil, nil, nil, err
@@ -69,10 +69,10 @@ func loadRCA(name string, passwordFunc func() string) (cert *x509.Certificate, k
 		}
 	}
 
-	serialNumber, err = utils.ReadBigIntFromFileWithFileStack(path.Join(home, "rca", name, "serial.num"))
+	rcaInfo, err = rootca.GetRCAInfo(path.Join(home, "rca", name, "rca-info.gob"))
 	if err != nil {
 		return nil, nil, nil, nil, err
 	}
 
-	return cert, key, fullchain, serialNumber, nil
+	return cert, key, fullchain, rcaInfo, nil
 }

+ 14 - 4
src/mainfunc/myca/mycav1/read.go

@@ -93,12 +93,22 @@ func ReadTimeDuration(defaultVal time.Duration) time.Duration {
 	return res
 }
 
-func ReadYes() bool {
+func ReadBoolDefaultYesPrint() bool {
+	fmt.Printf(" [default=yes/no] ")
+	return ReadBoolDefaultYes()
+}
+
+func ReadBoolDefaultYes() bool {
 	input := strings.ToLower(ReadString())
-	return input == "" || input == "yes" || input == "y"
+	return input != "n" && input != "no" && input != "not" && input != "stop"
+}
+
+func ReadBoolDefaultNoPrint() bool {
+	fmt.Printf(" [yes/default=no] ")
+	return ReadBoolDefaultNo()
 }
 
-func ReadYesMust() bool {
+func ReadBoolDefaultNo() bool {
 	input := strings.ToLower(ReadString())
-	return input == "yes" || input == "y"
+	return input == "yes" || input == "y" || input == "ok"
 }

+ 103 - 17
src/rootca/rootca.go

@@ -7,26 +7,114 @@ import (
 	"crypto/rsa"
 	"crypto/x509"
 	"crypto/x509/pkix"
+	"encoding/gob"
 	"fmt"
+	"github.com/SongZihuan/MyCA/src/sysinfo"
 	"github.com/SongZihuan/MyCA/src/utils"
 	"math/big"
+	"os"
 	"time"
 )
 
+type RCAInfo struct {
+	SerialNumber          *big.Int
+	OCSPServer            []string
+	IssuingCertificateURL []string
+	CRLDistributionPoints []string
+
+	FilePath string `gob:"-"`
+}
+
+func init() {
+	gob.Register(RCAInfo{})
+}
+
+func NewRCAInfo(filepath string, ocsp []string, issuerURL []string, crlURL []string) (*RCAInfo, error) {
+	info := &RCAInfo{
+		SerialNumber:          big.NewInt(0),
+		OCSPServer:            ocsp,
+		IssuingCertificateURL: issuerURL,
+		CRLDistributionPoints: crlURL,
+		FilePath:              filepath,
+	}
+
+	return info, nil
+}
+
+func GetRCAInfo(filepath string) (*RCAInfo, error) {
+	file, err := os.Open(filepath)
+	if err != nil {
+		return nil, err
+	}
+	defer func() {
+		_ = file.Close()
+	}()
+
+	var res RCAInfo
+	decoder := gob.NewDecoder(file)
+	err = decoder.Decode(&res)
+	if err != nil {
+		return nil, err
+	}
+
+	res.FilePath = filepath
+
+	return &res, nil
+}
+
+func (info *RCAInfo) SaveRCAInfo() error {
+	file, err := os.OpenFile(info.FilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		_ = file.Close()
+	}()
+
+	encoder := gob.NewEncoder(file)
+	err = encoder.Encode(*info) // 不需要以指针形式出现
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (info *RCAInfo) NewCert() *big.Int {
+	return info.SerialNumber.Add(info.SerialNumber, big.NewInt(1))
+}
+
+func (info *RCAInfo) GetIssuingCertificateURL() []string {
+	return info.IssuingCertificateURL
+}
+
+func (info *RCAInfo) GetOCSPServer() []string {
+	return info.OCSPServer
+}
+
+func (info *RCAInfo) GetCRLDistributionPoints() []string {
+	return info.CRLDistributionPoints
+}
+
 // CreateRCA 创建根CA证书
-func CreateRCA(cryptoType utils.CryptoType, keyLength int, org string, cn string, notBefore time.Time, notAfter time.Time) (*x509.Certificate, crypto.PrivateKey, error) {
+func CreateRCA(infoFilePath string, cryptoType utils.CryptoType, keyLength int, org string, cn string, ocsp []string, selfURL []string, crlURL []string, notBefore time.Time, notAfter time.Time) (*x509.Certificate, crypto.PrivateKey, *RCAInfo, error) {
 	var privKey crypto.PrivateKey
 	var pubKey interface{}
 
+	info, err := NewRCAInfo(infoFilePath, ocsp, selfURL, crlURL)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
 	switch cryptoType {
 	case utils.CryptoTypeRsa:
 		if keyLength != 2048 && keyLength != 4096 {
-			return nil, nil, fmt.Errorf("unsupported RSA key length: %d", keyLength)
+			return nil, nil, nil, fmt.Errorf("unsupported RSA key length: %d", keyLength)
 		}
 
 		priv, err := rsa.GenerateKey(utils.Rander(), keyLength)
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, nil, err
 		}
 
 		privKey = priv
@@ -43,25 +131,19 @@ func CreateRCA(cryptoType utils.CryptoType, keyLength int, org string, cn string
 		case 521:
 			curve = elliptic.P521()
 		default:
-			return nil, nil, fmt.Errorf("unsupported ECC key length: %d", keyLength)
+			return nil, nil, nil, fmt.Errorf("unsupported ECC key length: %d", keyLength)
 		}
 		priv, err := ecdsa.GenerateKey(curve, utils.Rander())
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, nil, err
 		}
 		privKey = priv
 		pubKey = &priv.PublicKey
 	default:
-		return nil, nil, fmt.Errorf("unsupported crypto type: %s", cryptoType)
+		return nil, 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
-	}
+	org, cn = sysinfo.CreateCASubject(org, cn)
 
 	if notBefore.Equal(time.Time{}) {
 		notBefore = time.Now()
@@ -72,7 +154,7 @@ func CreateRCA(cryptoType utils.CryptoType, keyLength int, org string, cn string
 	}
 
 	template := &x509.Certificate{
-		SerialNumber: big.NewInt(1),
+		SerialNumber: info.NewCert(),
 		Subject: pkix.Name{
 			Organization: []string{org},
 			CommonName:   cn,
@@ -86,17 +168,21 @@ func CreateRCA(cryptoType utils.CryptoType, keyLength int, org string, cn string
 		IsCA:                  true,
 		MaxPathLen:            -1,
 		MaxPathLenZero:        false,
+
+		OCSPServer:            info.OCSPServer,
+		IssuingCertificateURL: info.IssuingCertificateURL,
+		CRLDistributionPoints: info.CRLDistributionPoints,
 	}
 
 	derBytes, err := x509.CreateCertificate(utils.Rander(), template, template, pubKey, privKey)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, nil, err
 	}
 
 	cert, err := x509.ParseCertificate(derBytes)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, nil, err
 	}
 
-	return cert, privKey, nil
+	return cert, privKey, info, nil
 }

+ 26 - 0
src/sysinfo/hostname.go

@@ -0,0 +1,26 @@
+// Copyright 2025 MyCA Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package sysinfo
+
+import (
+	"fmt"
+	"github.com/SongZihuan/MyCA/src/utils"
+	"os"
+)
+
+var Hostname string
+
+func init() {
+	_hostname, err := os.Hostname()
+	if err != nil {
+		fmt.Printf("Load Hostname error: %s\n", err)
+		Hostname = utils.RandStr(8)
+	} else if _hostname == "" {
+		fmt.Println("Load Hostname error: Hostname empty")
+		Hostname = utils.RandStr(8)
+	} else {
+		Hostname = _hostname
+	}
+}

+ 46 - 0
src/sysinfo/subject.go

@@ -0,0 +1,46 @@
+// Copyright 2025 MyCA Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package sysinfo
+
+import (
+	"fmt"
+	"github.com/SongZihuan/MyCA/src/utils"
+	"net"
+	"net/url"
+)
+
+func CreateCASubject(org string, cn string) (string, string) {
+	if org == "" {
+		org = Hostname
+	}
+
+	if cn == "" {
+		cn = fmt.Sprintf("%s%02d", Username, utils.RandIntn(98)+1) // 数字范围1-99
+	}
+
+	return org, cn
+}
+
+func CreateCASubjectLong(org string, cn string, domains []string, ips []net.IP, emails []string, urls []*url.URL) (string, string) {
+	if org == "" {
+		org = Hostname
+	}
+
+	if cn == "" {
+		if len(domains) != 0 {
+			cn = domains[0]
+		} else if len(ips) != 0 {
+			cn = ips[0].String()
+		} else if len(emails) != 0 {
+			cn = emails[0]
+		} else if len(urls) != 0 {
+			cn = urls[0].String()
+		} else {
+			cn = utils.RandStr(6 + utils.RandIntn(3)) // 6-8位随机字符串
+		}
+	}
+
+	return org, cn
+}

+ 25 - 0
src/sysinfo/username.go

@@ -0,0 +1,25 @@
+// Copyright 2025 MyCA Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package sysinfo
+
+import (
+	"github.com/SongZihuan/MyCA/src/utils"
+	"os/user"
+)
+
+var Username string
+
+func init() {
+	currentUser, err := user.Current()
+	if err != nil {
+		Username = utils.RandStr(6 + utils.RandIntn(3))
+	} else if currentUser.Name != "" {
+		Username = currentUser.Name
+	} else if currentUser.Username != "" {
+		Username = currentUser.Username
+	} else {
+		Username = utils.RandStr(6 + utils.RandIntn(3))
+	}
+}

+ 20 - 0
src/utils/file.go

@@ -13,6 +13,26 @@ func IsExists(path string) bool {
 	return true
 }
 
+func IsFile(path string) bool {
+	stat, err := os.Stat(path)
+	if err != nil && errors.Is(err, os.ErrNotExist) {
+		return false
+	} else if stat.IsDir() {
+		return false
+	}
+	return true
+}
+
+func IsDir(path string) bool {
+	stat, err := os.Stat(path)
+	if err != nil && errors.Is(err, os.ErrNotExist) {
+		return false
+	} else if stat.IsDir() {
+		return true
+	}
+	return false
+}
+
 func ReadDir(dirPath string) ([]string, error) {
 	entry, err := os.ReadDir(dirPath)
 	if err != nil {