package securechat import ( "crypto/ed25519" "crypto/subtle" "fmt" "os" "path/filepath" "strings" "golang.org/x/crypto/curve25519" ) // LoadCompanyKeys lee private.key / public.key en el formato de `securechat keygen` // (base64url sin relleno de 32 bytes + "\n") y comprueba que la pública corresponde a la privada. // Para una llave rotada: LoadCompanyKeys(dir, "private.v1.key", "public.v1.key"). func LoadCompanyKeys(dir, privFile, pubFile string) (KeyPair, error) { priv, err := readKeyFile(filepath.Join(dir, privFile), 32) if err != nil { return KeyPair{}, err } pub, err := readKeyFile(filepath.Join(dir, pubFile), 32) if err != nil { return KeyPair{}, err } derived, err := curve25519.X25519(priv, curve25519.Basepoint) if err != nil || subtle.ConstantTimeCompare(derived, pub) != 1 { return KeyPair{}, fail(ErrKeyMismatch, "%s no corresponde a %s", pubFile, privFile) } return KeyPair{PublicKey: pub, PrivateKey: priv}, nil } // LoadSigningKey lee signing.key (Ed25519 de 64 bytes = semilla ‖ pública, formato libsodium, // igual que ed25519.PrivateKey de Go) y comprueba signing.pub. func LoadSigningKey(dir string) (ed25519.PrivateKey, error) { priv, err := readKeyFile(filepath.Join(dir, "signing.key"), 64) if err != nil { return nil, err } pub, err := readKeyFile(filepath.Join(dir, "signing.pub"), 32) if err != nil { return nil, err } sk := ed25519.PrivateKey(priv) if subtle.ConstantTimeCompare(sk.Public().(ed25519.PublicKey), pub) != 1 { return nil, fail(ErrKeyMismatch, "signing.pub no corresponde a signing.key") } return sk, nil } // LoadAllCompanyKeys: private.key/public.key primero y luego private.vN.key/public.vN.key. func LoadAllCompanyKeys(dir string) ([]KeyPair, error) { cur, err := LoadCompanyKeys(dir, "private.key", "public.key") if err != nil { return nil, err } out := []KeyPair{cur} old, _ := filepath.Glob(filepath.Join(dir, "private.v*.key")) for _, f := range old { v := strings.TrimSuffix(strings.TrimPrefix(filepath.Base(f), "private."), ".key") // v1 kp, err := LoadCompanyKeys(dir, "private."+v+".key", "public."+v+".key") if err != nil { return nil, err } out = append(out, kp) } return out, nil } func readKeyFile(path string, n int) ([]byte, error) { raw, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("no se pudo leer %s: %w", path, err) } return decodeLen(strings.TrimSpace(string(raw)), n, filepath.Base(path)) }