Skip to content

Commit b32f4b2

Browse files
thokra-navjksolbakkentoby1knby
authored
Certings (#224)
* wip - certz * Various changes Co-authored-by: Torbjørn Hallenberg <[email protected]> * Sync * småflikks * sync Co-authored-by: J-K. Solbakken <[email protected]> Co-authored-by: Torbjørn Hallenberg <[email protected]>
1 parent 71125b7 commit b32f4b2

File tree

2 files changed

+112
-36
lines changed

2 files changed

+112
-36
lines changed

pkg/outtune/outtune.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,17 @@ func download(ctx context.Context, serial string, privateKey *rsa.PrivateKey) (*
6767
return nil, err
6868
}
6969

70+
return downloadWithPublicKey(ctx, serial, publicKey)
71+
}
72+
73+
func downloadWithPublicKey(ctx context.Context, serial string, publicKey []byte) (*response, error) {
7074
block := &pem.Block{
7175
Type: "PUBLIC KEY",
7276
Bytes: publicKey,
7377
}
7478

7579
buf := &bytes.Buffer{}
76-
err = pem.Encode(buf, block)
80+
err := pem.Encode(buf, block)
7781
if err != nil {
7882
return nil, fmt.Errorf("encode public key in PEM format: %w", err)
7983
}

pkg/outtune/outtune_darwin.go

+107-35
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,35 @@ package outtune
33
import (
44
"bufio"
55
"context"
6+
"crypto/rand"
7+
"crypto/rsa"
8+
"crypto/x509"
9+
"encoding/pem"
10+
"errors"
611
"fmt"
12+
"io/fs"
713
"os"
814
"os/exec"
15+
"path/filepath"
916
"regexp"
10-
"strings"
1117

1218
"github.com/nais/device/pkg/pb"
1319
log "github.com/sirupsen/logrus"
1420
)
1521

16-
const aadLoginURL = "https://nav-no.managed.us2.access-control.cas.ms/aad_login"
22+
const (
23+
aadLoginURL = "https://nav-no.managed.us2.access-control.cas.ms/aad_login"
24+
certPath = "Library/Application Support/naisdevice/browser_cert_pubkey.pem"
25+
)
1726

1827
type darwin struct {
1928
helper pb.DeviceHelperClient
2029
}
2130

31+
type certresponse struct {
32+
CertPem string `json:"cert_pem"`
33+
}
34+
2235
func New(helper pb.DeviceHelperClient) Outtune {
2336
return &darwin{
2437
helper: helper,
@@ -32,71 +45,69 @@ func (o *darwin) Cleanup(ctx context.Context) error {
3245
return err
3346
}
3447

35-
// find identities in Mac OS X keychain for this serial
36-
identities, err := identities(ctx, serial.GetSerial())
48+
// find certificates in Mac OS X keychain for this serial
49+
certificates, err := certificates(ctx, serial.GetSerial())
3750
if err != nil {
3851
return err
3952
}
4053

4154
// remove identities
42-
for _, certificateSerial := range identities {
43-
cmd := exec.CommandContext(ctx, "/usr/bin/security", "delete-identity", "-Z", certificateSerial, "-t")
55+
for _, certificateSerial := range certificates {
56+
cmd := exec.CommandContext(ctx, "/usr/bin/security", "delete-certificate", "-Z", certificateSerial)
4457
err = cmd.Run()
4558
if err != nil {
4659
log.Errorf("unable to delete certificate and private key from keychain: %s", err)
4760
} else {
48-
log.Debugf("deleted identity '%s' from keychain", certificateSerial)
61+
log.Debugf("deleted certificate '%s' from keychain", certificateSerial)
4962
}
5063
}
5164

5265
return nil
5366
}
5467

5568
func (o *darwin) Install(ctx context.Context) error {
56-
o.Cleanup(ctx)
57-
5869
serial, err := o.helper.GetSerial(ctx, &pb.GetSerialRequest{})
5970
if err != nil {
6071
return err
6172
}
6273

63-
id, err := generateKeyAndCertificate(ctx, serial.GetSerial())
74+
home, err := os.UserHomeDir()
6475
if err != nil {
65-
return err
76+
return fmt.Errorf("could not determine user home directory: %v", err)
6677
}
6778

68-
w, err := os.CreateTemp(os.TempDir(), "naisdevice-")
69-
if err != nil {
70-
return err
71-
}
72-
defer w.Close()
73-
defer os.Remove(w.Name())
79+
pubKeyPath := filepath.Join(home, certPath)
7480

75-
// Write key+certificate pair to disk in PEM format
76-
err = id.SerializePEM(w)
81+
var pk *rsa.PrivateKey
82+
_, err = os.Stat(pubKeyPath)
7783
if err != nil {
78-
return err
84+
if !errors.Is(err, fs.ErrNotExist) {
85+
return err
86+
}
87+
88+
pk, err = o.generate(ctx, serial.GetSerial(), pubKeyPath)
89+
if err != nil {
90+
return err
91+
}
92+
} else {
93+
o.Cleanup(ctx)
7994
}
8095

81-
// flush contents to disk
82-
err = w.Close()
96+
resp, err := o.download_cert(ctx, serial.GetSerial(), pubKeyPath)
8397
if err != nil {
8498
return err
8599
}
86-
87-
// run Mac OS X keychain import tool
88-
cmd := exec.CommandContext(ctx, "/usr/bin/security", "import", w.Name(), "-A")
89-
err = cmd.Run()
100+
err = o.importIdentity(ctx, pk, resp.CertificatePEM)
90101
if err != nil {
91102
return err
92103
}
93104

94-
currentIdentities, err := identities(ctx, serial.GetSerial())
95-
if err != nil {
105+
currentIdentities, err := certificates(ctx, serial.GetSerial())
106+
if err != nil || len(currentIdentities) == 0 {
96107
return fmt.Errorf("unable to find identity in keychain: %s", err)
97108
}
98109

99-
cmd = exec.CommandContext(ctx, "/usr/bin/security", "set-identity-preference", "-Z", currentIdentities[0], "-s", aadLoginURL)
110+
cmd := exec.CommandContext(ctx, "/usr/bin/security", "set-identity-preference", "-Z", currentIdentities[0], "-s", aadLoginURL)
100111
err = cmd.Run()
101112
if err != nil {
102113
log.Errorf("set-identity-preference: %s", err)
@@ -105,9 +116,70 @@ func (o *darwin) Install(ctx context.Context) error {
105116
return nil
106117
}
107118

108-
func identities(ctx context.Context, serial string) ([]string, error) {
119+
func (o *darwin) download_cert(ctx context.Context, serial, pubKeyPath string) (*response, error) {
120+
publicKey, err := os.ReadFile(pubKeyPath)
121+
if err != nil {
122+
return nil, err
123+
}
124+
125+
return downloadWithPublicKey(ctx, serial, publicKey)
126+
}
127+
128+
func (o *darwin) generate(ctx context.Context, serial, pubKeyPath string) (*rsa.PrivateKey, error) {
129+
privateKey, err := rsa.GenerateKey(rand.Reader, entropyBits)
130+
if err != nil {
131+
return nil, err
132+
}
133+
134+
publicKey, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
135+
if err != nil {
136+
return nil, err
137+
}
138+
139+
return privateKey, os.WriteFile(pubKeyPath, publicKey, 0o644)
140+
}
141+
142+
func (o *darwin) importIdentity(ctx context.Context, privateKey *rsa.PrivateKey, certificate string) error {
143+
w, err := os.CreateTemp(os.TempDir(), "naisdevice-")
144+
if err != nil {
145+
return err
146+
}
147+
defer w.Close()
148+
defer os.Remove(w.Name())
149+
150+
if privateKey != nil {
151+
pem.Encode(w, &pem.Block{
152+
Type: "RSA PRIVATE KEY",
153+
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
154+
})
155+
}
156+
157+
block, rest := pem.Decode([]byte(certificate))
158+
if len(rest) > 0 {
159+
log.Warnf("certificate had remaining input which was ignored")
160+
}
161+
cert, err := x509.ParseCertificate(block.Bytes)
162+
if err != nil {
163+
return err
164+
}
165+
pem.Encode(w, &pem.Block{
166+
Type: "CERTIFICATE",
167+
Bytes: cert.Raw,
168+
})
169+
170+
// flush contents to disk
171+
err = w.Close()
172+
if err != nil {
173+
return err
174+
}
175+
// run Mac OS X keychain import tool
176+
cmd := exec.CommandContext(ctx, "/usr/bin/security", "import", w.Name(), "-A")
177+
return cmd.Run()
178+
}
179+
180+
func certificates(ctx context.Context, serial string) ([]string, error) {
109181
id := "naisdevice - " + serial
110-
cmd := exec.CommandContext(ctx, "/usr/bin/security", "find-identity", "-s", id)
182+
cmd := exec.CommandContext(ctx, "/usr/bin/security", "find-certificate", "-c", id, "-Z")
111183
stdout, err := cmd.StdoutPipe()
112184
if err != nil {
113185
return nil, err
@@ -120,15 +192,15 @@ func identities(ctx context.Context, serial string) ([]string, error) {
120192
}
121193

122194
idMap := make(map[string]struct{})
123-
re := regexp.MustCompile("[A-Za-z0-9]{40}")
195+
re := regexp.MustCompile(`SHA-1 hash:\s([A-Za-z0-9]{40})`)
124196
scan := bufio.NewScanner(stdout)
125197
for scan.Scan() {
126198
line := scan.Text()
127-
certificateID := re.FindString(line)
128-
if len(certificateID) == 0 || !strings.Contains(line, "naisdevice") {
199+
matches := re.FindAllStringSubmatch(line, 1)
200+
if len(matches) == 0 {
129201
continue
130202
}
131-
idMap[certificateID] = struct{}{}
203+
idMap[matches[0][1]] = struct{}{}
132204
}
133205

134206
err = cmd.Wait()

0 commit comments

Comments
 (0)