mirror of
https://github.com/jetkvm/kvm.git
synced 2026-05-21 05:20:35 +00:00
5806c80e6a
The 4096-bit limit produced ~500-byte serials that violate RFC 5280 §4.1.2.2's 20-octet cap. Apple's DER parser enforces this strictly, so URLSession, NWConnection, AVFoundation and every other client on macOS/iOS/tvOS rejected the cert with "Unknown format in import" before any trust evaluation ran. Lower the limit to 128 bits (matching Go stdlib's generate_cert.go example) and add a one-shot migration that drops any already-baked oversized CA, plus the leaves it issued, on startup so existing devices recover without manual SSH cleanup. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
95 lines
2.2 KiB
Go
95 lines
2.2 KiB
Go
package websecure
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
)
|
|
|
|
// Up to 128 bits, per Go stdlib's generate_cert.go example.
|
|
// RFC 5280 §4.1.2.2 caps serial numbers at 20 octets and Apple's DER
|
|
// parser enforces this; a wider limit produces ~500-byte serials that
|
|
// macOS rejects with "Unknown format in import" before any trust eval.
|
|
var serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128)
|
|
|
|
// maxValidSerialBits is the upper bound (in bits) at which an X.509
|
|
// serial number still fits within RFC 5280's 20-octet cap once
|
|
// ASN.1-encoded. Anything beyond this is rejected by Apple's TLS stack.
|
|
const maxValidSerialBits = 159
|
|
|
|
func withSecretFile(filename string, f func(*os.File) error) error {
|
|
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
return f(file)
|
|
}
|
|
|
|
func keyToFile(cert *tls.Certificate, filename string) error {
|
|
var keyBlock pem.Block
|
|
switch k := cert.PrivateKey.(type) {
|
|
case *rsa.PrivateKey:
|
|
keyBlock = pem.Block{
|
|
Type: "RSA PRIVATE KEY",
|
|
Bytes: x509.MarshalPKCS1PrivateKey(k),
|
|
}
|
|
case *ecdsa.PrivateKey:
|
|
b, e := x509.MarshalECPrivateKey(k)
|
|
if e != nil {
|
|
return fmt.Errorf("failed to marshal EC private key: %v", e)
|
|
}
|
|
keyBlock = pem.Block{
|
|
Type: "EC PRIVATE KEY",
|
|
Bytes: b,
|
|
}
|
|
case ed25519.PrivateKey:
|
|
keyBlock = pem.Block{
|
|
Type: "ED25519 PRIVATE KEY",
|
|
Bytes: k,
|
|
}
|
|
default:
|
|
return fmt.Errorf("unknown private key type: %T", k)
|
|
}
|
|
|
|
err := withSecretFile(filename, func(file *os.File) error {
|
|
return pem.Encode(file, &keyBlock)
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save private key: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func certToFile(cert *tls.Certificate, filename string) error {
|
|
return withSecretFile(filename, func(file *os.File) error {
|
|
for _, c := range cert.Certificate {
|
|
block := pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: c,
|
|
}
|
|
|
|
err := pem.Encode(file, &block)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save certificate: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func generateSerialNumber() (*big.Int, error) {
|
|
return rand.Int(rand.Reader, serialNumberLimit)
|
|
}
|