From 9e84ce38dd7c9cd6a326e6e88bffc8617c714919 Mon Sep 17 00:00:00 2001 From: mzz2017 Date: Mon, 9 Mar 2020 14:33:12 +0800 Subject: [PATCH] feat: support more types of certificates --- common/protocol/tls/cert/.gitignore | 1 + common/protocol/tls/cert/cert.go | 45 +++++++++++-- common/protocol/tls/cert/cert_test.go | 91 ++++++++++++++++++++++++++ common/protocol/tls/cert/privateKey.go | 44 +++++++++++++ 4 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 common/protocol/tls/cert/.gitignore create mode 100644 common/protocol/tls/cert/cert_test.go create mode 100644 common/protocol/tls/cert/privateKey.go diff --git a/common/protocol/tls/cert/.gitignore b/common/protocol/tls/cert/.gitignore new file mode 100644 index 000000000..612424a3c --- /dev/null +++ b/common/protocol/tls/cert/.gitignore @@ -0,0 +1 @@ +*.pem \ No newline at end of file diff --git a/common/protocol/tls/cert/cert.go b/common/protocol/tls/cert/cert.go index 36598c66a..6c2b8bc3b 100644 --- a/common/protocol/tls/cert/cert.go +++ b/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 } diff --git a/common/protocol/tls/cert/cert_test.go b/common/protocol/tls/cert/cert_test.go new file mode 100644 index 000000000..9b6e59b60 --- /dev/null +++ b/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)) +} diff --git a/common/protocol/tls/cert/privateKey.go b/common/protocol/tls/cert/privateKey.go new file mode 100644 index 000000000..52a8e68c9 --- /dev/null +++ b/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"` +}