diff --git a/CHANGELOG.md b/CHANGELOG.md index 92e38592..9c13bb8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changes from version 0.11.1 to master -- Changed default ECDSA curve used by `arangodb create tls ...` from `P521` to `p256`. +- Changed TLS algorithm for `-ssl.auto-key` from RSA (2048 bits) to ECDSA (`P256` curve). +- Changed default ECDSA curve used by `arangodb create tls ...` from `P521` to `P256`. - Log text showing the address the starter is listening on has been changed from "Listening on ..." to "ArangoDB Starter listening on ...". - Solved problem where starter did not properly log a resilientsingle server ("Your resilient single server can now be accessed ...") when leadership challenge was still ongoing. diff --git a/main.go b/main.go index 08f91afa..07c3fe60 100644 --- a/main.go +++ b/main.go @@ -579,7 +579,6 @@ func mustPrepareService(generateAutoKeyFile bool) (*service.Service, service.Boo } keyFile, err := service.CreateCertificate(service.CreateCertificateOptions{ Hosts: hosts, - RSABits: 2048, Organization: sslAutoOrganization, }, dataDir) if err != nil { diff --git a/service/certificate.go b/service/certificate.go index e3a5f1e7..b10ea402 100644 --- a/service/certificate.go +++ b/service/certificate.go @@ -23,121 +23,48 @@ package service import ( - "crypto" - "crypto/ecdsa" - "crypto/rand" - "crypto/rsa" "crypto/tls" - "crypto/x509" "crypto/x509/pkix" - "encoding/pem" - "errors" - "fmt" "io/ioutil" - "math/big" - "net" "strings" "time" + + certificates "github.com/arangodb-helper/go-certificates" ) // CreateCertificateOptions configures how to create a certificate. type CreateCertificateOptions struct { Hosts []string // Host names and/or IP addresses ValidFor time.Duration - RSABits int Organization string } const ( defaultValidFor = time.Hour * 24 * 365 // 1year + defaultCurve = "P256" ) -func publicKey(priv interface{}) interface{} { - switch k := priv.(type) { - case *rsa.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)} - default: - return nil - } -} - -// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates -// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys. -// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three. -func parsePrivateKey(der []byte) (crypto.PrivateKey, error) { - if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { - return key, nil - } - if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { - switch key := key.(type) { - case *rsa.PrivateKey, *ecdsa.PrivateKey: - return key, nil - default: - return nil, maskAny(errors.New("tls: found unknown private key type in PKCS#8 wrapping")) - } - } - if key, err := x509.ParseECPrivateKey(der); err == nil { - return key, nil - } - - return nil, maskAny(errors.New("tls: failed to parse private key")) -} - // CreateCertificate creates a self-signed certificate according to the given configuration. // The resulting certificate + private key will be written into a single file in the given folder. // The path of that single file is returned. func CreateCertificate(options CreateCertificateOptions, folder string) (string, error) { - priv, err := rsa.GenerateKey(rand.Reader, options.RSABits) - if err != nil { - return "", maskAny(err) - } - - notBefore := time.Now() if options.ValidFor == 0 { options.ValidFor = defaultValidFor } - notAfter := notBefore.Add(options.ValidFor) - - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return "", maskAny(fmt.Errorf("failed to generate serial number: %v", err)) - } - - template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ + certOpts := certificates.CreateCertificateOptions{ + Hosts: options.Hosts, + Subject: &pkix.Name{ Organization: []string{options.Organization}, }, - NotBefore: notBefore, - NotAfter: notAfter, - - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } - - for _, h := range options.Hosts { - if ip := net.ParseIP(h); ip != nil { - template.IPAddresses = append(template.IPAddresses, ip) - } else { - template.DNSNames = append(template.DNSNames, h) - } + ValidFrom: time.Now(), + ValidFor: options.ValidFor, + ECDSACurve: defaultCurve, } - // Create the certificate - derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) + // Create self-signed certificate + cert, priv, err := certificates.CreateCertificate(certOpts, nil) if err != nil { - return "", maskAny(fmt.Errorf("Failed to create certificate: %v", err)) + return "", maskAny(err) } // Write the certificate to disk @@ -146,46 +73,19 @@ func CreateCertificate(options CreateCertificateOptions, folder string) (string, return "", maskAny(err) } defer f.Close() - // Public key - pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - // Private key - pem.Encode(f, pemBlockForKey(priv)) + content := strings.TrimSpace(cert) + "\n" + priv + if _, err := f.WriteString(content); err != nil { + return "", maskAny(err) + } return f.Name(), nil } // LoadKeyFile loads a SSL keyfile formatted for the arangod server. func LoadKeyFile(keyFile string) (tls.Certificate, error) { - raw, err := ioutil.ReadFile(keyFile) + result, err := certificates.LoadKeyFile(keyFile) if err != nil { return tls.Certificate{}, maskAny(err) } - - result := tls.Certificate{} - for { - var derBlock *pem.Block - derBlock, raw = pem.Decode(raw) - if derBlock == nil { - break - } - if derBlock.Type == "CERTIFICATE" { - result.Certificate = append(result.Certificate, derBlock.Bytes) - } else if derBlock.Type == "PRIVATE KEY" || strings.HasSuffix(derBlock.Type, " PRIVATE KEY") { - if result.PrivateKey == nil { - result.PrivateKey, err = parsePrivateKey(derBlock.Bytes) - if err != nil { - return tls.Certificate{}, maskAny(err) - } - } - } - } - - if len(result.Certificate) == 0 { - return tls.Certificate{}, maskAny(fmt.Errorf("No certificates found in %s", keyFile)) - } - if result.PrivateKey == nil { - return tls.Certificate{}, maskAny(fmt.Errorf("No private key found in %s", keyFile)) - } - return result, nil }