codesign

package module
v1.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 16 Imported by: 0

README

codesign

Remote Authenticode signing for Windows PE/MSI files, backed by a YubiKey PIV token. The private key never leaves the token.

v1 is built for GitHub Actions. A workflow POSTs artifacts through an mTLS proxy to signerd, which verifies a GitHub OIDC token against a fail-closed repository allowlist and signs on the hardware token.

This module provides:

  • A composite GitHub Action (uses: golift/codesign@v1).
  • signerd — HTTP daemon next to the YubiKey (osslsigncode or jsign), on Linux (Docker/systemd) or macOS (launchd).
  • codesign — CLI and Go client library used by the Action.

Remote requests require both gates:

  1. mTLS at the reverse proxy (nginx ssl_verify_client on).
  2. GitHub Actions OIDC verified by signerd (allowed_repositories).

GitHub Action

The job must grant id-token: write or the signing service returns 401.

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write   # REQUIRED: lets the action fetch a GitHub OIDC token.
    steps:
      - uses: actions/checkout@v5
      # ... build your Windows binaries ...
      - uses: golift/codesign@v1
        with:
          files: |
            dist/*.exe
            dist/*.msi
          url: ${{ secrets.CODESIGN_URL }}
          client-cert: ${{ secrets.CODESIGN_CLIENT_CERT }}
          client-key: ${{ secrets.CODESIGN_CLIENT_KEY }}
          name: My Application
          website: https://app.example.com

@v1 tracks a floating tag and silently picks up new Action code. Pin by commit SHA (uses: golift/codesign@<sha>) when you need a frozen install; that is also why release checksums are cosign-signed and the image ships SLSA provenance.

Files are replaced in place. The operator must allowlist your Owner/repo and issue a client certificate that chains to the proxy CA. Server-side docs:

Deploying the daemon

Start with examples/signerd.toml.example:

A v1.0.0 release plants the floating v1 tag the Action tracks.

Documentation

Overview

Package codesign remotely Authenticode-signs Windows binaries with a key that lives on a hardware token (YubiKey PIV). This root package holds the HTTP client library used by the codesign CLI and the GitHub Action. The signing daemon lives in cmd/signerd, and the CLI lives in cmd/codesign.

Index

Constants

View Source
const (
	// SignPath accepts a POST with the unsigned file as the request body and
	// returns the signed file as the response body.
	SignPath = "/v1/sign"
	// HealthPath reports whether the daemon can sign right now. No auth.
	HealthPath = "/health"
	// HeaderFilename carries the original file name so the daemon can pick
	// the right signing format from the extension.
	HeaderFilename = "X-Codesign-Filename"
	// HeaderName carries the Authenticode program name. Optional.
	HeaderName = "X-Codesign-Name"
	// HeaderURL carries the Authenticode program URL. Optional.
	HeaderURL = "X-Codesign-Url"
)

The signerd HTTP protocol, shared by the daemon, the client library, and the CLI. POST the raw PE/MSI bytes to SignPath and read the signed bytes back. Callers authenticate with a GitHub Actions OIDC bearer token. Loopback peers may skip that check, but only when the daemon is explicitly configured to allow it (AllowUnauthenticatedLoopback); by default every caller, loopback or not, must present a token.

View Source
const (
	// DefaultTimeout covers upload, signing, and the timestamp round trip.
	DefaultTimeout = 5 * time.Minute
)

Variables

View Source
var (
	ErrNoURL         = errors.New("no signing service URL configured")
	ErrInsecureURL   = errors.New("signing service URL must be https (or http to loopback)")
	ErrHalfKeyPair   = errors.New("client certificate and key must both be set (or neither)")
	ErrEmptyOIDC     = errors.New("GitHub Actions OIDC response contained no token")
	ErrNoActionsOIDC = errors.New("not running under GitHub Actions with id-token permission " +
		"(ACTIONS_ID_TOKEN_REQUEST_URL/_TOKEN are unset)")
	ErrInvalidHeader = errors.New("request metadata contains characters not valid in an HTTP header")
)

Errors returned by the client.

Functions

func FetchGitHubToken

func FetchGitHubToken(ctx context.Context, audience string) (string, error)

FetchGitHubToken requests an OIDC token from the GitHub Actions runtime with the provided audience. The calling workflow job must set permissions: id-token: write.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client signs files against a remote signerd. Create one with New.

func New

func New(config *Config) (*Client, error)

New validates the configuration, loads mTLS material, and returns a Client. A nil config is treated as empty and returns ErrNoURL.

func (*Client) Health

func (c *Client) Health(ctx context.Context) error

Health checks the daemon's /health endpoint. No auth required.

func (*Client) Sign

func (c *Client) Sign(ctx context.Context, filename string, data []byte, opts *SignOptions) ([]byte, error)

Sign posts file bytes to the signing service and returns the signed bytes. The filename is advisory; the daemon uses its extension to pick the signing format.

func (*Client) SignFile

func (c *Client) SignFile(ctx context.Context, inputPath, outputPath string, opts *SignOptions) error

SignFile signs inputPath and writes the result to outputPath. An empty outputPath replaces the input file in place. The output is written to a temp file first and renamed, so a failure never truncates the target.

type Config

type Config struct {
	// URL is the base URL of signerd or the proxy in front of it, such as
	// https://sign.example.com or http://127.0.0.1:8750 over an SSH tunnel.
	URL string
	// ClientCert and ClientKey enable mTLS toward the proxy. Each may be a
	// file path or inline PEM (anything containing "-----BEGIN").
	ClientCert string
	// ClientKey is the private key matching ClientCert.
	ClientKey string
	// RootCA optionally pins the CA that signed the server certificate.
	// File path or inline PEM.
	RootCA string
	// Token is a GitHub Actions OIDC bearer token. It may be left empty only
	// when the server explicitly allows unauthenticated loopback requests.
	Token string
	// Retries is how many times a failed request is retried (network errors
	// and gateway errors only; auth and validation failures never retry).
	Retries int
	// Timeout per attempt. Non-positive uses DefaultTimeout.
	Timeout time.Duration
}

Config builds a Client.

type SignOptions

type SignOptions struct {
	// Name is the Authenticode program name.
	Name string
	// URL is the Authenticode program URL.
	URL string
}

SignOptions carries per-file Authenticode fields. The zero value uses the daemon's configured defaults.

Directories

Path Synopsis
Package backends implements the signer.Signer interface on top of external signing tools: osslsigncode (PKCS#11 against libykcs11) and jsign (native YubiKey store).
Package backends implements the signer.Signer interface on top of external signing tools: osslsigncode (PKCS#11 against libykcs11) and jsign (native YubiKey store).
cmd
codesign command
codesign is the CLI half of golift.io/codesign.
codesign is the CLI half of golift.io/codesign.
signerd command
signerd is the daemon half of golift.io/codesign.
signerd is the daemon half of golift.io/codesign.
Package oidc verifies GitHub Actions OIDC tokens presented to signerd.
Package oidc verifies GitHub Actions OIDC tokens presented to signerd.
Package server implements the signerd HTTP daemon: GET /health and POST /v1/sign.
Package server implements the signerd HTTP daemon: GET /health and POST /v1/sign.
Package signer defines the interface every signing backend implements, and provides a Fake backend for tests.
Package signer defines the interface every signing backend implements, and provides a Fake backend for tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL