Browse Source

feat: support more types of certificates

mzz2017 5 years ago
parent
commit
9e84ce38dd

+ 1 - 0
common/protocol/tls/cert/.gitignore

@@ -0,0 +1 @@
+*.pem

+ 39 - 6
common/protocol/tls/cert/cert.go

@@ -1,9 +1,13 @@
 package cert
 
 import (
+	"crypto/ecdsa"
+	"crypto/ed25519"
+	"crypto/elliptic"
 	"crypto/rand"
 	"crypto/rsa"
 	"crypto/x509"
+	"encoding/asn1"
 	"encoding/pem"
 	"math/big"
 	"time"
@@ -90,15 +94,39 @@ func MustGenerate(parent *Certificate, opts ...Option) *Certificate {
 	return cert
 }
 
+func publicKey(priv interface{}) interface{} {
+	switch k := priv.(type) {
+	case *rsa.PrivateKey:
+		return &k.PublicKey
+	case *ecdsa.PrivateKey:
+		return &k.PublicKey
+	case ed25519.PrivateKey:
+		return k.Public().(ed25519.PublicKey)
+	default:
+		return nil
+	}
+}
+
 func Generate(parent *Certificate, opts ...Option) (*Certificate, error) {
-	selfKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	var (
+		pKey      interface{}
+		parentKey interface{}
+		err       error
+	)
+	// higher signing performance than RSA2048
+	selfKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
 		return nil, newError("failed to generate self private key").Base(err)
 	}
-
-	parentKey := selfKey
+	parentKey = selfKey
 	if parent != nil {
-		pKey, err := x509.ParsePKCS1PrivateKey(parent.PrivateKey)
+		if _, e := asn1.Unmarshal(parent.PrivateKey, &ecPrivateKey{}); e == nil {
+			pKey, err = x509.ParseECPrivateKey(parent.PrivateKey)
+		} else if _, e := asn1.Unmarshal(parent.PrivateKey, &pkcs8{}); e == nil {
+			pKey, err = x509.ParsePKCS8PrivateKey(parent.PrivateKey)
+		} else if _, e := asn1.Unmarshal(parent.PrivateKey, &pkcs1PrivateKey{}); e == nil {
+			pKey, err = x509.ParsePKCS1PrivateKey(parent.PrivateKey)
+		}
 		if err != nil {
 			return nil, newError("failed to parse parent private key").Base(err)
 		}
@@ -133,13 +161,18 @@ func Generate(parent *Certificate, opts ...Option) (*Certificate, error) {
 		parentCert = pCert
 	}
 
-	derBytes, err := x509.CreateCertificate(rand.Reader, template, parentCert, selfKey.Public(), parentKey)
+	derBytes, err := x509.CreateCertificate(rand.Reader, template, parentCert, publicKey(selfKey), parentKey)
 	if err != nil {
 		return nil, newError("failed to create certificate").Base(err)
 	}
 
+	privateKey, err := x509.MarshalPKCS8PrivateKey(selfKey)
+	if err != nil {
+		return nil, newError("Unable to marshal private key").Base(err)
+	}
+
 	return &Certificate{
 		Certificate: derBytes,
-		PrivateKey:  x509.MarshalPKCS1PrivateKey(selfKey),
+		PrivateKey:  privateKey,
 	}, nil
 }

+ 91 - 0
common/protocol/tls/cert/cert_test.go

@@ -0,0 +1,91 @@
+package cert
+
+import (
+	"context"
+	"crypto/x509"
+	"encoding/json"
+	"os"
+	"strings"
+	"testing"
+	"time"
+	"v2ray.com/core/common"
+	"v2ray.com/core/common/task"
+)
+
+func TestGenerate(t *testing.T) {
+	err := generate(nil, true, true, "ca")
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func generate(domainNames []string, isCA bool, jsonOutput bool, fileOutput string) error {
+	commonName := "V2Ray Root CA"
+	organization := "V2Ray Inc"
+
+	expire := time.Hour * 3
+
+	var opts []Option
+	if isCA {
+		opts = append(opts, Authority(isCA))
+		opts = append(opts, KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature))
+	}
+
+	opts = append(opts, NotAfter(time.Now().Add(expire)))
+	opts = append(opts, CommonName(commonName))
+	if len(domainNames) > 0 {
+		opts = append(opts, DNSNames(domainNames...))
+	}
+	opts = append(opts, Organization(organization))
+
+	cert, err := Generate(nil, opts...)
+	if err != nil {
+		return newError("failed to generate TLS certificate").Base(err)
+	}
+
+	if jsonOutput {
+		printJson(cert)
+	}
+
+	if len(fileOutput) > 0 {
+		if err := printFile(cert, fileOutput); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+type jsonCert struct {
+	Certificate []string `json:"certificate"`
+	Key         []string `json:"key"`
+}
+
+func printJson(certificate *Certificate) {
+	certPEM, keyPEM := certificate.ToPEM()
+	jCert := &jsonCert{
+		Certificate: strings.Split(strings.TrimSpace(string(certPEM)), "\n"),
+		Key:         strings.Split(strings.TrimSpace(string(keyPEM)), "\n"),
+	}
+	content, err := json.MarshalIndent(jCert, "", "  ")
+	common.Must(err)
+	os.Stdout.Write(content)
+	os.Stdout.WriteString("\n")
+}
+func printFile(certificate *Certificate, name string) error {
+	certPEM, keyPEM := certificate.ToPEM()
+	return task.Run(context.Background(), func() error {
+		return writeFile(certPEM, name+"_cert.pem")
+	}, func() error {
+		return writeFile(keyPEM, name+"_key.pem")
+	})
+}
+func writeFile(content []byte, name string) error {
+	f, err := os.Create(name)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	return common.Error2(f.Write(content))
+}

+ 44 - 0
common/protocol/tls/cert/privateKey.go

@@ -0,0 +1,44 @@
+package cert
+
+import (
+	"crypto/x509/pkix"
+	"encoding/asn1"
+	"math/big"
+)
+
+type ecPrivateKey struct {
+	Version       int
+	PrivateKey    []byte
+	NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
+	PublicKey     asn1.BitString        `asn1:"optional,explicit,tag:1"`
+}
+
+type pkcs8 struct {
+	Version    int
+	Algo       pkix.AlgorithmIdentifier
+	PrivateKey []byte
+	// optional attributes omitted.
+}
+
+type pkcs1AdditionalRSAPrime struct {
+	Prime *big.Int
+
+	// We ignore these values because rsa will calculate them.
+	Exp   *big.Int
+	Coeff *big.Int
+}
+
+type pkcs1PrivateKey struct {
+	Version int
+	N       *big.Int
+	E       int
+	D       *big.Int
+	P       *big.Int
+	Q       *big.Int
+	// We ignore these values, if present, because rsa will calculate them.
+	Dp   *big.Int `asn1:"optional"`
+	Dq   *big.Int `asn1:"optional"`
+	Qinv *big.Int `asn1:"optional"`
+
+	AdditionalPrimes []pkcs1AdditionalRSAPrime `asn1:"optional,omitempty"`
+}