Quellcode durchsuchen

添加域名解析和电子邮件验证功能

在`utils`包中新增了域名解析和电子邮件MX记录检查的功能,并在证书生成过程中增加了对这些信息的收集与验证。同时,更新了证书创建逻辑以支持更多的用户输入类型,包括电子邮件和URL。
SongZihuan vor 1 Monat
Ursprung
Commit
5cbd8a9639
4 geänderte Dateien mit 416 neuen und 23 gelöschten Zeilen
  1. 31 16
      src/cert/cert.go
  2. 330 7
      src/mainfunc/myca/mycav1/action.go
  3. 32 0
      src/utils/dns.go
  4. 23 0
      src/utils/email.go

+ 31 - 16
src/cert/cert.go

@@ -11,21 +11,27 @@ import (
 	"github.com/SongZihuan/MyCA/src/utils"
 	"math/big"
 	"net"
+	"net/url"
 	"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) {
+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{}
 
-	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()
+	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 {
@@ -94,6 +100,8 @@ func CreateCert(cryptoType utils.CryptoType, keyLength int, org string, domains
 		IsCA:                  false,
 		DNSNames:              domains,
 		IPAddresses:           ips,
+		EmailAddresses:        emails,
+		URIs:                  urls,
 	}
 
 	derBytes, err := x509.CreateCertificate(utils.Rander(), template, ca, pubKey, caKey)
@@ -110,17 +118,22 @@ func CreateCert(cryptoType utils.CryptoType, keyLength int, org string, domains
 }
 
 // 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) {
+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) {
 	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()
+	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 {
@@ -187,6 +200,8 @@ func CreateSelfCert(cryptoType utils.CryptoType, keyLength int, org string, doma
 		IsCA:                  false,
 		DNSNames:              domains,
 		IPAddresses:           ips,
+		EmailAddresses:        emails,
+		URIs:                  urls,
 	}
 
 	derBytes, err := x509.CreateCertificate(utils.Rander(), template, template, pubKey, privKey)

+ 330 - 7
src/mainfunc/myca/mycav1/action.go

@@ -8,6 +8,8 @@ import (
 	"github.com/SongZihuan/MyCA/src/utils"
 	"math/big"
 	"net"
+	"net/mail"
+	"net/url"
 	"os"
 	"path"
 	"time"
@@ -386,6 +388,9 @@ func CreateUserCertFromRCA() {
 	fmt.Printf("Org: ")
 	org := ReadString()
 
+	fmt.Printf("Common Name: ")
+	cn := ReadString()
+
 	fmt.Printf("Validity: ")
 	validity := ReadTimeDuration(time.Hour * 24 * 365 * 5)
 
@@ -398,9 +403,11 @@ func CreateUserCertFromRCA() {
 		res := ReadString()
 		if res == "" {
 			break
-		} else {
-			domains = append(domains, res)
+		} else if !utils.IsValidDomain(res) {
+			fmt.Println("Error: not a valid domain")
+			break
 		}
+		domains = append(domains, res)
 	}
 
 	ips := make([]net.IP, 0, 10)
@@ -413,13 +420,116 @@ func CreateUserCertFromRCA() {
 			ip := net.ParseIP(res)
 			if ip == nil {
 				fmt.Println("Error: not a valid ip")
+				break
 			}
 
 			ips = append(ips, ip)
 		}
 	}
 
-	userCert, key, err := cert.CreateCert(cryptoType, keyLength, org, domains, ips, notBefore, notAfter, rcaSerialNumber, rcaCert, rcaKey)
+	fmt.Printf("Now we need to add your email (if you have), do you want to check it from DNS? [yes/no]")
+	checkEmail := ReadYes()
+	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()
+	}
+
+	emails := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your email [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			email, err := mail.ParseAddress(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid email (%s)\n", err.Error())
+				break
+			} else if !utils.IsValidEmail(email.Address) {
+				fmt.Println("Error: not a valid email")
+				break
+			} else if checkEmail {
+				if utils.CheckEmailMX(email) {
+					fmt.Printf("OK: email (%s) check success\n", res)
+				} else {
+					if StillAddEmail {
+						fmt.Printf("Warn: email (%s) check failed\n", res)
+					} else {
+						fmt.Printf("Error: email (%s) check failed\n", res)
+						break
+					}
+				}
+			}
+
+			emails = append(emails, email.Address)
+		}
+	}
+
+	urls := make([]*url.URL, 0, 10)
+	for {
+		fmt.Printf("Enter your 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
+			}
+
+			urls = append(urls, u)
+		}
+	}
+
+	domainsR := make([]string, 0, 10)
+	domainsRS := make([]string, 0, 10)
+	ipsR := make([]net.IP, 0, 10)
+	for {
+		fmt.Printf("Enter your domain and it will resolve to ip [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else if !utils.IsValidDomain(res) {
+			fmt.Println("Error: not a valid domain")
+			break
+		}
+
+		domainsR = append(domainsR, res)
+
+		ipsN, err := utils.ResolveDomainToIPs(res)
+		if err != nil {
+			fmt.Printf("Error: domain resolve error (%s)\n", err.Error())
+			break
+		} else if ipsN == nil {
+			fmt.Println("Error: domain without ip")
+			break
+		} else {
+			fmt.Printf("Domain %s resolve result: \n", res)
+			for _, i := range ipsN {
+				fmt.Printf("  - %s\n", i.String())
+			}
+			fmt.Printf("Domain %s resolve finished.\n", res)
+		}
+
+		domainsRS = append(domainsRS, res)
+		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() {
+			domains = append(domains, domainsR...)
+		} else {
+			domains = append(domains, domainsRS...)
+		}
+	}
+
+	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
@@ -515,6 +625,9 @@ func CreateUserCertFromICA() {
 	fmt.Printf("Org: ")
 	org := ReadString()
 
+	fmt.Printf("Common Name: ")
+	cn := ReadString()
+
 	fmt.Printf("Validity: ")
 	validity := ReadTimeDuration(time.Hour * 24 * 365 * 5)
 
@@ -548,7 +661,109 @@ func CreateUserCertFromICA() {
 		}
 	}
 
-	userCert, key, err := cert.CreateCert(cryptoType, keyLength, org, domains, ips, notBefore, notAfter, icaSerialNumber, icaCert, icaKey)
+	fmt.Printf("Now we need to add your email (if you have), do you want to check it from DNS? [yes/no]")
+	checkEmail := ReadYes()
+	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()
+	}
+
+	emails := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your email [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			email, err := mail.ParseAddress(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid email (%s)\n", err.Error())
+				break
+			} else if !utils.IsValidEmail(email.Address) {
+				fmt.Println("Error: not a valid email")
+				break
+			} else if checkEmail {
+				if utils.CheckEmailMX(email) {
+					fmt.Printf("OK: email (%s) check success\n", res)
+				} else {
+					if StillAddEmail {
+						fmt.Printf("Warn: email (%s) check failed\n", res)
+					} else {
+						fmt.Printf("Error: email (%s) check failed\n", res)
+						break
+					}
+				}
+			}
+
+			emails = append(emails, email.Address)
+		}
+	}
+
+	urls := make([]*url.URL, 0, 10)
+	for {
+		fmt.Printf("Enter your 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
+			}
+
+			urls = append(urls, u)
+		}
+	}
+
+	domainsR := make([]string, 0, 10)
+	domainsRS := make([]string, 0, 10)
+	ipsR := make([]net.IP, 0, 10)
+	for {
+		fmt.Printf("Enter your domain and it will resolve to ip [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else if !utils.IsValidDomain(res) {
+			fmt.Println("Error: not a valid domain")
+			break
+		}
+
+		domainsR = append(domainsR, res)
+
+		ipsN, err := utils.ResolveDomainToIPs(res)
+		if err != nil {
+			fmt.Printf("Error: domain resolve error (%s)\n", err.Error())
+			break
+		} else if ipsN == nil {
+			fmt.Println("Error: domain without ip")
+			break
+		} else {
+			fmt.Printf("Domain %s resolve result: \n", res)
+			for _, i := range ipsN {
+				fmt.Printf("  - %s\n", i.String())
+			}
+			fmt.Printf("Domain %s resolve finished.\n", res)
+		}
+
+		domainsRS = append(domainsRS, res)
+		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() {
+			domains = append(domains, domainsR...)
+		} else {
+			domains = append(domains, domainsRS...)
+		}
+	}
+
+	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
@@ -638,6 +853,9 @@ func CreateUserCertSelf() {
 	fmt.Printf("Org: ")
 	org := ReadString()
 
+	fmt.Printf("Common Name: ")
+	cn := ReadString()
+
 	fmt.Printf("Validity: ")
 	validity := ReadTimeDuration(time.Hour * 24 * 365 * 5)
 
@@ -650,9 +868,11 @@ func CreateUserCertSelf() {
 		res := ReadString()
 		if res == "" {
 			break
-		} else {
-			domains = append(domains, res)
+		} else if !utils.IsValidDomain(res) {
+			fmt.Println("Error: not a valid domain")
+			break
 		}
+		domains = append(domains, res)
 	}
 
 	ips := make([]net.IP, 0, 10)
@@ -665,13 +885,116 @@ func CreateUserCertSelf() {
 			ip := net.ParseIP(res)
 			if ip == nil {
 				fmt.Println("Error: not a valid ip")
+				break
 			}
 
 			ips = append(ips, ip)
 		}
 	}
 
