diff options
author | Martin Polden <mpolden@mpolden.no> | 2021-08-25 14:07:36 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2021-08-25 18:56:03 +0200 |
commit | 1d7f47a7d48fb36d27d6c86d7d0aa8ebdabb753f (patch) | |
tree | 8f25feb2c99dd667ae220dfa1086270f926032f5 /client | |
parent | ec5fe5c67b98311e1b7bb9c0eda20750564eb54a (diff) |
Implement key pair generation
Diffstat (limited to 'client')
-rw-r--r-- | client/go/vespa/crypto.go | 89 | ||||
-rw-r--r-- | client/go/vespa/crypto_test.go | 13 |
2 files changed, 102 insertions, 0 deletions
diff --git a/client/go/vespa/crypto.go b/client/go/vespa/crypto.go new file mode 100644 index 00000000000..b9e41d81576 --- /dev/null +++ b/client/go/vespa/crypto.go @@ -0,0 +1,89 @@ +package vespa + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "errors" + "fmt" + "math/big" + "os" + "time" +) + +const ( + defaultCommonName = "cloud.vespa.example" + certificateExpiry = 3650 * 24 * time.Hour // Approximately 10 years + tempFilePattern = "vespa" +) + +// PemKeyPair represents a PEM-encoded private key and X509 certificate. +type PemKeyPair struct { + Certificate []byte + PrivateKey []byte +} + +// WriteCertificateFile writes the certificate contained in this key pair to certificateFile. +func (kp *PemKeyPair) WriteCertificateFile(certificateFile string, overwrite bool) error { + return atomicWriteFile(certificateFile, kp.Certificate, overwrite) +} + +// WritePrivateKeyFile writes the private key contained in this key pair to privateKeyFile. +func (kp *PemKeyPair) WritePrivateKeyFile(privateKeyFile string, overwrite bool) error { + return atomicWriteFile(privateKeyFile, kp.PrivateKey, overwrite) +} + +func atomicWriteFile(filename string, data []byte, overwrite bool) error { + tmpFile, err := os.CreateTemp("", tempFilePattern) + if err != nil { + return err + } + defer os.Remove(tmpFile.Name()) + if err := os.WriteFile(tmpFile.Name(), data, 0600); err != nil { + return err + } + _, err = os.Stat(filename) + if errors.Is(err, os.ErrNotExist) || overwrite { + return os.Rename(tmpFile.Name(), filename) + } + return fmt.Errorf("cannot overwrite existing file: %s", filename) +} + +// CreateKeyPair creates a key pair containing a private key and self-signed X509 certificate. +func CreateKeyPair() (PemKeyPair, error) { + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return PemKeyPair{}, fmt.Errorf("failed to generate private key: %w", err) + } + serialNumber, err := randomSerialNumber() + if err != nil { + return PemKeyPair{}, fmt.Errorf("failed to create serial number: %w", err) + } + notBefore := time.Now() + notAfter := notBefore.Add(certificateExpiry) + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{CommonName: defaultCommonName}, + NotBefore: notBefore, + NotAfter: notAfter, + } + certificateDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return PemKeyPair{}, err + } + privateKeyDER, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return PemKeyPair{}, err + } + pemPrivateKey := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyDER}) + pemCertificate := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificateDER}) + return PemKeyPair{Certificate: pemCertificate, PrivateKey: pemPrivateKey}, nil +} + +func randomSerialNumber() (*big.Int, error) { + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + return rand.Int(rand.Reader, serialNumberLimit) +} diff --git a/client/go/vespa/crypto_test.go b/client/go/vespa/crypto_test.go new file mode 100644 index 00000000000..5ac8464e4b0 --- /dev/null +++ b/client/go/vespa/crypto_test.go @@ -0,0 +1,13 @@ +package vespa + +import ( + "testing" + "github.com/stretchr/testify/assert" +) + +func TestCreateKeyPair(t *testing.T) { + kp, err := CreateKeyPair() + assert.Nil(t, err) + assert.NotEmpty(t, kp.Certificate) + assert.NotEmpty(t, kp.PrivateKey) +} |