Skip to content

Commit 7227c56

Browse files
committed
feat: add cert pool
1 parent 342d2a9 commit 7227c56

File tree

2 files changed

+172
-0
lines changed

2 files changed

+172
-0
lines changed

pool.go

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package appstore
2+
3+
import (
4+
"crypto/x509"
5+
"embed"
6+
"io"
7+
"net/http"
8+
"net/url"
9+
"os"
10+
"path"
11+
"path/filepath"
12+
"regexp"
13+
"strings"
14+
"sync"
15+
)
16+
17+
//go:embed certs/*.cer
18+
var certs embed.FS
19+
20+
const srcUrl = "https://www.apple.com/certificateauthority/"
21+
const outDir = "certs/"
22+
23+
var certLinkPattern = regexp.MustCompile(`<a [^>]*href="([^"]+\.cer)"`)
24+
25+
type CertPool struct {
26+
pool *x509.CertPool
27+
poolOnce sync.Once
28+
}
29+
30+
func NewCertPool() (*CertPool, error) {
31+
cp := &CertPool{}
32+
err := cp.Init()
33+
if err != nil {
34+
return nil, err
35+
}
36+
return cp, nil
37+
}
38+
39+
func (cp *CertPool) Init() error {
40+
var err error
41+
cp.poolOnce.Do(func() {
42+
cp.pool = x509.NewCertPool()
43+
err = cp.downloadCerts()
44+
err = cp.loadCerts()
45+
})
46+
return err
47+
}
48+
49+
func (cp *CertPool) downloadCerts() error {
50+
resp, err := http.Get(srcUrl)
51+
if err != nil {
52+
return err
53+
}
54+
defer resp.Body.Close()
55+
56+
content, err := io.ReadAll(resp.Body)
57+
if err != nil {
58+
return err
59+
}
60+
61+
if err := os.RemoveAll(outDir); err != nil {
62+
return err
63+
}
64+
if err := os.MkdirAll(outDir, 0755); err != nil {
65+
return err
66+
}
67+
68+
matches := certLinkPattern.FindAllSubmatch(content, -1)
69+
for _, match := range matches {
70+
certUrl, err := cp.constructCertUrl(string(match[1]))
71+
if err != nil {
72+
return err
73+
}
74+
75+
if err := cp.downloadAndSaveCert(certUrl); err != nil {
76+
return err
77+
}
78+
}
79+
return nil
80+
}
81+
82+
func (cp *CertPool) constructCertUrl(certPath string) (string, error) {
83+
if certPath[0] == '/' {
84+
baseUrl, err := url.Parse(srcUrl)
85+
if err != nil {
86+
return "", err
87+
}
88+
baseUrl.Path = certPath
89+
return baseUrl.String(), nil
90+
} else if strings.HasPrefix(certPath, "https://www.apple.com/") || strings.HasPrefix(certPath, "https://developer.apple.com/") {
91+
return certPath, nil
92+
} else {
93+
return url.JoinPath(srcUrl, certPath)
94+
}
95+
}
96+
97+
func (cp *CertPool) downloadAndSaveCert(certUrl string) error {
98+
resp, err := http.Get(certUrl)
99+
if err != nil {
100+
return err
101+
}
102+
defer resp.Body.Close()
103+
104+
if resp.StatusCode != http.StatusOK {
105+
return nil
106+
}
107+
108+
fileName := path.Base(certUrl)
109+
filePath := filepath.Join(outDir, fileName)
110+
f, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY, 0644)
111+
if err != nil {
112+
return err
113+
}
114+
defer f.Close()
115+
116+
_, err = io.Copy(f, resp.Body)
117+
return err
118+
}
119+
120+
func (cp *CertPool) loadCerts() error {
121+
entries, err := certs.ReadDir("certs")
122+
if err != nil {
123+
return err
124+
}
125+
for _, entry := range entries {
126+
if !entry.IsDir() && entry.Type().IsRegular() {
127+
cert, err := certs.ReadFile("certs/" + entry.Name())
128+
if err != nil {
129+
continue
130+
}
131+
if ok := cp.pool.AppendCertsFromPEM(cert); ok {
132+
continue
133+
}
134+
if cer, err := x509.ParseCertificate(cert); err == nil {
135+
cp.pool.AddCert(cer)
136+
}
137+
}
138+
}
139+
return nil
140+
}
141+
142+
func (cp *CertPool) GetCertPool() *x509.CertPool {
143+
return cp.pool
144+
}

pool_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package appstore
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
)
7+
8+
func TestNewCertPool(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
want *CertPool
12+
wantErr bool
13+
}{
14+
{"test", &CertPool{}, false},
15+
}
16+
for _, tt := range tests {
17+
t.Run(tt.name, func(t *testing.T) {
18+
got, err := NewCertPool()
19+
if (err != nil) != tt.wantErr {
20+
t.Errorf("NewCertPool() error = %v, wantErr %v", err, tt.wantErr)
21+
return
22+
}
23+
if !reflect.DeepEqual(got, tt.want) {
24+
t.Errorf("NewCertPool() got = %v, want %v", got, tt.want)
25+
}
26+
})
27+
}
28+
}

0 commit comments

Comments
 (0)