-	userCert, key, err := cert.CreateSelfCert(cryptoType, keyLength, org, domains, ips, notBefore, notAfter)
+	fmt.Printf("Now we need to add your email (if you have), do you want to check it from DNS? [yes/no]")
+	checkEmail := ReadYes()
+	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()
+	}
+
+	emails := make([]string, 0, 10)
+	for {
+		fmt.Printf("Enter your email [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else {
+			email, err := mail.ParseAddress(res)
+			if err != nil {
+				fmt.Printf("Error: not a valid email (%s)\n", err.Error())
+				break
+			} else if !utils.IsValidEmail(email.Address) {
+				fmt.Println("Error: not a valid email")
+				break
+			} else if checkEmail {
+				if utils.CheckEmailMX(email) {
+					fmt.Printf("OK: email (%s) check success\n", res)
+				} else {
+					if StillAddEmail {
+						fmt.Printf("Warn: email (%s) check failed\n", res)
+					} else {
+						fmt.Printf("Error: email (%s) check failed\n", res)
+						break
+					}
+				}
+			}
+
+			emails = append(emails, email.Address)
+		}
+	}
+
+	urls := make([]*url.URL, 0, 10)
+	for {
+		fmt.Printf("Enter your 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
+			}
+
+			urls = append(urls, u)
+		}
+	}
+
+	domainsR := make([]string, 0, 10)
+	domainsRS := make([]string, 0, 10)
+	ipsR := make([]net.IP, 0, 10)
+	for {
+		fmt.Printf("Enter your domain and it will resolve to ip [empty to stop]: ")
+		res := ReadString()
+		if res == "" {
+			break
+		} else if !utils.IsValidDomain(res) {
+			fmt.Println("Error: not a valid domain")
+			break
+		}
+
+		domainsR = append(domainsR, res)
+
+		ipsN, err := utils.ResolveDomainToIPs(res)
+		if err != nil {
+			fmt.Printf("Error: domain resolve error (%s)\n", err.Error())
+			break
+		} else if ipsN == nil {
+			fmt.Println("Error: domain without ip")
+			break
+		} else {
+			fmt.Printf("Domain %s resolve result: \n", res)
+			for _, i := range ipsN {
+				fmt.Printf("  - %s\n", i.String())
+			}
+			fmt.Printf("Domain %s resolve finished.\n", res)
+		}
+
+		domainsRS = append(domainsRS, res)
+		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() {
+			domains = append(domains, domainsR...)
+		} else {
+			domains = append(domains, domainsRS...)
+		}
+	}
+
+	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

