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}