diff --git a/cmd/keygen.go b/cmd/keygen.go index f9fbf14..831e8a8 100644 --- a/cmd/keygen.go +++ b/cmd/keygen.go @@ -6,10 +6,11 @@ import ( "crypto/x509" "encoding/pem" "fmt" - "io/ioutil" "log" + "os" "strings" + "github.com/MDrollette/go-i2p/reseed" "github.com/MDrollette/go-i2p/su3" "github.com/codegangsta/cli" ) @@ -22,11 +23,11 @@ func NewKeygenCommand() cli.Command { Flags: []cli.Flag{ cli.StringFlag{ Name: "signer", - Usage: "Your su3 signing ID (ex. something@mail.i2p)", + Usage: "Generate a private key and certificate for the given su3 signing ID (ex. something@mail.i2p)", }, cli.StringFlag{ Name: "host", - Usage: "Hostname to use for self-signed TLS certificate", + Usage: "Generate a self-signed TLS certificate and private key for the given host", }, }, } @@ -34,13 +35,24 @@ func NewKeygenCommand() cli.Command { func keygenAction(c *cli.Context) { signerId := c.String("signer") - if signerId == "" { - fmt.Println("--signer is required") - return + host := c.String("host") + + if signerId == "" && host == "" { + log.Fatalln("You must specify either a --host or a --signer") } + if signerId != "" { + createSigner(signerId) + } + + if host != "" { + createTLSCertificate(host) + } +} + +func createSigner(signerId string) { // generate private key - fmt.Println("Generating keys. This may take a moment...") + fmt.Println("Generating signing keys. This may take a minute...") signerKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { log.Fatalln(err) @@ -48,17 +60,53 @@ func keygenAction(c *cli.Context) { signerCert, err := su3.NewSigningCertificate(signerId, signerKey) - // save private key - privFile := strings.Replace(signerId, "@", "_at_", 1) + ".pem" - if ioutil.WriteFile(privFile, pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(signerKey)}), 0600); err != nil { - log.Fatalln(err) - } - fmt.Println("private key saved to:", privFile) - // save cert certFile := strings.Replace(signerId, "@", "_at_", 1) + ".crt" - if ioutil.WriteFile(certFile, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: signerCert}), 0755); err != nil { + certOut, err := os.Create(certFile) + if err != nil { + log.Printf("failed to open %s for writing\n", certFile) log.Fatalln(err) } - fmt.Println("certificate saved to", certFile) + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: signerCert}) + certOut.Close() + fmt.Println("signing certificate saved to:", certFile) + + // save signing private key + privFile := strings.Replace(signerId, "@", "_at_", 1) + ".pem" + keyOut, err := os.OpenFile(privFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Printf("failed to open %s for writing\n", privFile) + log.Fatalln(err) + } + pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(signerKey)}) + keyOut.Close() + fmt.Println("signing private key saved to:", privFile) +} + +func createTLSCertificate(host string) { + fmt.Println("Generating TLS keys. This may take a minute...") + priv, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + log.Fatalln("failed to generate TLS private key:", err) + } + + tlsCert, err := reseed.NewTLSCertificate(host, priv) + + // save the TLS certificate + certOut, err := os.Create("tls_cert.pem") + if err != nil { + log.Fatalln("failed to open tls_cert.pem for writing:", err) + } + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: tlsCert}) + certOut.Close() + fmt.Println("TLS certificate saved to: tls_cert.pem") + + // save the TLS private key + keyOut, err := os.OpenFile("tls_key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Fatalln("failed to open tls_key.pem for writing:", err) + } + pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) + keyOut.Close() + fmt.Println("TLS private key saved to: tls_key.pem") } diff --git a/cmd/reseeder.go b/cmd/reseeder.go index 3b18546..ddfe8ac 100644 --- a/cmd/reseeder.go +++ b/cmd/reseeder.go @@ -32,19 +32,19 @@ func NewReseedCommand() cli.Command { }, cli.StringFlag{ Name: "port", - Value: "9090", + Value: "8080", Usage: "Port to listen on", }, cli.StringFlag{ - Name: "tlscert", - Usage: "Path to tls certificate", + Name: "tlsCert", + Usage: "Path to TLS certificate", }, cli.StringFlag{ - Name: "tlskey", - Usage: "Path to tls key", + Name: "tlsKey", + Usage: "Path to TLS private key", }, cli.StringFlag{ - Name: "keyfile", + Name: "keyFile", Value: "reseed_private.pem", Usage: "Path to your su3 signing private key", }, @@ -64,7 +64,7 @@ func NewReseedCommand() cli.Command { }, cli.StringFlag{ Name: "prefix", - Usage: "Prefix path for server. (ex. /netdb)", + Usage: "Prefix path for the HTTP(S) server. (ex. /netdb)", }, cli.BoolFlag{ Name: "trustProxy", @@ -125,7 +125,7 @@ func reseedAction(c *cli.Context) { log.Printf("Server listening on %s\n", server.Addr) - if c.String("tlscert") != "" && c.String("tlskey") != "" { + if c.String("tlsCert") != "" && c.String("tlsKey") != "" { log.Fatalln(server.ListenAndServeTLS(c.String("tlscert"), c.String("tlskey"))) } else { log.Fatalln(server.ListenAndServe()) diff --git a/reseed/keystore.go b/reseed/keystore.go deleted file mode 100644 index 13c1ad7..0000000 --- a/reseed/keystore.go +++ /dev/null @@ -1,28 +0,0 @@ -package reseed - -import ( - "crypto/x509" - "encoding/pem" - "io/ioutil" - "path/filepath" - "strings" -) - -type KeyStore struct { - Path string -} - -func (ks *KeyStore) ReseederCertificate(signer []byte) (*x509.Certificate, error) { - certFile := filepath.Base(SignerFilename(string(signer))) - certString, err := ioutil.ReadFile(filepath.Join(ks.Path, "reseed", certFile)) - if nil != err { - return nil, err - } - - certPem, _ := pem.Decode(certString) - return x509.ParseCertificate(certPem.Bytes) -} - -func SignerFilename(signer string) string { - return strings.Replace(signer, "@", "_at_", 1) + ".crt" -} diff --git a/reseed/utils.go b/reseed/utils.go new file mode 100644 index 0000000..aeb11cd --- /dev/null +++ b/reseed/utils.go @@ -0,0 +1,80 @@ +package reseed + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "io/ioutil" + "math/big" + "net" + "path/filepath" + "strings" + "time" +) + +type KeyStore struct { + Path string +} + +func (ks *KeyStore) ReseederCertificate(signer []byte) (*x509.Certificate, error) { + certFile := filepath.Base(SignerFilename(string(signer))) + certString, err := ioutil.ReadFile(filepath.Join(ks.Path, "reseed", certFile)) + if nil != err { + return nil, err + } + + certPem, _ := pem.Decode(certString) + return x509.ParseCertificate(certPem.Bytes) +} + +func SignerFilename(signer string) string { + return strings.Replace(signer, "@", "_at_", 1) + ".crt" +} + +func NewTLSCertificate(host string, priv *rsa.PrivateKey) ([]byte, error) { + notBefore := time.Now() + notAfter := notBefore.Add(2 * 365 * 24 * time.Hour) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, err + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"I2P Anonymous Network"}, + OrganizationalUnit: []string{"I2P"}, + Locality: []string{"XX"}, + StreetAddress: []string{"XX"}, + Country: []string{"XX"}, + CommonName: host, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: true, + } + + hosts := strings.Split(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) + } + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return nil, err + } + + return derBytes, nil +}