From b8d6e29b354d5bf5f71a010d925a47feff0c4e98 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Mon, 24 Aug 2026 15:32:26 -0700 Subject: [PATCH 1/3] Stop creating osslsigncode's -out before the tool runs. Debian osslsigncode 2.5 fails with "Failed to create file" when -out already exists. publishSigned reserved the staging name by creating an empty file; unlink it so the tool can write the signed PE. Co-authored-by: Cursor --- backends/backends_test.go | 46 ++++++++++++++++++++++++++++++++++++++- backends/osslsigncode.go | 11 +++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/backends/backends_test.go b/backends/backends_test.go index 4001f13..e9e411e 100644 --- a/backends/backends_test.go +++ b/backends/backends_test.go @@ -28,8 +28,24 @@ func (f *fakeRunner) run(_ context.Context, command string, args ...string) ([]b defer f.mu.Unlock() f.calls = append(f.calls, append([]string{command}, args...)) + if f.err != nil { + return f.output, f.err + } - return f.output, f.err + // osslsigncode writes -out itself. Mimic that so publishSigned can rename. + for i, arg := range args { + if arg == "-out" && i+1 < len(args) { + path := args[i+1] + if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { + err = os.WriteFile(path, []byte("signed"), 0o600) + if err != nil { + return f.output, err + } + } + } + } + + return f.output, nil } func TestOSSLSigncodeSign(t *testing.T) { @@ -83,6 +99,34 @@ func TestOSSLSigncodeSign(t *testing.T) { assert.NotContains(t, got, "123456", "PIN must not appear in argv") } +func TestOSSLSigncodeOutDoesNotExist(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + input := filepath.Join(dir, "in.exe") + output := filepath.Join(dir, "out.exe") + require.NoError(t, os.WriteFile(input, []byte("MZ"), 0o600)) + + var sawOut string + backend := backends.NewOSSLSigncode(&backends.OSSLConfig{ + PKCS11Module: "/usr/lib/libykcs11.so", + CertFile: "/etc/signerd/chain.pem", + Run: func(_ context.Context, _ string, args ...string) ([]byte, error) { + sawOut = args[len(args)-1] + _, err := os.Stat(sawOut) + require.ErrorIs(t, err, os.ErrNotExist, "osslsigncode 2.5 cannot overwrite -out") + require.NoError(t, os.WriteFile(sawOut, []byte("signed"), 0o600)) + + return nil, nil + }, + }) + + err := backend.Sign(t.Context(), &signer.Request{InputPath: input, OutputPath: output}) + require.NoError(t, err) + require.FileExists(t, output) + assert.NotEqual(t, output, sawOut) +} + func TestOSSLSigncodeDefaults(t *testing.T) { t.Parallel() diff --git a/backends/osslsigncode.go b/backends/osslsigncode.go index 34b07ac..39f014e 100644 --- a/backends/osslsigncode.go +++ b/backends/osslsigncode.go @@ -3,6 +3,7 @@ package backends import ( "context" "fmt" + "os" "golift.io/codesign/signer" ) @@ -101,6 +102,14 @@ func (s *OSSLSigncode) Sign(ctx context.Context, req *signer.Request) error { return withPINFile(s.config.PIN, func(pinPath string) error { return publishSigned(req.OutputPath, func(staging string) error { + // osslsigncode 2.5 refuses to overwrite -out ("Failed to create + // file"). publishSigned's CreateTemp reserves a unique name by + // creating an empty file; remove it so the tool can write. + err := os.Remove(staging) + if err != nil { + return fmt.Errorf("osslsigncode sign: clearing staging file: %w", err) + } + args := []string{ "sign", "-pkcs11module", s.config.PKCS11Module, @@ -124,7 +133,7 @@ func (s *OSSLSigncode) Sign(ctx context.Context, req *signer.Request) error { args = append(args, "-in", req.InputPath, "-out", staging) - _, err := s.config.Run(ctx, s.config.Command, args...) + _, err = s.config.Run(ctx, s.config.Command, args...) if err != nil { return fmt.Errorf("osslsigncode sign: %w", err) } From b0c4e6d4741f1fe4aefe9e1bab5f4c6ae29b753e Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Mon, 24 Aug 2026 15:38:49 -0700 Subject: [PATCH 2/3] Satisfy wrapcheck and noinlineerr in the osslsigncode fake runner. Co-authored-by: Cursor --- backends/backends_test.go | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/backends/backends_test.go b/backends/backends_test.go index e9e411e..1831ff3 100644 --- a/backends/backends_test.go +++ b/backends/backends_test.go @@ -3,6 +3,7 @@ package backends_test import ( "context" "errors" + "fmt" "os" "path/filepath" "strings" @@ -35,12 +36,9 @@ func (f *fakeRunner) run(_ context.Context, command string, args ...string) ([]b // osslsigncode writes -out itself. Mimic that so publishSigned can rename. for i, arg := range args { if arg == "-out" && i+1 < len(args) { - path := args[i+1] - if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { - err = os.WriteFile(path, []byte("signed"), 0o600) - if err != nil { - return f.output, err - } + err := writeFakeOut(args[i+1]) + if err != nil { + return f.output, err } } } @@ -48,6 +46,24 @@ func (f *fakeRunner) run(_ context.Context, command string, args ...string) ([]b return f.output, nil } +func writeFakeOut(path string) error { + _, err := os.Stat(path) + if err == nil { + return nil + } + + if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("checking staging -out: %w", err) + } + + err = os.WriteFile(path, []byte("signed"), 0o600) + if err != nil { + return fmt.Errorf("writing staging -out: %w", err) + } + + return nil +} + func TestOSSLSigncodeSign(t *testing.T) { t.Parallel() @@ -105,9 +121,11 @@ func TestOSSLSigncodeOutDoesNotExist(t *testing.T) { dir := t.TempDir() input := filepath.Join(dir, "in.exe") output := filepath.Join(dir, "out.exe") + require.NoError(t, os.WriteFile(input, []byte("MZ"), 0o600)) var sawOut string + backend := backends.NewOSSLSigncode(&backends.OSSLConfig{ PKCS11Module: "/usr/lib/libykcs11.so", CertFile: "/etc/signerd/chain.pem", From 753b5027a25f48c37fb87c7f1d3aac0d886ad34b Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Mon, 24 Aug 2026 15:40:34 -0700 Subject: [PATCH 3/3] Bump the module and signerd image to Go 1.27.0. Co-authored-by: Cursor --- Dockerfile | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index ff663e6..881f95a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ # same token (exclusive PC/SC access; pick one owner). Pass the YubiKey CCID # interface in with --device; see docs/docker-usb.md for finding it safely. -FROM golang:1.25.7@sha256:5a79b94c34c299ac0361fbb7c7fca6dc552e166b42341050323fa3ab137d7be9 AS build +FROM golang:1.27.0@sha256:65b6f280bf050ec5af12716857e8ea8439d694dbba8f31ceeb7630670071f2bb AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download diff --git a/go.mod b/go.mod index cbb58ab..f3a005b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module golift.io/codesign -go 1.25.7 +go 1.27.0 require ( github.com/golang-jwt/jwt/v5 v5.3.1