Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
1 change: 0 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
134 changes: 17 additions & 117 deletions service/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}