Raw
 1package ldap
 2
 3import (
 4	"crypto/tls"
 5	"fmt"
 6	"io"
 7	"io/fs"
 8	"os"
 9	"syscall"
10
11	"tug/internal/platform/lockfile"
12)
13
14const (
15	maxKeyBytes  = 64 << 10
16	maxCertBytes = 1 << 20
17)
18
19var keyOpened = func() {}
20
21func loadKeyPair(certPath, keyPath string) (tls.Certificate, error) {
22	keyPEM, err := readKey(keyPath)
23	if err != nil {
24		return tls.Certificate{}, err
25	}
26	certPEM, err := readCert(certPath)
27	if err != nil {
28		return tls.Certificate{}, err
29	}
30	pair, err := tls.X509KeyPair(certPEM, keyPEM)
31	if err != nil {
32		return tls.Certificate{}, fmt.Errorf("ldap: cannot load the LDAPS certificate %s and key %s", certPath, keyPath)
33	}
34	return pair, nil
35}
36
37func readCert(path string) ([]byte, error) {
38	f, info, err := openRegular(path, "certificate")
39	if err != nil {
40		return nil, err
41	}
42	defer f.Close()
43	if info.Size() > maxCertBytes {
44		return nil, tooLarge("certificate", path, info.Mode(), maxCertBytes)
45	}
46	cert, err := io.ReadAll(io.LimitReader(f, maxCertBytes+1))
47	if err != nil {
48		return nil, fmt.Errorf("ldap: cannot read the LDAPS certificate %s: %w", path, err)
49	}
50	if len(cert) > maxCertBytes {
51		return nil, tooLarge("certificate", path, info.Mode(), maxCertBytes)
52	}
53	return cert, nil
54}
55
56func readKey(path string) ([]byte, error) {
57	f, info, err := openRegular(path, "key")
58	if err != nil {
59		return nil, err
60	}
61	defer f.Close()
62	mode := info.Mode()
63	switch {
64	case mode.Perm()&0o077 != 0:
65		return nil, fmt.Errorf("ldap: the LDAPS key %s has mode %04o; it must grant no permission to group or others (0600 or 0400)", path, mode.Perm())
66	case info.Size() > maxKeyBytes:
67		return nil, tooLarge("key", path, mode, maxKeyBytes)
68	}
69	keyOpened()
70	key, err := io.ReadAll(io.LimitReader(f, maxKeyBytes+1))
71	if err != nil {
72		return nil, fmt.Errorf("ldap: cannot read the LDAPS key %s: %w", path, err)
73	}
74	if len(key) > maxKeyBytes {
75		return nil, tooLarge("key", path, mode, maxKeyBytes)
76	}
77	return key, nil
78}
79
80func openRegular(path, what string) (*os.File, fs.FileInfo, error) {
81	f, err := os.OpenFile(path, os.O_RDONLY|syscall.O_NONBLOCK, 0)
82	if err != nil {
83		return nil, nil, fmt.Errorf("ldap: cannot open the LDAPS %s: %w", what, err)
84	}
85	info, err := f.Stat()
86	if err != nil {
87		f.Close()
88		return nil, nil, fmt.Errorf("ldap: cannot inspect the LDAPS %s: %w", what, err)
89	}
90	if mode := info.Mode(); !mode.IsRegular() {
91		f.Close()
92		return nil, nil, fmt.Errorf("ldap: the LDAPS %s %s is a %s (mode %04o), not a regular file", what, path, lockfile.DescribeType(mode), mode.Perm())
93	}
94	return f, info, nil
95}
96
97func tooLarge(what, path string, mode fs.FileMode, limit int) error {
98	return fmt.Errorf("ldap: the LDAPS %s %s (mode %04o) is over the %d-byte limit", what, path, mode.Perm(), limit)
99}