qcc/internal/ca/ca.go
Niko Marmeladkov fc0caf13ef
Initial commit: QuiC Call server
- PoW (Hashcash) authentication
- X.509 CA for phone number certificates (+0 XXX YYY ZZZ)
- QUIC transport (hysteria quic-go fork) with ACME TLS
- Custom append-only DB engine (from NikoGram)
- Call signaling (dial/ring/accept/reject/end)
- E2EE media relay (X25519 + ChaCha20-Poly1305)
- Brutal congestion control (from hysteria)
- Media datagram relay (Opus/VP9/H264/H265)
- Graceful shutdown
2026-06-30 12:38:34 +03:00

150 lines
3.5 KiB
Go

package ca
import (
"crypto"
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"os"
"path/filepath"
"time"
)
type CA struct {
key crypto.Signer
cert *x509.Certificate
}
func NewOrLoad(dataDir string, keyType string, validity time.Duration) (*CA, error) {
keyPath := filepath.Join(dataDir, "ca.key")
certPath := filepath.Join(dataDir, "ca.crt")
caKey, err := loadOrGenKey(keyPath, keyType)
if err != nil {
return nil, fmt.Errorf("ca key: %w", err)
}
caCert, err := loadOrGenCert(certPath, caKey, validity)
if err != nil {
return nil, fmt.Errorf("ca cert: %w", err)
}
return &CA{key: caKey, cert: caCert}, nil
}
func loadOrGenKey(path, keyType string) (crypto.Signer, error) {
if data, err := os.ReadFile(path); err == nil {
block, _ := pem.Decode(data)
if block == nil {
return nil, fmt.Errorf("invalid CA key PEM")
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return key.(crypto.Signer), nil
}
var signer crypto.Signer
switch keyType {
case "ed25519":
_, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
signer = priv
default:
return nil, fmt.Errorf("unsupported key type: %s", keyType)
}
b, err := x509.MarshalPKCS8PrivateKey(signer)
if err != nil {
return nil, err
}
pemData := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: b})
if err := os.WriteFile(path, pemData, 0600); err != nil {
return nil, err
}
return signer, nil
}
func loadOrGenCert(path string, key crypto.Signer, validity time.Duration) (*x509.Certificate, error) {
if data, err := os.ReadFile(path); err == nil {
block, _ := pem.Decode(data)
if block == nil {
return nil, fmt.Errorf("invalid CA cert PEM")
}
return x509.ParseCertificate(block.Bytes)
}
pub := key.Public()
serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
now := time.Now()
template := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
CommonName: "QuiC Call Root CA",
},
NotBefore: now,
NotAfter: now.Add(validity),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLenZero: true,
}
certDER, err := x509.CreateCertificate(rand.Reader, template, template, pub, key)
if err != nil {
return nil, err
}
pemData := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
if err := os.WriteFile(path, pemData, 0644); err != nil {
return nil, err
}
return x509.ParseCertificate(certDER)
}
func (ca *CA) IssueCert(commonName string, pub crypto.PublicKey, validity time.Duration) ([]byte, error) {
serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
now := time.Now()
template := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
CommonName: commonName,
Organization: []string{"QuiC Call"},
},
NotBefore: now,
NotAfter: now.Add(validity),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
},
}
certDER, err := x509.CreateCertificate(rand.Reader, template, ca.cert, pub, ca.key)
if err != nil {
return nil, fmt.Errorf("create cert: %w", err)
}
return certDER, nil
}
func (ca *CA) CAKey() crypto.Signer {
return ca.key
}
func (ca *CA) CACert() *x509.Certificate {
return ca.cert
}
func (ca *CA) CACertPEM() []byte {
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.cert.Raw})
}