diff --git a/internal/codesign/keychain.go b/internal/codesign/keychain.go new file mode 100644 index 0000000..765aed3 --- /dev/null +++ b/internal/codesign/keychain.go @@ -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 +} diff --git a/internal/codesign/keychain_test.go b/internal/codesign/keychain_test.go new file mode 100644 index 0000000..1541049 --- /dev/null +++ b/internal/codesign/keychain_test.go @@ -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) + } +}