Skip to content

Commit ea8a175

Browse files
committed
v0.0.4: add homebrew openssl store
- skip installed certificates - add homebrew openssl store
1 parent 8673793 commit ea8a175

10 files changed

Lines changed: 489 additions & 10 deletions

File tree

trust/trust.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ func (c *Command) run(ctx context.Context, tty termenv.File) error {
109109
SysFS: rootFS,
110110
}
111111

112+
brewStore := &truststore.Brew{
113+
RootDir: "/",
114+
115+
DataFS: rootFS,
116+
SysFS: rootFS,
117+
}
118+
112119
for _, cert := range *certs.Items {
113120
blk, _ := pem.Decode([]byte(cert.TextualEncoding))
114121

@@ -150,17 +157,48 @@ func (c *Command) run(ctx context.Context, tty termenv.File) error {
150157
continue
151158
}
152159

153-
if installed, err := systemStore.InstallCA(ca); installed {
154-
fmt.Fprintf(tty, " - installed in the system store.\n")
155-
} else if err != nil {
160+
if err := install(tty, ca, systemStore, "system"); err != nil {
161+
return err
162+
}
163+
if err := install(tty, ca, nssStore, "Network Security Services (NSS)"); err != nil {
156164
return err
157165
}
158-
if installed, err := nssStore.InstallCA(ca); installed {
159-
fmt.Fprintf(tty, " - installed in the Network Security Services (NSS) store.\n")
160-
} else if err != nil {
166+
if err := install(tty, ca, brewStore, "Homebrew OpenSSL (ca-certificates)"); err != nil {
161167
return err
162168
}
163169
}
164170

165171
return nil
166172
}
173+
174+
type trustStore interface {
175+
Check() (bool, error)
176+
CheckCA(*truststore.CA) (bool, error)
177+
InstallCA(*truststore.CA) (bool, error)
178+
}
179+
180+
func install(tty termenv.File, ca *truststore.CA, store trustStore, name string) error {
181+
if ok, err := store.Check(); !ok {
182+
if err != nil {
183+
fmt.Fprintf(tty, " - skipping the %s store: %s\n", name, err)
184+
} else {
185+
fmt.Fprintf(tty, " - skipping the %s store\n", name)
186+
}
187+
return nil
188+
}
189+
190+
if ok, err := store.CheckCA(ca); err != nil {
191+
fmt.Fprintf(tty, " - skipping the %s store: %s\n", name, err)
192+
return nil
193+
} else if ok {
194+
fmt.Fprintf(tty, " - already installed in the %s store.\n", name)
195+
return nil
196+
}
197+
198+
if installed, err := store.InstallCA(ca); err != nil {
199+
return err
200+
} else if installed {
201+
fmt.Fprintf(tty, " - installed in the %s store.\n", name)
202+
}
203+
return nil
204+
}

truststore/brew.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package truststore
2+
3+
import (
4+
"bytes"
5+
"encoding/pem"
6+
"path/filepath"
7+
"strings"
8+
)
9+
10+
const certPath = "etc/ca-certificates/cert.pem"
11+
12+
type Brew struct {
13+
RootDir string
14+
15+
DataFS DataFS
16+
SysFS CmdFS
17+
18+
brewPath string
19+
prefixPath string
20+
21+
certPath string
22+
}
23+
24+
func (s *Brew) Check() (bool, error) {
25+
if s.brewPath == "" {
26+
var err error
27+
if s.brewPath, err = s.SysFS.LookPath("brew"); err != nil {
28+
return false, err
29+
}
30+
}
31+
32+
if s.prefixPath == "" {
33+
path, err := s.SysFS.Exec(s.SysFS.Command(s.brewPath, "--prefix"))
34+
if err != nil {
35+
return false, err
36+
}
37+
s.prefixPath = strings.TrimPrefix(strings.TrimSpace(string(path)), s.RootDir)
38+
}
39+
40+
if s.certPath == "" {
41+
path := filepath.Join(s.prefixPath, certPath)
42+
if _, err := s.SysFS.Stat(path); err != nil {
43+
return false, err
44+
}
45+
s.certPath = path
46+
}
47+
48+
return true, nil
49+
}
50+
51+
func (s *Brew) CheckCA(ca *CA) (bool, error) {
52+
if ok, err := s.Check(); !ok {
53+
return ok, err
54+
}
55+
56+
buf, err := s.DataFS.ReadFile(s.certPath)
57+
if err != nil {
58+
return false, err
59+
}
60+
61+
var blk *pem.Block
62+
for len(buf) > 0 {
63+
if blk, buf = pem.Decode(buf); blk == nil {
64+
return false, nil
65+
}
66+
67+
if bytes.Equal(ca.Raw, blk.Bytes) {
68+
return true, nil
69+
}
70+
}
71+
return false, nil
72+
}
73+
74+
func (s *Brew) InstallCA(ca *CA) (bool, error) {
75+
if ok, err := s.Check(); !ok {
76+
return ok, err
77+
}
78+
79+
blk := &pem.Block{
80+
Type: "CERTIFICATE",
81+
Bytes: ca.Raw,
82+
}
83+
84+
if err := s.DataFS.AppendToFile(s.certPath, pem.EncodeToMemory(blk)); err != nil {
85+
return false, err
86+
}
87+
return true, nil
88+
}

truststore/brew_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package truststore
2+
3+
import (
4+
"crypto/x509"
5+
"crypto/x509/pkix"
6+
"flag"
7+
"testing"
8+
9+
"github.com/anchordotdev/cli/truststore/internal/must"
10+
)
11+
12+
var (
13+
_ = flag.Bool("prism-verbose", false, "ignored")
14+
_ = flag.Bool("prism-proxy", false, "ignored")
15+
)
16+
17+
func TestBrewCheck(t *testing.T) {
18+
t.Skip("pending mock filesystem")
19+
20+
brew := &Brew{
21+
RootDir: "/",
22+
DataFS: RootFS(),
23+
SysFS: RootFS(),
24+
}
25+
26+
ok, err := brew.Check()
27+
if err != nil {
28+
t.Fatal(err)
29+
}
30+
if want, got := true, ok; want != got {
31+
t.Errorf("want check %t, got %t", want, got)
32+
}
33+
34+
if ok, err = brew.CheckCA(ca); err != nil {
35+
t.Fatal(err)
36+
}
37+
if want, got := false, ok; want != got {
38+
t.Errorf("want check ca %t, got %t", want, got)
39+
}
40+
41+
if ok, err = brew.InstallCA(ca); err != nil {
42+
t.Fatal(err)
43+
}
44+
if want, got := true, ok; want != got {
45+
t.Errorf("want install ca %t, got %t", want, got)
46+
}
47+
48+
if ok, err = brew.CheckCA(ca); err != nil {
49+
t.Fatal(err)
50+
}
51+
if want, got := true, ok; want != got {
52+
t.Errorf("want check ca %t, got %t", want, got)
53+
}
54+
}
55+
56+
var (
57+
ca = mustCA(must.CA(&x509.Certificate{
58+
Subject: pkix.Name{
59+
CommonName: "Example CA",
60+
Organization: []string{"Example, Inc"},
61+
},
62+
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
63+
64+
ExtraExtensions: []pkix.Extension{},
65+
}))
66+
)
67+
68+
func mustCA(cert *must.Certificate) *CA {
69+
uniqueName := cert.Leaf.SerialNumber.Text(16)
70+
71+
return &CA{
72+
Certificate: cert.Leaf,
73+
FilePath: "example-ca-" + uniqueName + ".pem",
74+
UniqueName: uniqueName,
75+
}
76+
}

truststore/fs.go

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package truststore
22

33
import (
4+
"errors"
45
"io/fs"
6+
"io/ioutil"
57
"os"
68
"os/exec"
79
"os/user"
10+
"path/filepath"
11+
"reflect"
12+
"strings"
813
"sync"
914
)
1015

@@ -17,15 +22,30 @@ type CmdFS interface {
1722
LookPath(cmd string) (string, error)
1823
}
1924

20-
func RootFS() CmdFS {
25+
type DataFS interface {
26+
fs.StatFS
27+
fs.ReadFileFS
28+
29+
AppendToFile(name string, p []byte) error
30+
}
31+
32+
type FS interface {
33+
CmdFS
34+
DataFS
35+
}
36+
37+
func RootFS() FS {
2138
return &rootFS{
22-
StatFS: os.DirFS("/").(fs.StatFS),
39+
StatFS: os.DirFS("/").(fs.StatFS),
40+
rootPath: "/",
2341
}
2442
}
2543

2644
type rootFS struct {
2745
fs.StatFS
2846

47+
rootPath string
48+
2949
sudoWarningOnce sync.Once
3050
}
3151

@@ -68,3 +88,54 @@ func (r *rootFS) SudoExec(cmd *exec.Cmd) (out []byte, err error) {
6888
func (r *rootFS) LookPath(cmd string) (string, error) {
6989
return exec.LookPath(cmd)
7090
}
91+
92+
func (r *rootFS) AppendToFile(name string, p []byte) error {
93+
if err := r.checkFile(name); err != nil {
94+
return err
95+
}
96+
97+
f, err := os.OpenFile(filepath.Join(r.rootPath, name), os.O_APPEND|os.O_WRONLY, 0644)
98+
if err != nil {
99+
return err
100+
}
101+
defer f.Close()
102+
103+
if _, err := f.Write(p); err != nil {
104+
return err
105+
}
106+
return f.Close()
107+
}
108+
109+
func (r *rootFS) ReadFile(name string) ([]byte, error) {
110+
if err := r.checkFile(name); err != nil {
111+
return nil, err
112+
}
113+
114+
f, err := os.OpenFile(filepath.Join(r.rootPath, name), os.O_RDONLY, 0444)
115+
if err != nil {
116+
return nil, err
117+
}
118+
defer f.Close()
119+
120+
return ioutil.ReadAll(f)
121+
}
122+
123+
func (r *rootFS) checkFile(name string) error {
124+
infoFS, err := r.StatFS.Stat(strings.Trim(name, string(os.PathSeparator)))
125+
if err != nil {
126+
return err
127+
}
128+
129+
infoOS, err := os.Stat(filepath.Join(r.rootPath, name))
130+
if err != nil {
131+
return err
132+
}
133+
134+
if infoFS.Name() == infoOS.Name() && infoFS.Size() == infoOS.Size() &&
135+
infoFS.Mode() == infoOS.Mode() && infoFS.ModTime().Equal(infoOS.ModTime()) &&
136+
reflect.DeepEqual(infoFS.Sys(), infoOS.Sys()) {
137+
return nil
138+
}
139+
140+
return errors.New("file system setup is misconfigured or corrupt")
141+
}

0 commit comments

Comments
 (0)