-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add package-kit support for authenticode signing through signtool.exe. This should be seen as an initial pass at getting general APIs and structure right. Tools may change here. Reworks the options. Signing keys now have platform specific configuration Makefile updated to include launcher code push snippets. These are Kolide specific, and part of how we update notary. Makefile updated to use osslsigncode to sign windows binaries
- Loading branch information
1 parent
e415bd1
commit 9a39edb
Showing
13 changed files
with
322 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package authenticode | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"os/exec" | ||
"strings" | ||
|
||
"github.com/go-kit/kit/log/level" | ||
"github.com/kolide/launcher/pkg/contexts/ctxlog" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// signtoolOptions are the options for how we call signtool.exe. These | ||
// are *not* the tool options, but instead our own representation of | ||
// the arguments.w | ||
type signtoolOptions struct { | ||
extraArgs []string | ||
subjectName string // If present, use this as the `/n` argument | ||
skipValidation bool | ||
signtoolPath string | ||
timestampServer string | ||
rfc3161Server string | ||
|
||
execCC func(context.Context, string, ...string) *exec.Cmd // Allows test overrides | ||
|
||
} | ||
|
||
type SigntoolOpt func(*signtoolOptions) | ||
|
||
func SkipValidation() SigntoolOpt { | ||
return func(so *signtoolOptions) { | ||
so.skipValidation = true | ||
} | ||
} | ||
|
||
// WithExtraArgs set additional arguments for signtool. Common ones | ||
// may be {`\n`, "subject name"} | ||
func WithExtraArgs(args []string) SigntoolOpt { | ||
return func(so *signtoolOptions) { | ||
so.extraArgs = args | ||
} | ||
} | ||
|
||
func WithSigntoolPath(path string) SigntoolOpt { | ||
return func(so *signtoolOptions) { | ||
so.signtoolPath = path | ||
} | ||
} | ||
|
||
func (so *signtoolOptions) execOut(ctx context.Context, argv0 string, args ...string) (string, string, error) { | ||
logger := ctxlog.FromContext(ctx) | ||
|
||
cmd := so.execCC(ctx, argv0, args...) | ||
|
||
level.Debug(logger).Log( | ||
"msg", "execing", | ||
"cmd", strings.Join(cmd.Args, " "), | ||
) | ||
|
||
stdout, stderr := new(bytes.Buffer), new(bytes.Buffer) | ||
cmd.Stdout, cmd.Stderr = stdout, stderr | ||
if err := cmd.Run(); err != nil { | ||
return strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), errors.Wrapf(err, "run command %s %v, stderr=%s", argv0, args, stderr) | ||
} | ||
return strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// +build !windows | ||
|
||
package authenticode | ||
|
||
import "context" | ||
|
||
func Sign(ctx context.Context, file string, opts ...SigntoolOpt) error { | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package authenticode | ||
|
||
import ( | ||
"context" | ||
"io/ioutil" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"runtime" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
const ( | ||
srcExe = `C:\Windows\System32\netmsg.dll` | ||
signtoolPath = `C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64\signtool.exe` | ||
) | ||
|
||
func TestSign(t *testing.T) { | ||
t.Parallel() | ||
|
||
if runtime.GOOS != "windows" { | ||
t.Skip("not windows") | ||
} | ||
|
||
// create a signtoolOptions object so we can call the exec method | ||
so := &signtoolOptions{ | ||
execCC: exec.CommandContext, | ||
} | ||
|
||
ctx, ctxCancel := context.WithTimeout(context.Background(), 120*time.Second) | ||
defer ctxCancel() | ||
|
||
tmpDir, err := ioutil.TempDir("", "packagekit-authenticode-signing") | ||
defer os.RemoveAll(tmpDir) | ||
require.NoError(t, err) | ||
|
||
testExe := filepath.Join(tmpDir, "test.exe") | ||
|
||
// copy our test file | ||
data, err := ioutil.ReadFile(srcExe) | ||
require.NoError(t, err) | ||
err = ioutil.WriteFile(testExe, data, 0755) | ||
require.NoError(t, err) | ||
|
||
// confirm that we _don't_ have a sig on this file | ||
_, verifyInitial, err := so.execOut(ctx, signtoolPath, "verify", "/pa", testExe) | ||
require.Error(t, err, "no initial signature") | ||
require.Contains(t, verifyInitial, "No signature found", "no initial signature") | ||
|
||
// Sign it! | ||
err = Sign(ctx, testExe, WithSigntoolPath(signtoolPath)) | ||
require.NoError(t, err) | ||
|
||
// verify, as an explicit test. Gotta check both indexes manually. | ||
verifyOut0, _, err := so.execOut(ctx, signtoolPath, "verify", "/pa", "/ds", "0", testExe) | ||
require.NoError(t, err, "verify signature position 0") | ||
require.Contains(t, verifyOut0, "sha1", "contains algorithm verify output") | ||
require.Contains(t, verifyOut0, "Authenticode", "contains timestamp verify output") | ||
|
||
verifyOut1, _, err := so.execOut(ctx, signtoolPath, "verify", "/pa", "/ds", "1", testExe) | ||
require.NoError(t, err, "verify signature position 1") | ||
require.Contains(t, verifyOut1, "sha256", "contains algorithm verify output") | ||
require.Contains(t, verifyOut1, "RFC3161", "contains timestamp verify output") | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// +build windows | ||
|
||
// Authenticode is a light wrapper around signing code under windows. | ||
// | ||
// See | ||
// | ||
// https://docs.microsoft.com/en-us/dotnet/framework/tools/signtool-exe | ||
|
||
package authenticode | ||
|
||
import ( | ||
"context" | ||
"os/exec" | ||
"strings" | ||
|
||
"github.com/go-kit/kit/log" | ||
"github.com/go-kit/kit/log/level" | ||
"github.com/kolide/launcher/pkg/contexts/ctxlog" | ||
"github.com/pkg/errors" | ||
"go.opencensus.io/trace" | ||
) | ||
|
||
// Sign uses signtool to add authenticode signatures. It supports | ||
// optional arguments to allow cert specification | ||
func Sign(ctx context.Context, file string, opts ...SigntoolOpt) error { | ||
ctx, span := trace.StartSpan(ctx, "authenticode.Sign") | ||
defer span.End() | ||
|
||
logger := log.With(ctxlog.FromContext(ctx), "caller", "authenticode.Sign") | ||
|
||
level.Debug(logger).Log( | ||
"msg", "signing file", | ||
"file", file, | ||
) | ||
|
||
so := &signtoolOptions{ | ||
signtoolPath: "signtool.exe", | ||
timestampServer: "http://timestamp.verisign.com/scripts/timstamp.dll", | ||
rfc3161Server: "http://sha256timestamp.ws.symantec.com/sha256/timestamp", | ||
execCC: exec.CommandContext, | ||
} | ||
|
||
for _, opt := range opts { | ||
opt(so) | ||
} | ||
|
||
// signtool.exe can be called multiple times to apply multiple | ||
// signatures. _But_ it uses different arguments for the subsequent | ||
// signatures. So, multiple calls. | ||
// | ||
// _However_ it's not clear this is supported for MSIs, which maybe | ||
// only have a single slot for signing. | ||
// | ||
// References: | ||
// https://knowledge.digicert.com/generalinformation/INFO2274.html | ||
if strings.HasSuffix(file, ".msi") { | ||
if err := so.signtoolSign(ctx, file, "/ph", "/fd", "sha256", "/td", "sha256", "/tr", so.rfc3161Server); err != nil { | ||
return errors.Wrap(err, "signing msi with sha256") | ||
} | ||
} else { | ||
if err := so.signtoolSign(ctx, file, "/ph", "/fd", "sha1", "/t", so.timestampServer); err != nil { | ||
return errors.Wrap(err, "signing file with sha1") | ||
} | ||
|
||
if err := so.signtoolSign(ctx, file, "/as", "/ph", "/fd", "sha256", "/td", "sha256", "/tr", so.rfc3161Server); err != nil { | ||
return errors.Wrap(err, "signing file with sha256") | ||
} | ||
} | ||
|
||
if so.skipValidation { | ||
return nil | ||
} | ||
|
||
_, _, err := so.execOut(ctx, so.signtoolPath, "verify", "/pa", "/v", file) | ||
if err != nil { | ||
return errors.Wrap(err, "verify") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// signtoolSign appends some arguments and execs | ||
func (so *signtoolOptions) signtoolSign(ctx context.Context, file string, args ...string) error { | ||
ctx, span := trace.StartSpan(ctx, "signtoolSign") | ||
defer span.End() | ||
|
||
args = append([]string{"sign"}, args...) | ||
|
||
if so.extraArgs != nil { | ||
args = append(args, so.extraArgs...) | ||
} | ||
|
||
args = append(args, file) | ||
|
||
if _, _, err := so.execOut(ctx, so.signtoolPath, args...); err != nil { | ||
return errors.Wrap(err, "calling signtool") | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.