Add temporary keychain lifecycle for codesigning
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0d12173ff9
commit
eefce13a7c
60
internal/codesign/keychain.go
Normal file
60
internal/codesign/keychain.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package codesign
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/leonmika/wails-release/internal/runner"
|
||||
)
|
||||
|
||||
// Keychain identifies a temporary keychain we created.
|
||||
type Keychain struct {
|
||||
Path string
|
||||
Password string
|
||||
}
|
||||
|
||||
// CreateKeychain creates a new keychain at path with the given password
|
||||
// and unlocks it. The keychain's auto-lock timeout is set to 6 hours so
|
||||
// it does not relock during a long notarization.
|
||||
func CreateKeychain(ctx context.Context, r runner.Runner, path, password string) (*Keychain, error) {
|
||||
steps := [][]string{
|
||||
{"create-keychain", "-p", password, path},
|
||||
{"set-keychain-settings", "-lut", "21600", path},
|
||||
{"unlock-keychain", "-p", password, path},
|
||||
}
|
||||
for _, args := range steps {
|
||||
if _, err := r.Run(ctx, runner.Spec{Name: "security", Args: args}); err != nil {
|
||||
return nil, fmt.Errorf("security %s: %w", args[0], err)
|
||||
}
|
||||
}
|
||||
return &Keychain{Path: path, Password: password}, nil
|
||||
}
|
||||
|
||||
// ImportP12 imports the .p12 at certPath into kc using certPassword and
|
||||
// authorises codesign to use the resulting key without prompting.
|
||||
func ImportP12(ctx context.Context, r runner.Runner, kc Keychain, certPath, certPassword string) error {
|
||||
if _, err := r.Run(ctx, runner.Spec{
|
||||
Name: "security",
|
||||
Args: []string{"import", certPath, "-k", kc.Path, "-P", certPassword, "-T", "/usr/bin/codesign"},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("security import: %w", err)
|
||||
}
|
||||
if _, err := r.Run(ctx, runner.Spec{
|
||||
Name: "security",
|
||||
Args: []string{"set-key-partition-list", "-S", "apple-tool:,apple:,codesign:", "-s", "-k", kc.Password, kc.Path},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("security set-key-partition-list: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteKeychain removes the keychain. Safe to call from cleanup.
|
||||
func DeleteKeychain(ctx context.Context, r runner.Runner, kc Keychain) error {
|
||||
if _, err := r.Run(ctx, runner.Spec{
|
||||
Name: "security",
|
||||
Args: []string{"delete-keychain", kc.Path},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("security delete-keychain: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
73
internal/codesign/keychain_test.go
Normal file
73
internal/codesign/keychain_test.go
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package codesign_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/leonmika/wails-release/internal/codesign"
|
||||
"github.com/leonmika/wails-release/internal/runner"
|
||||
)
|
||||
|
||||
func TestCreateKeychain_BuildsCorrectArgs(t *testing.T) {
|
||||
f := &runner.Fake{}
|
||||
f.On("security", nil).Return(nil, nil)
|
||||
|
||||
kc, err := codesign.CreateKeychain(context.Background(), f, "/tmp/foo.keychain", "pw")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected: %v", err)
|
||||
}
|
||||
if kc.Path != "/tmp/foo.keychain" {
|
||||
t.Fatalf("kc.Path got %q want /tmp/foo.keychain", kc.Path)
|
||||
}
|
||||
want := [][]string{
|
||||
{"create-keychain", "-p", "pw", "/tmp/foo.keychain"},
|
||||
{"set-keychain-settings", "-lut", "21600", "/tmp/foo.keychain"},
|
||||
{"unlock-keychain", "-p", "pw", "/tmp/foo.keychain"},
|
||||
}
|
||||
for i, w := range want {
|
||||
if !reflect.DeepEqual(f.Calls[i].Args, w) {
|
||||
t.Fatalf("call %d: got %v want %v", i, f.Calls[i].Args, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportP12_BuildsCorrectArgs(t *testing.T) {
|
||||
f := &runner.Fake{}
|
||||
f.On("security", nil).Return(nil, nil)
|
||||
|
||||
err := codesign.ImportP12(context.Background(), f, codesign.Keychain{Path: "/tmp/k", Password: "pw"}, "/tmp/c.p12", "certpw")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected: %v", err)
|
||||
}
|
||||
want := []string{
|
||||
"import", "/tmp/c.p12", "-k", "/tmp/k", "-P", "certpw",
|
||||
"-T", "/usr/bin/codesign",
|
||||
}
|
||||
if !reflect.DeepEqual(f.Calls[0].Args, want) {
|
||||
t.Fatalf("args got %v want %v", f.Calls[0].Args, want)
|
||||
}
|
||||
|
||||
// Partition list set so codesign can use the imported key non-interactively.
|
||||
wantPartition := []string{
|
||||
"set-key-partition-list", "-S", "apple-tool:,apple:,codesign:",
|
||||
"-s", "-k", "pw", "/tmp/k",
|
||||
}
|
||||
if !reflect.DeepEqual(f.Calls[1].Args, wantPartition) {
|
||||
t.Fatalf("partition args got %v want %v", f.Calls[1].Args, wantPartition)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteKeychain_BuildsCorrectArgs(t *testing.T) {
|
||||
f := &runner.Fake{}
|
||||
f.On("security", nil).Return(nil, nil)
|
||||
|
||||
err := codesign.DeleteKeychain(context.Background(), f, codesign.Keychain{Path: "/tmp/k"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected: %v", err)
|
||||
}
|
||||
want := []string{"delete-keychain", "/tmp/k"}
|
||||
if !reflect.DeepEqual(f.Calls[0].Args, want) {
|
||||
t.Fatalf("args got %v want %v", f.Calls[0].Args, want)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue