Skip to content

Commit

Permalink
kms: export GenerateCertificate function
Browse files Browse the repository at this point in the history
This commit exposes the `GenerateCertificate` function
for clients to use and generate their own certificates
from API keys.

Callers can customize the certificate using a template.

Signed-off-by: Andreas Auernhammer <[email protected]>
  • Loading branch information
aead committed Apr 30, 2024
1 parent 5afd555 commit dba5abc
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 48 deletions.
49 changes: 1 addition & 48 deletions kms/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,8 @@ import (
"bytes"
"compress/gzip"
"context"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"math/big"
"net"
"net/http"
"net/url"
Expand Down Expand Up @@ -66,7 +61,7 @@ func NewClient(conf *Config) (*Client, error) {

tlsConf := conf.TLS.Clone()
if conf.APIKey != nil {
cert, err := generateCertificate(conf.APIKey)
cert, err := GenerateCertificate(conf.APIKey, nil)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1312,45 +1307,3 @@ func httpsURL(endpoint string) string {
}
return endpoint
}

func generateCertificate(key APIKey) (tls.Certificate, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return tls.Certificate{}, err
}

template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: key.Identity().String(),
},
NotBefore: time.Now().UTC(),
NotAfter: time.Now().UTC().Add(90 * 24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
},
BasicConstraintsValid: true,
}

certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, key.Public(), key.Private())
if err != nil {
return tls.Certificate{}, err
}
privPKCS8, err := x509.MarshalPKCS8PrivateKey(key.Private())
if err != nil {
return tls.Certificate{}, err
}
cert, err := tls.X509KeyPair(
pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}),
pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privPKCS8}),
)
if err != nil {
return tls.Certificate{}, err
}
if cert.Leaf == nil {
cert.Leaf, _ = x509.ParseCertificate(cert.Certificate[0])
}
return cert, nil
}
55 changes: 55 additions & 0 deletions kms/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@ package kms
import (
"crypto"
"crypto/ed25519"
"crypto/rand"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"encoding/pem"
"errors"
"io"
"math/big"
"strconv"
"strings"
"time"
)

// An Identity uniquely identifies a private/public key pair.
Expand Down Expand Up @@ -178,6 +184,55 @@ func ParseAPIKey(s string) (APIKey, error) {
}, nil
}

// GenerateCertificate generates a new self-signed TLS certificate
// from the given template using the APIKey's private and public key.
//
// The template may be nil. In such a case the returned certificate
// is generated using a default template and valid for 90 days.
func GenerateCertificate(key APIKey, template *x509.Certificate) (tls.Certificate, error) {
if template == nil {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return tls.Certificate{}, err
}

template = &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: key.Identity().String(),
},
NotBefore: time.Now().UTC(),
NotAfter: time.Now().UTC().Add(90 * 24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
},
BasicConstraintsValid: true,
}
}

certDER, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key.Private())
if err != nil {
return tls.Certificate{}, err
}
privPKCS8, err := x509.MarshalPKCS8PrivateKey(key.Private())
if err != nil {
return tls.Certificate{}, err
}
cert, err := tls.X509KeyPair(
pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}),
pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privPKCS8}),
)
if err != nil {
return tls.Certificate{}, err
}
if cert.Leaf == nil {
cert.Leaf, _ = x509.ParseCertificate(cert.Certificate[0])
}
return cert, nil
}

// apiKey is an APIKey implementation using Ed25519 public/private keys.
type apiKey struct {
key ed25519.PrivateKey
Expand Down

0 comments on commit dba5abc

Please sign in to comment.