detached 1c01096

feat(pki): add trust store wrapper and iOS mobileconfig generation

commit
1c010968359878613bacf019f676403ce064091a
author
brice · · 2026-03-29 16:41 UTC
parent
b939cf1
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +111 -0
M go.mod +2 -0
@@ -15,6 +15,7 @@ require (
1515 github.com/mattn/go-runewidth v0.0.21
1616 github.com/muesli/termenv v0.16.0
1717 github.com/rivo/uniseg v0.4.7
18+ github.com/smallstep/truststore v0.13.0
1819 github.com/spf13/cobra v1.10.2
1920 github.com/spf13/viper v1.21.0
2021 github.com/stretchr/testify v1.11.1
@@ -67,6 +68,7 @@ require (
6768 google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
6869 google.golang.org/grpc v1.79.3 // indirect
6970 google.golang.org/protobuf v1.36.11 // indirect
71+ howett.net/plist v1.0.0 // indirect
7072 )
7173
7274 require (
@@ -15,6 +15,7 @@ require (
15 github.com/mattn/go-runewidth v0.0.2115 github.com/mattn/go-runewidth v0.0.21
16 github.com/muesli/termenv v0.16.016 github.com/muesli/termenv v0.16.0
17 github.com/rivo/uniseg v0.4.717 github.com/rivo/uniseg v0.4.7
18+ github.com/smallstep/truststore v0.13.0
18 github.com/spf13/cobra v1.10.219 github.com/spf13/cobra v1.10.2
19 github.com/spf13/viper v1.21.020 github.com/spf13/viper v1.21.0
20 github.com/stretchr/testify v1.11.121 github.com/stretchr/testify v1.11.1
@@ -67,6 +68,7 @@ require (
67 google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect68 google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
68 google.golang.org/grpc v1.79.3 // indirect69 google.golang.org/grpc v1.79.3 // indirect
69 google.golang.org/protobuf v1.36.11 // indirect70 google.golang.org/protobuf v1.36.11 // indirect
71+ howett.net/plist v1.0.0 // indirect
70 )72 )
71 73
72 require (74 require (
M go.sum +6 -0 generated file: collapsed
A internal/adapters/out/pki/mobileconfig.go +58 -0
@@ -0,0 +1,58 @@
1+package pki
2+
3+import (
4+ "encoding/base64"
5+ "fmt"
6+
7+ "github.com/google/uuid"
8+)
9+
10+// GenerateMobileconfig generates an iOS configuration profile containing
11+// the root CA certificate for one-tap trust installation.
12+func GenerateMobileconfig(rootDER []byte, rootCN string) []byte {
13+ b64Cert := base64.StdEncoding.EncodeToString(rootDER)
14+ profileUUID := uuid.New().String()
15+ certUUID := uuid.New().String()
16+
17+ profile := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
18+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
19+<plist version="1.0">
20+<dict>
21+ <key>PayloadContent</key>
22+ <array>
23+ <dict>
24+ <key>PayloadCertificateFileName</key>
25+ <string>gordon-ca.crt</string>
26+ <key>PayloadContent</key>
27+ <data>%s</data>
28+ <key>PayloadDescription</key>
29+ <string>Installs the Gordon internal CA root certificate</string>
30+ <key>PayloadDisplayName</key>
31+ <string>%s</string>
32+ <key>PayloadIdentifier</key>
33+ <string>dev.gordon.ca.%s</string>
34+ <key>PayloadType</key>
35+ <string>com.apple.security.root</string>
36+ <key>PayloadUUID</key>
37+ <string>%s</string>
38+ <key>PayloadVersion</key>
39+ <integer>1</integer>
40+ </dict>
41+ </array>
42+ <key>PayloadDescription</key>
43+ <string>Trust Gordon's internal Certificate Authority for HTTPS access</string>
44+ <key>PayloadDisplayName</key>
45+ <string>Gordon CA Trust</string>
46+ <key>PayloadIdentifier</key>
47+ <string>dev.gordon.ca-profile</string>
48+ <key>PayloadType</key>
49+ <string>Configuration</string>
50+ <key>PayloadUUID</key>
51+ <string>%s</string>
52+ <key>PayloadVersion</key>
53+ <integer>1</integer>
54+</dict>
55+</plist>`, b64Cert, rootCN, certUUID, certUUID, profileUUID)
56+
57+ return []byte(profile)
58+}
@@ -0,0 +1,58 @@
1+package pki
2+
3+import (
4+ "encoding/base64"
5+ "fmt"
6+
7+ "github.com/google/uuid"
8+)
9+
10+// GenerateMobileconfig generates an iOS configuration profile containing
11+// the root CA certificate for one-tap trust installation.
12+func GenerateMobileconfig(rootDER []byte, rootCN string) []byte {
13+ b64Cert := base64.StdEncoding.EncodeToString(rootDER)
14+ profileUUID := uuid.New().String()
15+ certUUID := uuid.New().String()
16+
17+ profile := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
18+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
19+<plist version="1.0">
20+<dict>
21+ <key>PayloadContent</key>
22+ <array>
23+ <dict>
24+ <key>PayloadCertificateFileName</key>
25+ <string>gordon-ca.crt</string>
26+ <key>PayloadContent</key>
27+ <data>%s</data>
28+ <key>PayloadDescription</key>
29+ <string>Installs the Gordon internal CA root certificate</string>
30+ <key>PayloadDisplayName</key>
31+ <string>%s</string>
32+ <key>PayloadIdentifier</key>
33+ <string>dev.gordon.ca.%s</string>
34+ <key>PayloadType</key>
35+ <string>com.apple.security.root</string>
36+ <key>PayloadUUID</key>
37+ <string>%s</string>
38+ <key>PayloadVersion</key>
39+ <integer>1</integer>
40+ </dict>
41+ </array>
42+ <key>PayloadDescription</key>
43+ <string>Trust Gordon's internal Certificate Authority for HTTPS access</string>
44+ <key>PayloadDisplayName</key>
45+ <string>Gordon CA Trust</string>
46+ <key>PayloadIdentifier</key>
47+ <string>dev.gordon.ca-profile</string>
48+ <key>PayloadType</key>
49+ <string>Configuration</string>
50+ <key>PayloadUUID</key>
51+ <string>%s</string>
52+ <key>PayloadVersion</key>
53+ <integer>1</integer>
54+</dict>
55+</plist>`, b64Cert, rootCN, certUUID, certUUID, profileUUID)
56+
57+ return []byte(profile)
58+}
A internal/adapters/out/pki/mobileconfig_test.go +22 -0
@@ -0,0 +1,22 @@
1+package pki_test
2+
3+import (
4+ "strings"
5+ "testing"
6+
7+ "github.com/bnema/gordon/internal/adapters/out/pki"
8+ "github.com/stretchr/testify/assert"
9+ "github.com/stretchr/testify/require"
10+)
11+
12+func TestGenerateMobileconfig(t *testing.T) {
13+ dir := t.TempDir()
14+ ca, err := pki.NewCA(dir, testLogger())
15+ require.NoError(t, err)
16+
17+ mc := pki.GenerateMobileconfig(ca.RootCertificateDER(), ca.RootCommonName())
18+ assert.True(t, strings.HasPrefix(string(mc), "<?xml"))
19+ assert.Contains(t, string(mc), "Gordon Internal CA")
20+ assert.Contains(t, string(mc), "<key>PayloadType</key>")
21+ assert.Contains(t, string(mc), "com.apple.security.root")
22+}
@@ -0,0 +1,22 @@
1+package pki_test
2+
3+import (
4+ "strings"
5+ "testing"
6+
7+ "github.com/bnema/gordon/internal/adapters/out/pki"
8+ "github.com/stretchr/testify/assert"
9+ "github.com/stretchr/testify/require"
10+)
11+
12+func TestGenerateMobileconfig(t *testing.T) {
13+ dir := t.TempDir()
14+ ca, err := pki.NewCA(dir, testLogger())
15+ require.NoError(t, err)
16+
17+ mc := pki.GenerateMobileconfig(ca.RootCertificateDER(), ca.RootCommonName())
18+ assert.True(t, strings.HasPrefix(string(mc), "<?xml"))
19+ assert.Contains(t, string(mc), "Gordon Internal CA")
20+ assert.Contains(t, string(mc), "<key>PayloadType</key>")
21+ assert.Contains(t, string(mc), "com.apple.security.root")
22+}
A internal/adapters/out/pki/trust.go +23 -0
@@ -0,0 +1,23 @@
1+package pki
2+
3+import (
4+ "crypto/x509"
5+
6+ "github.com/smallstep/truststore"
7+)
8+
9+// InstallRoot installs the root CA certificate into the system trust store.
10+func InstallRoot(cert *x509.Certificate) error {
11+ return truststore.Install(cert,
12+ truststore.WithFirefox(),
13+ truststore.WithJava(),
14+ )
15+}
16+
17+// UninstallRoot removes the root CA certificate from the system trust store.
18+func UninstallRoot(cert *x509.Certificate) error {
19+ return truststore.Uninstall(cert,
20+ truststore.WithFirefox(),
21+ truststore.WithJava(),
22+ )
23+}
@@ -0,0 +1,23 @@
1+package pki
2+
3+import (
4+ "crypto/x509"
5+
6+ "github.com/smallstep/truststore"
7+)
8+
9+// InstallRoot installs the root CA certificate into the system trust store.
10+func InstallRoot(cert *x509.Certificate) error {
11+ return truststore.Install(cert,
12+ truststore.WithFirefox(),
13+ truststore.WithJava(),
14+ )
15+}
16+
17+// UninstallRoot removes the root CA certificate from the system trust store.
18+func UninstallRoot(cert *x509.Certificate) error {
19+ return truststore.Uninstall(cert,
20+ truststore.WithFirefox(),
21+ truststore.WithJava(),
22+ )
23+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close