diff --git a/cmd/cert.go b/cmd/cert.go new file mode 100644 index 00000000..b693b7d9 --- /dev/null +++ b/cmd/cert.go @@ -0,0 +1,158 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2014 The Gogs 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 cmd + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "log" + "math/big" + "net" + "os" + "strings" + "time" + + "github.com/codegangsta/cli" +) + +var CmdCert = cli.Command{ + Name: "cert", + Usage: "Generate self-signed certificate", + Description: `Generate a self-signed X.509 certificate for a TLS server. +Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`, + Action: runCert, + Flags: []cli.Flag{ + cli.StringFlag{"host", "", "Comma-separated hostnames and IPs to generate a certificate for", ""}, + cli.StringFlag{"ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521", ""}, + cli.IntFlag{"rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set", ""}, + cli.StringFlag{"start-date", "", "Creation date formatted as Jan 1 15:04:05 2011", ""}, + cli.DurationFlag{"duration", 365 * 24 * time.Hour, "Duration that certificate is valid for", ""}, + cli.BoolFlag{"ca", "whether this cert should be its own Certificate Authority", ""}, + }, +} + +func publicKey(priv interface{}) interface{} { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &k.PublicKey + case *ecdsa.PrivateKey: + return &k.PublicKey + default: + return nil + } +} + +func pemBlockForKey(priv interface{}) *pem.Block { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} + case *ecdsa.PrivateKey: + b, err := x509.MarshalECPrivateKey(k) + if err != nil { + log.Fatal("unable to marshal ECDSA private key: %v", err) + } + return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + default: + return nil + } +} + +func runCert(ctx *cli.Context) { + if len(ctx.String("host")) == 0 { + log.Fatal("Missing required --host parameter") + } + + var priv interface{} + var err error + switch ctx.String("ecdsa-curve") { + case "": + priv, err = rsa.GenerateKey(rand.Reader, ctx.Int("rsa-bits")) + case "P224": + priv, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader) + case "P256": + priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + case "P384": + priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + case "P521": + priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + default: + log.Fatalf("Unrecognized elliptic curve: %q", ctx.String("ecdsa-curve")) + } + if err != nil { + log.Fatalf("Failed to generate private key: %s", err) + } + + var notBefore time.Time + if len(ctx.String("start-date")) == 0 { + notBefore = time.Now() + } else { + notBefore, err = time.Parse("Jan 2 15:04:05 2006", ctx.String("start-date")) + if err != nil { + log.Fatalf("Failed to parse creation date: %s", err) + } + } + + notAfter := notBefore.Add(ctx.Duration("duration")) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + log.Fatalf("Failed to generate serial number: %s", err) + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + hosts := strings.Split(ctx.String("host"), ",") + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + if ctx.Bool("ca") { + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) + if err != nil { + log.Fatalf("Failed to create certificate: %s", err) + } + + certOut, err := os.Create("cert.pem") + if err != nil { + log.Fatalf("Failed to open cert.pem for writing: %s", err) + } + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + certOut.Close() + log.Println("Written cert.pem") + + keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Fatal("failed to open key.pem for writing: %v", err) + } + pem.Encode(keyOut, pemBlockForKey(priv)) + keyOut.Close() + log.Println("Written key.pem") +} diff --git a/conf/app.ini b/conf/app.ini index ba73a0e8..6c65fd21 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -21,7 +21,7 @@ OFFLINE_MODE = false DISABLE_ROUTER_LOG = false ; Generate steps: ; $ cd path/to/gogs/custom/https -; $ go run $GOROOT/src/pkg/crypto/tls/generate_cert.go -ca=true -duration=8760h0m0s -host=myhost.example.com +; $ ./gogs cert -ca=true -duration=8760h0m0s -host=myhost.example.com CERT_FILE = custom/https/cert.pem KEY_FILE = custom/https/key.pem ; Upper level of template and static file path diff --git a/gogs.go b/gogs.go index d565d7bb..84733042 100644 --- a/gogs.go +++ b/gogs.go @@ -35,6 +35,7 @@ func main() { cmd.CmdUpdate, cmd.CmdFix, cmd.CmdDump, + cmd.CmdCert, } app.Flags = append(app.Flags, []cli.Flag{}...) app.Run(os.Args) diff --git a/modules/log/log.go b/modules/log/log.go index 8f4de1e1..4fb74d40 100644 --- a/modules/log/log.go +++ b/modules/log/log.go @@ -87,6 +87,12 @@ func Fatal(skip int, format string, v ...interface{}) { os.Exit(1) } +func Close() { + for _, l := range loggers { + l.Close() + } +} + // .___ __ _____ // | | _____/ |_ ____________/ ____\____ ____ ____ // | |/ \ __\/ __ \_ __ \ __\\__ \ _/ ___\/ __ \