authenticode

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: May 16, 2026 License: MIT Imports: 15 Imported by: 0

README

authenticode

Go Reference CI Coverage Status Go Report Card

Pure-Go Microsoft Authenticode signing for Windows PE (.exe / .dll) files. No CGO, no osslsigncode shell-out, no PKCS#11 engine — just encoding/asn1 and the standard crypto.Signer interface.

What it does

  • Parses PE32 / PE32+ images and computes the Authenticode digest (skipping the file checksum, the attribute-certificate data directory entry, and the existing attribute certificate table, per Microsoft's spec).
  • Builds the SpcIndirectDataContent structure and the CMS SignedData ContentInfo with the four Authenticode-required signed attributes (contentType, messageDigest, SpcSpOpusInfo, SpcStatementType).
  • Optionally requests an RFC 3161 timestamp from any Content-Type: application/timestamp-query TSA and embeds it under id-smime-aa-timeStampToken.
  • Wraps the result in a WIN_CERTIFICATE and writes it into a new attribute certificate table on the PE.

Verified end-to-end with osslsigncode verify (DigiCert-issued code-signing chain, ECDSA P-384 / SHA-384).

Usage

import "github.com/KarpelesLab/authenticode"

// signer is anything implementing authenticode.Signer:
//   crypto.Signer + Certificate() *x509.Certificate + CertificateChain() []*x509.Certificate
// (github.com/KarpelesLab/hsm Key satisfies it directly.)
signed, err := authenticode.Sign(peBytes, signer, authenticode.SignOptions{
    Hash:   crypto.SHA384,
    TSAURL: "http://timestamp.digicert.com",
})

SignWithChain is the lower-level form that accepts a raw crypto.Signer and an explicit chain.

Status

  • ECDSA leaf certs only (P-256, P-384, P-521); RSA leaf support and richer compatibility tests are open follow-ups.

License

MIT — see LICENSE.

Documentation

Overview

Package authenticode implements Microsoft Authenticode PE signing in pure Go. It computes the Authenticode digest, builds the CMS SignedData (with SpcIndirectDataContent), embeds an RFC3161 timestamp, and writes the resulting WIN_CERTIFICATE into the PE's attribute certificate table. It signs through any crypto.Signer (e.g. the github.com/KarpelesLab/hsm IDPrime backend driving a USB token).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildSignedData

func BuildSignedData(spc []byte, signer crypto.Signer, chain []*x509.Certificate, opts SignOptions) ([]byte, error)

BuildSignedData assembles a complete CMS SignedData ContentInfo around the supplied SpcIndirectDataContent. The signing key is driven through the standard crypto.Signer interface — pass an idprime.Signer (or any other implementation) and the resulting SignedData carries an ECDSA signature ready for embedding in a PE.

chain must start with the leaf certificate that matches signer's public key; subsequent entries (intermediates) are embedded as-is.

If opts.TSAURL is set, an RFC 3161 timestamp is fetched and embedded as an unsigned attribute on the SignerInfo.

func BuildSpcIndirectDataContent

func BuildSpcIndirectDataContent(peDigest []byte, h crypto.Hash) ([]byte, error)

BuildSpcIndirectDataContent builds the SpcIndirectDataContent payload that goes into the eContent field of the signed CMS ContentInfo. peDigest is the Authenticode digest of the PE image (the bytes produced by PE.AuthenticodeDigest), and h is the hash algorithm used to compute it.

func RequestTimestamp

func RequestTimestamp(ctx context.Context, tsaURL string, signature []byte, h crypto.Hash) ([]byte, error)

RequestTimestamp asks an RFC 3161 Time-Stamp Authority to timestamp the given signature bytes. It returns the raw TimeStampToken (a CMS ContentInfo) ready for embedding as the value of the SPC_RFC3161_OBJID (1.3.6.1.4.1.311.3.3.1) unsigned attribute on a SignerInfo — the Microsoft-Authenticode-specific carrier that signtool / osslsigncode actually parse.

The TSA URL must accept POSTs with Content-Type application/timestamp-query (the standard).

func Sign

func Sign(pe []byte, signer Signer, opts SignOptions) ([]byte, error)

Sign produces a Microsoft Authenticode signature over the given PE image, embeds it as a WIN_CERTIFICATE in the file, and returns the new bytes. The signing key + cert chain are supplied through Signer. If opts.TSAURL is set, an RFC 3161 timestamp from that authority is fetched and embedded in the SignerInfo's unsigned attributes.

The input slice is not modified.

func SignWithChain

func SignWithChain(pe []byte, signer crypto.Signer, chain []*x509.Certificate, opts SignOptions) ([]byte, error)

SignWithChain is the lower-level entry point: pass a raw crypto.Signer plus the certificate chain explicitly (leaf first). Useful when the chain comes from somewhere other than the Signer.

Types

type PE

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

PE represents a parsed PE32 / PE32+ image and remembers the offsets the Authenticode hash algorithm needs to skip.

func Parse

func Parse(raw []byte) (*PE, error)

Parse parses a PE32 or PE32+ image and locates the offsets used by Authenticode hashing. Does not modify the input slice.

func (*PE) AuthenticodeDigest

func (p *PE) AuthenticodeDigest(h hash.Hash) []byte

AuthenticodeDigest computes the Microsoft Authenticode hash over the PE image, omitting the four-byte file checksum, the eight-byte attribute-certificate-table data-directory entry, and any existing attribute certificate table contents.

All offsets are clamped to [0, len(raw)] before slicing so a malformed PE that survived Parse cannot panic here.

func (*PE) EmbedSignature

func (p *PE) EmbedSignature(cms []byte) []byte

EmbedSignature returns a copy of the PE with the given CMS SignedData blob embedded as a WIN_CERTIFICATE in a new attribute certificate table appended to the file. The data-directory entry is updated accordingly.

Any pre-existing attribute certificate table is replaced.

type SignOptions

type SignOptions struct {
	// Hash selects the digest algorithm used for both the
	// SpcIndirectDataContent's messageDigest and the SignerInfo's
	// signed-attrs hash. Defaults to crypto.SHA256.
	Hash crypto.Hash

	// TSAURL, if non-empty, requests an RFC 3161 timestamp from the
	// given Time-Stamp Authority over HTTP and embeds the resulting
	// token under SPC_RFC3161_OBJID (1.3.6.1.4.1.311.3.3.1) in the
	// SignerInfo's unsigned attributes — the OID Authenticode
	// verifiers (signtool, osslsigncode) look up.
	TSAURL string

	// Context governs the TSA HTTP call. Defaults to
	// context.Background().
	Context context.Context

	// SigningTime overrides the PKCS#9 signingTime attribute. Zero
	// (the default) means time.Now() at signing — which is what
	// signtool / osslsigncode do. Set explicitly only for reproducible
	// builds or tests.
	SigningTime time.Time

	// ProgramName and ProgramURL populate the SpcSpOpusInfo signed
	// attribute (signtool's "/d <name>" / "/du <url>"; osslsigncode's
	// "-n <name>" / "-i <url>"). If both are empty the attribute is
	// omitted entirely, matching osslsigncode's behavior — verifiers
	// surface them as the publisher's display name / "more info" link.
	ProgramName string
	ProgramURL  string
}

SignOptions configures the CMS signing operation.

type Signer

type Signer interface {
	crypto.Signer
	Certificate() *x509.Certificate
	CertificateChain() []*x509.Certificate
}

Signer is a signing key bundled with the certificate (and the rest of the chain) that asserts its public key. hsm.Key from github.com/KarpelesLab/hsm v0.2.5+ satisfies it directly — an IDPrime smart-card key plugs in without conversion.

CertificateChain must return the chain leaf-first; intermediates and (optionally) the root follow. When the slice is empty Sign falls back to Certificate() and signs with a single-element chain.

Jump to

Keyboard shortcuts

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