170 lines
4.1 KiB
Go
170 lines
4.1 KiB
Go
package registration
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
type Certs struct {
|
|
CACertPEM []byte
|
|
ClientCertPEM []byte
|
|
ClientKeyPEM []byte
|
|
}
|
|
|
|
type RegisterRequest struct {
|
|
CSR string `json:"csr"`
|
|
Token string `json:"token"`
|
|
}
|
|
|
|
type RegisterResponse struct {
|
|
CACert string `json:"ca_cert"`
|
|
ClientCert string `json:"client_cert"`
|
|
}
|
|
|
|
type TokenResponse struct {
|
|
Token string `json:"token"`
|
|
}
|
|
|
|
type ErrorResponse struct {
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
// GenerateKeyAndCSR generates a new ECDSA private key and CSR for the agent.
|
|
func GenerateKeyAndCSR(label string) (*ecdsa.PrivateKey, []byte, error) {
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("generate key: %w", err)
|
|
}
|
|
|
|
template := x509.CertificateRequest{
|
|
Subject: pkix.Name{
|
|
CommonName: label,
|
|
Organization: []string{"HellreigN Agent"},
|
|
},
|
|
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
|
}
|
|
|
|
csrDER, err := x509.CreateCertificateRequest(rand.Reader, &template, key)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("create csr: %w", err)
|
|
}
|
|
|
|
csrPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "CERTIFICATE REQUEST",
|
|
Bytes: csrDER,
|
|
})
|
|
|
|
return key, csrPEM, nil
|
|
}
|
|
|
|
// Register sends CSR to backend and receives signed certificates.
|
|
func Register(backendURL, token string, csrPEM []byte) (*Certs, error) {
|
|
reqBody := RegisterRequest{CSR: string(csrPEM), Token: token}
|
|
body, err := json.Marshal(reqBody)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/api/v1/agents/register", backendURL)
|
|
resp, err := http.Post(url, "application/json", bytes.NewReader(body))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("register request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
var errResp ErrorResponse
|
|
json.NewDecoder(resp.Body).Decode(&errResp)
|
|
return nil, fmt.Errorf("registration failed (status %d): %s", resp.StatusCode, errResp.Error)
|
|
}
|
|
|
|
var regResp RegisterResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(®Resp); err != nil {
|
|
return nil, fmt.Errorf("decode response: %w", err)
|
|
}
|
|
|
|
return &Certs{
|
|
CACertPEM: []byte(regResp.CACert),
|
|
ClientCertPEM: []byte(regResp.ClientCert),
|
|
}, nil
|
|
}
|
|
|
|
// SaveCerts saves CA cert, client cert, and client key to the given directory.
|
|
func SaveCerts(certDir string, certs *Certs, key *ecdsa.PrivateKey) error {
|
|
if err := os.MkdirAll(certDir, 0700); err != nil {
|
|
return fmt.Errorf("create cert dir: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(filepath.Join(certDir, "ca.crt"), certs.CACertPEM, 0644); err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(filepath.Join(certDir, "client.crt"), certs.ClientCertPEM, 0644); err != nil {
|
|
return err
|
|
}
|
|
|
|
keyDER, err := x509.MarshalECPrivateKey(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keyPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "EC PRIVATE KEY",
|
|
Bytes: keyDER,
|
|
})
|
|
if err := os.WriteFile(filepath.Join(certDir, "client.key"), keyPEM, 0600); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadCerts loads existing certificates and key from disk.
|
|
func LoadCerts(certDir string) (*Certs, *ecdsa.PrivateKey, error) {
|
|
caCert, err := os.ReadFile(filepath.Join(certDir, "ca.crt"))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
clientCert, err := os.ReadFile(filepath.Join(certDir, "client.crt"))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
clientKeyPEM, err := os.ReadFile(filepath.Join(certDir, "client.key"))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
block, _ := pem.Decode(clientKeyPEM)
|
|
if block == nil {
|
|
return nil, nil, fmt.Errorf("decode client key")
|
|
}
|
|
key, err := x509.ParseECPrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("parse client key: %w", err)
|
|
}
|
|
|
|
return &Certs{
|
|
CACertPEM: caCert,
|
|
ClientCertPEM: clientCert,
|
|
}, key, nil
|
|
}
|
|
|
|
// CertsExist checks if all certificate files exist in the directory.
|
|
func CertsExist(certDir string) bool {
|
|
files := []string{"ca.crt", "client.crt", "client.key"}
|
|
for _, f := range files {
|
|
if _, err := os.Stat(filepath.Join(certDir, f)); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|