diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cbf4eab..c54a5a8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Initial release of Oragono! ### Added * Added YAML config file format. * Added native SSL/TLS support (thanks to @edmand). +* Added ability to generate certificates from the command line. * We now advertise the [`RPL_ISUPPORT`](http://modern.ircdocs.horse/#rplisupport-005) numeric. * Parse new mode change syntax commonly used these days (i.e. `+h-ov dan dan dan`). diff --git a/README.md b/README.md index d9c3580a..876d60c6 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ go install cp oragono.yaml ircd.yaml vim ircd.yaml # modify the config file to your liking oragono initdb +oragono createcerts ``` # Configuration diff --git a/oragono b/oragono new file mode 100755 index 00000000..11560e6c Binary files /dev/null and b/oragono differ diff --git a/oragono.go b/oragono.go index 2aab3e4a..fd11bf10 100644 --- a/oragono.go +++ b/oragono.go @@ -1,9 +1,19 @@ package main import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" "fmt" "log" + "math/big" + "net" + "os" "syscall" + "time" "github.com/DanielOaks/oragono/irc" "github.com/docopt/docopt-go" @@ -17,6 +27,7 @@ Usage: oragono initdb [--conf ] oragono upgradedb [--conf ] oragono genpasswd [--conf ] + oragono createcerts [--conf ] oragono run [--conf ] oragono -h | --help oragono --version @@ -52,6 +63,71 @@ Options: } else if arguments["upgradedb"].(bool) { irc.UpgradeDB(config.Server.Database) log.Println("database upgraded: ", config.Server.Database) + } else if arguments["createcerts"].(bool) { + log.Println("creating self-signed certificates") + + for name, conf := range config.Server.TLSListeners { + log.Printf(" creating cert for %s listener\n", name) + host := config.Server.Name + validFrom := time.Now() + validFor := 365 * 24 * time.Hour + notAfter := validFrom.Add(validFor) + + priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + + 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{"Oragono"}, + }, + NotBefore: validFrom, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + // TODO: allow explicitly listing allowed addresses/names + template.IPAddresses = append(template.IPAddresses, net.ParseIP("127.0.0.1")) + template.IPAddresses = append(template.IPAddresses, net.ParseIP("::1")) + template.DNSNames = append(template.DNSNames, host) + template.DNSNames = append(template.DNSNames, "localhost") + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + log.Fatalf("Failed to create certificate: %s", err) + } + + certOut, err := os.Create(conf.Cert) + if err != nil { + log.Fatalf("failed to open %s for writing: %s", conf.Cert, err) + } + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + certOut.Close() + log.Printf(" wrote %s\n", conf.Cert) + + keyOut, err := os.OpenFile(conf.Key, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Print("failed to open %s for writing:", conf.Key, err) + return + } + b, err := x509.MarshalECPrivateKey(priv) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err) + os.Exit(2) + } + pemBlock := pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + pem.Encode(keyOut, &pemBlock) + keyOut.Close() + log.Printf(" wrote %s\n", conf.Key) + } } else if arguments["run"].(bool) { irc.Log.SetLevel(config.Server.Log) server := irc.NewServer(config)