mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-05-13 03:21:31 +00:00
145 lines
4.3 KiB
Go
145 lines
4.3 KiB
Go
package appview
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"database/sql"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"log/slog"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"atcr.io/pkg/appview/db"
|
|
"github.com/bluesky-social/indigo/atproto/atcrypto"
|
|
)
|
|
|
|
// loadOAuthKey loads the OAuth P-256 key from the DB, generating one if absent.
|
|
func loadOAuthKey(database *sql.DB) (*atcrypto.PrivateKeyP256, error) {
|
|
data, err := db.GetCryptoKey(database, "oauth_p256")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query crypto_keys: %w", err)
|
|
}
|
|
if data != nil {
|
|
key, err := atcrypto.ParsePrivateBytesP256(data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse OAuth key from database: %w", err)
|
|
}
|
|
slog.Info("Loaded OAuth P-256 key from database")
|
|
return key, nil
|
|
}
|
|
|
|
p256Key, err := atcrypto.GeneratePrivateKeyP256()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate OAuth P-256 key: %w", err)
|
|
}
|
|
|
|
keyBytes := p256Key.Bytes()
|
|
if err := db.PutCryptoKey(database, "oauth_p256", keyBytes); err != nil {
|
|
return nil, fmt.Errorf("failed to store generated OAuth key in database: %w", err)
|
|
}
|
|
slog.Info("Generated new OAuth P-256 key and stored in database")
|
|
|
|
return p256Key, nil
|
|
}
|
|
|
|
// loadJWTKeyAndCert loads the JWT RSA key from the DB and generates a self-signed
|
|
// certificate. The cert is always regenerated and written to certPath on disk
|
|
// because the distribution library reads it via os.Open().
|
|
func loadJWTKeyAndCert(database *sql.DB, certPath string) (*rsa.PrivateKey, []byte, error) {
|
|
rsaKey, err := loadRSAKey(database)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
certDER, err := generateAndWriteCert(rsaKey, certPath)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return rsaKey, certDER, nil
|
|
}
|
|
|
|
// loadRSAKey loads the RSA private key from the DB, generating one if absent.
|
|
func loadRSAKey(database *sql.DB) (*rsa.PrivateKey, error) {
|
|
data, err := db.GetCryptoKey(database, "jwt_rsa")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query crypto_keys: %w", err)
|
|
}
|
|
if data != nil {
|
|
key, err := parseRSAKeyPEM(data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse RSA key from database: %w", err)
|
|
}
|
|
slog.Info("Loaded JWT RSA key from database")
|
|
return key, nil
|
|
}
|
|
|
|
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate RSA key: %w", err)
|
|
}
|
|
|
|
keyPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "RSA PRIVATE KEY",
|
|
Bytes: x509.MarshalPKCS1PrivateKey(rsaKey),
|
|
})
|
|
if err := db.PutCryptoKey(database, "jwt_rsa", keyPEM); err != nil {
|
|
return nil, fmt.Errorf("failed to store generated RSA key in database: %w", err)
|
|
}
|
|
slog.Info("Generated new JWT RSA key and stored in database")
|
|
|
|
return rsaKey, nil
|
|
}
|
|
|
|
func parseRSAKeyPEM(data []byte) (*rsa.PrivateKey, error) {
|
|
block, _ := pem.Decode(data)
|
|
if block == nil || block.Type != "RSA PRIVATE KEY" {
|
|
return nil, fmt.Errorf("failed to decode PEM block containing RSA private key")
|
|
}
|
|
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
}
|
|
|
|
// generateAndWriteCert creates a self-signed certificate from the RSA key and writes
|
|
// it to certPath. Returns the DER-encoded certificate bytes for the JWT x5c header.
|
|
func generateAndWriteCert(rsaKey *rsa.PrivateKey, certPath string) ([]byte, error) {
|
|
template := x509.Certificate{
|
|
SerialNumber: big.NewInt(1),
|
|
Subject: pkix.Name{
|
|
Organization: []string{"ATCR"},
|
|
CommonName: "ATCR Token Signing Certificate",
|
|
},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour),
|
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
BasicConstraintsValid: true,
|
|
}
|
|
|
|
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &rsaKey.PublicKey, rsaKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create certificate: %w", err)
|
|
}
|
|
|
|
// Write cert to disk for distribution library
|
|
certPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: certDER,
|
|
})
|
|
|
|
dir := filepath.Dir(certPath)
|
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
|
return nil, fmt.Errorf("failed to create cert directory: %w", err)
|
|
}
|
|
if err := os.WriteFile(certPath, certPEM, 0644); err != nil {
|
|
return nil, fmt.Errorf("failed to write certificate: %w", err)
|
|
}
|
|
|
|
slog.Info("Generated JWT signing certificate", "path", certPath)
|
|
return certDER, nil
|
|
}
|