+ 32 - 0
src/utils/dns.go

@@ -0,0 +1,32 @@
+// 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 utils
+
+import (
+	"net"
+)
+
+func ResolveDomainToIPs(domain string) ([]net.IP, error) {
+	ips, err := net.LookupHost(domain)
+	if err != nil {
+		return nil, err
+	} else if len(ips) == 0 {
+		return nil, nil
+	}
+
+	return ipStringChangeToIp(ips), nil
+}
+
+func ipStringChangeToIp(ipList []string) []net.IP {
+	ips := make([]net.IP, 0, len(ipList))
+
+	for _, ipStr := range ipList {
+		if ip := net.ParseIP(ipStr); ip != nil {
+			ips = append(ips, ip)
+		}
+	}
+
+	return ips
+}

+ 23 - 0
src/utils/email.go

@@ -0,0 +1,23 @@
+// 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 utils
+
+import (
+	"net"
+	"net/mail"
+	"strings"
+)
+
+func CheckEmailMX(email *mail.Address) bool {
+	emailSplit := strings.Split(email.Address, "@")
+	if len(emailSplit) != 2 {
+		return false
+	}
+
+	// 查询MX记录
+	mxRecords, err := net.LookupMX(emailSplit[1])
+
+	return err == nil && len(mxRecords) != 0
+}