mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat: Add opt-in support for proxy CA cert installation (#318)
* feat: Add opt-in support for proxy CA cert installation * fix: Code review fixes * fix: Code review fixes * fix: Code review fixes * fix: Code review fixes * fix: Code review fixes * docs: Add limitation for MacOS MDM script
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
// Package truststore installs and removes PMG's MITM CA certificate in the
|
||||
// operating-system trust store. Per-OS behavior lives in build-tagged files
|
||||
// (truststore_darwin.go, truststore_linux.go, truststore_windows.go) mirroring
|
||||
// the sandbox/platform package; this file holds the OS-agnostic surface.
|
||||
package truststore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/safedep/dry/log"
|
||||
)
|
||||
|
||||
// Scope selects which trust store to operate on.
|
||||
type Scope int
|
||||
|
||||
const (
|
||||
// ScopeUser is the per-user trust store (no elevation). Unsupported on Linux.
|
||||
ScopeUser Scope = iota
|
||||
// ScopeSystem is the machine-wide trust store (PMG elevates the write).
|
||||
ScopeSystem
|
||||
)
|
||||
|
||||
func (s Scope) String() string {
|
||||
if s == ScopeSystem {
|
||||
return "system"
|
||||
}
|
||||
return "user"
|
||||
}
|
||||
|
||||
// ErrUserScopeUnsupported is returned by Install/Uninstall when per-user trust
|
||||
// is not a platform concept (Linux). Callers treat it as informational.
|
||||
var ErrUserScopeUnsupported = errors.New("user-scope trust store is not supported on this platform")
|
||||
|
||||
// commandRunner runs an external trust-store tool. Overridable in tests.
|
||||
var commandRunner = func(name string, args ...string) ([]byte, error) {
|
||||
return exec.Command(name, args...).CombinedOutput()
|
||||
}
|
||||
|
||||
var euid = os.Geteuid
|
||||
|
||||
// runElevated prefixes sudo on Unix (unless already root) so only the privileged
|
||||
// system-store write is elevated. Windows has no sudo and relies on an elevated
|
||||
// prompt, so the command runs as-is.
|
||||
func runElevated(name string, args ...string) ([]byte, error) {
|
||||
if runtime.GOOS != "windows" && euid() != 0 {
|
||||
log.Infof("Elevating via sudo to modify the system trust store")
|
||||
return commandRunner("sudo", append([]string{name}, args...)...)
|
||||
}
|
||||
return commandRunner(name, args...)
|
||||
}
|
||||
|
||||
// Install adds certPEM (a PEM-encoded CA certificate) to the OS trust store.
|
||||
func Install(certPEM []byte, scope Scope) error { return installPlatform(certPEM, scope) }
|
||||
|
||||
// Uninstall removes the certificate matched by commonName from the OS trust store.
|
||||
func Uninstall(commonName string, scope Scope) error { return uninstallPlatform(commonName, scope) }
|
||||
|
||||
// Status reports whether the certificate matched by commonName is trusted in the
|
||||
// user and/or system store. It is best-effort; callers may treat errors as not trusted.
|
||||
func Status(commonName string) (user bool, system bool, err error) {
|
||||
return statusPlatform(commonName)
|
||||
}
|
||||
|
||||
// UserScopeSupported reports whether the platform has a per-user trust store
|
||||
// (false on Linux). Consumers use it to interpret "not trusted" correctly.
|
||||
func UserScopeSupported() bool { return userScopeSupportedPlatform() }
|
||||
@@ -0,0 +1,35 @@
|
||||
//go:build darwin || linux || windows
|
||||
// +build darwin linux windows
|
||||
|
||||
package truststore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// writeTempCert writes certPEM to a temp file and returns its path and a cleanup
|
||||
// func. The OS trust tools (macOS security, Windows certutil, Linux install)
|
||||
// take a file path argument, so the cert is staged in a user-owned temp file
|
||||
// before the elevated store write.
|
||||
func writeTempCert(certPEM []byte) (string, func(), error) {
|
||||
noop := func() {}
|
||||
|
||||
f, err := os.CreateTemp("", "pmg-ca-*.pem")
|
||||
if err != nil {
|
||||
return "", noop, fmt.Errorf("failed to create temp cert file: %w", err)
|
||||
}
|
||||
|
||||
if _, err := f.Write(certPEM); err != nil {
|
||||
_ = f.Close()
|
||||
_ = os.Remove(f.Name())
|
||||
return "", noop, fmt.Errorf("failed to write temp cert file: %w", err)
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
_ = os.Remove(f.Name())
|
||||
return "", noop, fmt.Errorf("failed to close temp cert file: %w", err)
|
||||
}
|
||||
|
||||
return f.Name(), func() { _ = os.Remove(f.Name()) }, nil
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package truststore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/safedep/dry/log"
|
||||
)
|
||||
|
||||
const systemKeychainPath = "/Library/Keychains/System.keychain"
|
||||
|
||||
func userScopeSupportedPlatform() bool { return true }
|
||||
|
||||
func loginKeychainPath() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to resolve home dir: %w", err)
|
||||
}
|
||||
return filepath.Join(home, "Library", "Keychains", "login.keychain-db"), nil
|
||||
}
|
||||
|
||||
func installPlatform(certPEM []byte, scope Scope) error {
|
||||
path, cleanup, err := writeTempCert(certPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
var args []string
|
||||
runner := commandRunner
|
||||
if scope == ScopeSystem {
|
||||
args = []string{"add-trusted-cert", "-d", "-r", "trustRoot", "-k", systemKeychainPath, path}
|
||||
runner = runElevated
|
||||
} else {
|
||||
kc, err := loginKeychainPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args = []string{"add-trusted-cert", "-r", "trustRoot", "-k", kc, path}
|
||||
}
|
||||
|
||||
if out, err := runner("security", args...); err != nil {
|
||||
return fmt.Errorf("security add-trusted-cert failed: %w: %s", err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func uninstallPlatform(commonName string, scope Scope) error {
|
||||
// security delete-certificate removes a single match; loop until none remain
|
||||
// so a --force rotation that left two same-CN certs is fully cleaned.
|
||||
for i := 0; i < 16; i++ {
|
||||
args := []string{"delete-certificate", "-c", commonName}
|
||||
runner := commandRunner
|
||||
if scope == ScopeSystem {
|
||||
// -t clears trust settings; the keychain is a positional argument for
|
||||
// delete-certificate (it has no -k flag, unlike add-trusted-cert).
|
||||
args = append(args, "-t", systemKeychainPath)
|
||||
runner = runElevated
|
||||
}
|
||||
|
||||
out, err := runner("security", args...)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// security reports no remaining match with "Unable to delete certificate
|
||||
// matching ..." (older/other paths use "Could not find"). Either marks the
|
||||
// terminal "nothing left to delete" case, so the loop ends successfully.
|
||||
msg := strings.TrimSpace(string(out))
|
||||
if strings.Contains(msg, "Unable to delete certificate matching") || strings.Contains(msg, "Could not find") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("security delete-certificate failed: %w: %s", err, msg)
|
||||
}
|
||||
|
||||
log.Warnf("security delete-certificate did not converge after 16 iterations; trust store cleanup may be incomplete")
|
||||
return nil
|
||||
}
|
||||
|
||||
func statusPlatform(commonName string) (bool, bool, error) {
|
||||
lkc, err := loginKeychainPath()
|
||||
if err != nil {
|
||||
return false, certInKeychain(commonName, systemKeychainPath), nil
|
||||
}
|
||||
return certInKeychain(commonName, lkc), certInKeychain(commonName, systemKeychainPath), nil
|
||||
}
|
||||
|
||||
func certInKeychain(commonName, keychain string) bool {
|
||||
args := []string{"find-certificate", "-c", commonName}
|
||||
if keychain != "" {
|
||||
args = append(args, keychain)
|
||||
}
|
||||
_, err := commandRunner("security", args...)
|
||||
return err == nil
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package truststore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDarwinInstallUserArgs(t *testing.T) {
|
||||
var gotName string
|
||||
var gotArgs []string
|
||||
orig := commandRunner
|
||||
commandRunner = func(name string, args ...string) ([]byte, error) {
|
||||
gotName, gotArgs = name, args
|
||||
return nil, nil
|
||||
}
|
||||
t.Cleanup(func() { commandRunner = orig })
|
||||
|
||||
require.NoError(t, Install([]byte("PEM"), ScopeUser))
|
||||
assert.Equal(t, "security", gotName)
|
||||
assert.Equal(t, "add-trusted-cert", gotArgs[0])
|
||||
assert.NotContains(t, gotArgs, "-d") // user scope: no -d
|
||||
assert.Contains(t, gotArgs, "trustRoot")
|
||||
}
|
||||
|
||||
func TestDarwinInstallSystemArgs(t *testing.T) {
|
||||
var gotArgs []string
|
||||
orig := commandRunner
|
||||
commandRunner = func(_ string, args ...string) ([]byte, error) {
|
||||
gotArgs = args
|
||||
return nil, nil
|
||||
}
|
||||
t.Cleanup(func() { commandRunner = orig })
|
||||
|
||||
require.NoError(t, Install([]byte("PEM"), ScopeSystem))
|
||||
assert.Contains(t, gotArgs, "-d") // system scope: -d
|
||||
assert.Contains(t, gotArgs, systemKeychainPath)
|
||||
}
|
||||
|
||||
func TestDarwinUninstallStopsWhenNotFound(t *testing.T) {
|
||||
calls := 0
|
||||
orig := commandRunner
|
||||
commandRunner = func(_ string, _ ...string) ([]byte, error) {
|
||||
calls++
|
||||
return []byte(`Unable to delete certificate matching "Test CA"`), assert.AnError
|
||||
}
|
||||
t.Cleanup(func() { commandRunner = orig })
|
||||
|
||||
require.NoError(t, Uninstall("Test CA", ScopeUser))
|
||||
assert.Equal(t, 1, calls)
|
||||
}
|
||||
|
||||
func TestDarwinUninstallStopsAfterDeletingMatches(t *testing.T) {
|
||||
calls := 0
|
||||
orig := commandRunner
|
||||
commandRunner = func(_ string, _ ...string) ([]byte, error) {
|
||||
calls++
|
||||
if calls == 1 {
|
||||
return nil, nil // first match deleted
|
||||
}
|
||||
return []byte(`Unable to delete certificate matching "Test CA"`), assert.AnError
|
||||
}
|
||||
t.Cleanup(func() { commandRunner = orig })
|
||||
|
||||
require.NoError(t, Uninstall("Test CA", ScopeUser))
|
||||
assert.Equal(t, 2, calls) // delete one, then terminal not-found
|
||||
}
|
||||
|
||||
func TestDarwinUninstallSystemTargetsSystemKeychain(t *testing.T) {
|
||||
var gotArgs []string
|
||||
orig := commandRunner
|
||||
commandRunner = func(_ string, args ...string) ([]byte, error) {
|
||||
gotArgs = args
|
||||
return []byte("Could not find"), assert.AnError
|
||||
}
|
||||
t.Cleanup(func() { commandRunner = orig })
|
||||
|
||||
require.NoError(t, Uninstall("Test CA", ScopeSystem))
|
||||
assert.Contains(t, gotArgs, systemKeychainPath)
|
||||
assert.Contains(t, gotArgs, "-t")
|
||||
}
|
||||
|
||||
func TestDarwinUserScopeSupported(t *testing.T) {
|
||||
assert.True(t, UserScopeSupported())
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package truststore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func userScopeSupportedPlatform() bool { return false }
|
||||
|
||||
type linuxTrustTool struct {
|
||||
anchorDir string
|
||||
updateCmd string
|
||||
anchorName string
|
||||
}
|
||||
|
||||
// lookPath is overridable in tests.
|
||||
var lookPath = exec.LookPath
|
||||
|
||||
// detectTrustTool is overridable in tests so anchorDir points to a temp dir.
|
||||
var detectTrustTool = detectLinuxTrustTool
|
||||
|
||||
func detectLinuxTrustTool() (linuxTrustTool, error) {
|
||||
if _, err := lookPath("update-ca-certificates"); err == nil {
|
||||
return linuxTrustTool{
|
||||
anchorDir: "/usr/local/share/ca-certificates",
|
||||
updateCmd: "update-ca-certificates",
|
||||
anchorName: "pmg-proxy-ca.crt",
|
||||
}, nil
|
||||
}
|
||||
if _, err := lookPath("update-ca-trust"); err == nil {
|
||||
return linuxTrustTool{
|
||||
anchorDir: "/etc/pki/ca-trust/source/anchors",
|
||||
updateCmd: "update-ca-trust",
|
||||
anchorName: "pmg-proxy-ca.crt",
|
||||
}, nil
|
||||
}
|
||||
return linuxTrustTool{}, fmt.Errorf("no supported trust tool (update-ca-certificates / update-ca-trust) found")
|
||||
}
|
||||
|
||||
func installPlatform(certPEM []byte, scope Scope) error {
|
||||
if scope == ScopeUser {
|
||||
return ErrUserScopeUnsupported
|
||||
}
|
||||
|
||||
tool, err := detectTrustTool()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest := filepath.Join(tool.anchorDir, tool.anchorName)
|
||||
|
||||
tmp, cleanup, err := writeTempCert(certPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
// install(1) sets an explicit 0644 mode and creates the anchor dir if missing.
|
||||
if out, err := runElevated("install", "-m", "0644", "-D", tmp, dest); err != nil {
|
||||
return fmt.Errorf("failed to install CA anchor to %s: %w: %s", dest, err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
|
||||
if out, err := runElevated(tool.updateCmd); err != nil {
|
||||
return fmt.Errorf("%s failed: %w: %s", tool.updateCmd, err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func uninstallPlatform(_ string, scope Scope) error {
|
||||
if scope == ScopeUser {
|
||||
return ErrUserScopeUnsupported
|
||||
}
|
||||
|
||||
tool, err := detectTrustTool()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest := filepath.Join(tool.anchorDir, tool.anchorName)
|
||||
if _, err := os.Stat(dest); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to stat CA anchor %s: %w", dest, err)
|
||||
}
|
||||
|
||||
if out, err := runElevated("rm", "-f", dest); err != nil {
|
||||
return fmt.Errorf("failed to remove CA anchor %s: %w: %s", dest, err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
|
||||
if out, err := runElevated(tool.updateCmd); err != nil {
|
||||
return fmt.Errorf("%s failed: %w: %s", tool.updateCmd, err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func statusPlatform(_ string) (bool, bool, error) {
|
||||
tool, err := detectTrustTool()
|
||||
if err != nil {
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
dest := filepath.Join(tool.anchorDir, tool.anchorName)
|
||||
if _, err := os.Stat(dest); err == nil {
|
||||
return false, true, nil // user scope is never trusted on Linux
|
||||
}
|
||||
return false, false, nil
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package truststore
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLinuxUserScopeUnsupported(t *testing.T) {
|
||||
assert.False(t, UserScopeSupported())
|
||||
assert.ErrorIs(t, Install([]byte("PEM"), ScopeUser), ErrUserScopeUnsupported)
|
||||
assert.ErrorIs(t, Uninstall("Test CA", ScopeUser), ErrUserScopeUnsupported)
|
||||
}
|
||||
|
||||
func TestLinuxSystemInstallStagesAnchorAndUpdates(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
dest := filepath.Join(dir, "pmg-proxy-ca.crt")
|
||||
|
||||
origDetect := detectTrustTool
|
||||
detectTrustTool = func() (linuxTrustTool, error) {
|
||||
return linuxTrustTool{anchorDir: dir, updateCmd: "update-ca-certificates", anchorName: "pmg-proxy-ca.crt"}, nil
|
||||
}
|
||||
origEUID := euid
|
||||
euid = func() int { return 0 } // run as root so no sudo prefix is added
|
||||
var calls [][]string
|
||||
origRunner := commandRunner
|
||||
commandRunner = func(name string, args ...string) ([]byte, error) {
|
||||
calls = append(calls, append([]string{name}, args...))
|
||||
return nil, nil
|
||||
}
|
||||
t.Cleanup(func() { detectTrustTool = origDetect; euid = origEUID; commandRunner = origRunner })
|
||||
|
||||
require.NoError(t, Install([]byte("PEM-BYTES"), ScopeSystem))
|
||||
|
||||
require.Len(t, calls, 2)
|
||||
assert.Equal(t, "install", calls[0][0])
|
||||
assert.Contains(t, calls[0], dest)
|
||||
assert.Equal(t, "update-ca-certificates", calls[1][0])
|
||||
}
|
||||
|
||||
func TestLinuxSystemInstallElevatesWhenNotRoot(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
origDetect := detectTrustTool
|
||||
detectTrustTool = func() (linuxTrustTool, error) {
|
||||
return linuxTrustTool{anchorDir: dir, updateCmd: "update-ca-certificates", anchorName: "pmg-proxy-ca.crt"}, nil
|
||||
}
|
||||
origEUID := euid
|
||||
euid = func() int { return 1000 }
|
||||
var firstName string
|
||||
origRunner := commandRunner
|
||||
commandRunner = func(name string, _ ...string) ([]byte, error) {
|
||||
if firstName == "" {
|
||||
firstName = name
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
t.Cleanup(func() { detectTrustTool = origDetect; euid = origEUID; commandRunner = origRunner })
|
||||
|
||||
require.NoError(t, Install([]byte("PEM"), ScopeSystem))
|
||||
assert.Equal(t, "sudo", firstName)
|
||||
}
|
||||
|
||||
func TestLinuxStatusReflectsAnchorFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
origDetect := detectTrustTool
|
||||
detectTrustTool = func() (linuxTrustTool, error) {
|
||||
return linuxTrustTool{anchorDir: dir, updateCmd: "update-ca-certificates", anchorName: "pmg-proxy-ca.crt"}, nil
|
||||
}
|
||||
t.Cleanup(func() { detectTrustTool = origDetect })
|
||||
|
||||
_, system, err := Status("Test CA")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, system)
|
||||
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "pmg-proxy-ca.crt"), []byte("x"), 0o644))
|
||||
_, system, err = Status("Test CA")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, system)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//go:build !darwin && !linux && !windows
|
||||
// +build !darwin,!linux,!windows
|
||||
|
||||
package truststore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/safedep/dry/usefulerror"
|
||||
"github.com/safedep/pmg/errcodes"
|
||||
)
|
||||
|
||||
func installPlatform(_ []byte, _ Scope) error { return unsupportedPlatformError() }
|
||||
func uninstallPlatform(_ string, _ Scope) error { return unsupportedPlatformError() }
|
||||
func statusPlatform(_ string) (bool, bool, error) { return false, false, unsupportedPlatformError() }
|
||||
func userScopeSupportedPlatform() bool { return false }
|
||||
|
||||
func unsupportedPlatformError() error {
|
||||
return usefulerror.NewUsefulError().
|
||||
WithCode(errcodes.UnsupportedPlatform).
|
||||
WithHumanError("trust store operations are not supported on this platform").
|
||||
WithHelp("Use PMG's default env-var trust injection, or install the CA manually").
|
||||
Wrap(errors.New("unsupported platform"))
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package truststore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func userScopeSupportedPlatform() bool { return true }
|
||||
|
||||
func installPlatform(certPEM []byte, scope Scope) error {
|
||||
path, cleanup, err := writeTempCert(certPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
args := []string{}
|
||||
if scope == ScopeUser {
|
||||
args = append(args, "-user")
|
||||
}
|
||||
args = append(args, "-addstore", "Root", path)
|
||||
|
||||
if out, err := commandRunner("certutil", args...); err != nil {
|
||||
return fmt.Errorf("certutil -addstore failed: %w: %s", err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func uninstallPlatform(commonName string, scope Scope) error {
|
||||
args := []string{}
|
||||
if scope == ScopeUser {
|
||||
args = append(args, "-user")
|
||||
}
|
||||
args = append(args, "-delstore", "Root", commonName)
|
||||
|
||||
if out, err := commandRunner("certutil", args...); err != nil {
|
||||
msg := strings.TrimSpace(string(out))
|
||||
lower := strings.ToLower(msg)
|
||||
if strings.Contains(lower, "cannot find") || strings.Contains(msg, "0x80092004") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("certutil -delstore failed: %w: %s", err, msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func statusPlatform(commonName string) (bool, bool, error) {
|
||||
return certInWindowsStore(commonName, true), certInWindowsStore(commonName, false), nil
|
||||
}
|
||||
|
||||
func certInWindowsStore(commonName string, userScope bool) bool {
|
||||
args := []string{}
|
||||
if userScope {
|
||||
args = append(args, "-user")
|
||||
}
|
||||
args = append(args, "-store", "Root", commonName)
|
||||
|
||||
_, err := commandRunner("certutil", args...)
|
||||
return err == nil
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package truststore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWindowsInstallUserAddsUserFlag(t *testing.T) {
|
||||
var gotName string
|
||||
var gotArgs []string
|
||||
orig := commandRunner
|
||||
commandRunner = func(name string, args ...string) ([]byte, error) {
|
||||
gotName, gotArgs = name, args
|
||||
return nil, nil
|
||||
}
|
||||
t.Cleanup(func() { commandRunner = orig })
|
||||
|
||||
require.NoError(t, Install([]byte("PEM"), ScopeUser))
|
||||
assert.Equal(t, "certutil", gotName)
|
||||
assert.Equal(t, "-user", gotArgs[0])
|
||||
assert.Contains(t, gotArgs, "-addstore")
|
||||
assert.Contains(t, gotArgs, "Root")
|
||||
}
|
||||
|
||||
func TestWindowsInstallSystemHasNoUserFlag(t *testing.T) {
|
||||
var gotArgs []string
|
||||
orig := commandRunner
|
||||
commandRunner = func(_ string, args ...string) ([]byte, error) {
|
||||
gotArgs = args
|
||||
return nil, nil
|
||||
}
|
||||
t.Cleanup(func() { commandRunner = orig })
|
||||
|
||||
require.NoError(t, Install([]byte("PEM"), ScopeSystem))
|
||||
assert.NotContains(t, gotArgs, "-user")
|
||||
assert.Equal(t, "-addstore", gotArgs[0])
|
||||
}
|
||||
|
||||
func TestWindowsUninstallNotFoundIsOK(t *testing.T) {
|
||||
orig := commandRunner
|
||||
commandRunner = func(_ string, _ ...string) ([]byte, error) {
|
||||
return []byte("Cannot find the requested object"), assert.AnError
|
||||
}
|
||||
t.Cleanup(func() { commandRunner = orig })
|
||||
require.NoError(t, Uninstall("Test CA", ScopeUser))
|
||||
}
|
||||
Reference in New Issue
Block a user