sunlight

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Jan 12, 2026 License: ISC Imports: 30 Imported by: 3

README

The Sunlight logo, a bench under a tree in stylized black ink, cast against a large yellow sun, with the text Sunlight underneath

Sunlight is a production-ready Static Certificate Transparency log implementation designed for scalability, ease of operation, and reduced cost.

Additional resources, including test logs, a formal specification of the monitoring API, and a comprehensive design document which explores the motivating tradeoffs are available at sunlight.dev.

Sunlight's development was sponsored by Let's Encrypt.

Operating a Sunlight log

go install filippo.io/sunlight/cmd/...@latest
sunlight -c sunlight.yaml

All configuration is provided in a YAML file.

The full docs for the configuration file are on the Config and LogConfig types, but this README presents all the options you need in common settings.

You can also browse the live configuration of the production Tuscolo instance, which includes systemd units for all services.

Global configuration

listen:
  - ":443"
acme:
  cache: /var/db/sunlight/autocert/

Sunlight listens on all the listed addresses (passed to net.Listen), and automatically obtains TLS certificates for the logs hostnames via ACME.

checkpoints: /tank/shared/checkpoints.db

Checkpoints is the path to an SQLite file that acts as the global lock backend, storing the latest checkpoint for each log, with compare-and-swap semantics. This database will always be very small.

This database must be global and it must never be changed or modified as an extra safety measure: it ensures that logs won't encounter a fatal split even due to accidental operational mistakes such as running two Sunlight instances against the same configuration.

The database must already exist, to prevent misconfigurations. Create it with

sqlite3 checkpoints.db "CREATE TABLE checkpoints (logID BLOB PRIMARY KEY, body BLOB NOT NULL) STRICT"

Sunlight can alternatively use DynamoDB or S3-compatible object storage with ETag and If-Match support (such as Tigris) as global lock backends.

Per-log configuration

Sunlight instances are multi-tenant: a single process hosts multiple logs, usually different time shards of the same log series.

logs:
  - shortname: example2025h2
    notafterstart: 2025-07-01T00:00:00Z
    notafterlimit: 2026-01-01T00:00:00Z

Certificate Transparency logs are append-only, so to prevent them from growing unboundedly they are temporally sharded by certificate expiration time (the NotAfter X.509 field). You should generally run six month shards, i.e. one log for the first half of 2026, one for the second half, one for the first half of 2027, and so on. You should set up shards for the current year and the following two (e.g. 2025h1–2027h2 if setting up in 2025).

    inception: 2025-04-25

The inception date is the only date on which Sunlight will create the log if it doesn’t exist yet, again to prevent misconfigurations.

    submissionprefix: https://sunlight.example.org/example2025h2
    monitoringprefix: https://static.example.org/example2025h2

The submission prefix is the path at which Sunlight will serve this log.

The monitoring prefix is where the read path (see below) is available. Sunlight expects the files uploaded to the storage backend to become available at this path, but it doesn’t serve them itself!

    secret: /tank/enc/example2025h2.seed.bin

Secret is the path to a file containing a secret seed from which the log's private keys are derived. This is the most important secret of the log!

To generate a new secret, run

sunlight-keygen -f example2025h2.seed.bin
    period: 200
    poolsize: 750

The period is how often pending certificates are pooled and written to the storage backend, in milliseconds. The pool size is how many certificates can fit in the pool before new submissions are rejected until the next write.

A shorter period reduces latency, but causes more frequent writes. You should not set period any higher than 1000.

The pool size effectively acts as a rate limit: Sunlight will accept at most poolsize / period submissions.

    cache: /tank/logs/example2025h2/cache.db

Cache is the path to an SQLite database that keeps track of submitted certificates to avoid duplicate entries. This part of Sunlight can tolerate data loss: it's ok to rollback a few entries on a regular basis, or even lose the cache in an emergency. The only consequence is that existing entries might be resubmitted, growing the size of the log. If the actual log data is hosted on object storage (see below) and the secret is backed up, a log can recover from the complete loss of the Sunlight server.

This generally doesn’t grow beyond 100 GB.

    localdirectory: /tank/logs/example2025h2/data

or

    s3region: atlantis-1
    s3bucket: example2025h2
    s3endpoint: https://data.example/s3/

There are two options for storing the actual Static CT assets: a regular POSIX filesystem (to which Sunlight issues fsync syscalls to ensure durability), or an S3-compatible object storage (which can be eventually consistent but must guarantee durability after a successful PUT). Remember that this storage backend must never lose writes or be rolled back.

Each six-months shard will reach approximately 1.5 TB at current rates. It starts growing sharply 90 days before its notafterstart date, and slows down 90 days before its notafterlimit date. It can be deleted a couple months after its notafterlimit date.

You should consider a separate ZFS dataset or object storage bucket for each log, to make it easier to delete it once the log is retired. See, for example, the Tuscolo ZFS configuration. However, note that using separate AWS S3 buckets can cause dynamic scaling issues when traffic moves naturally from one to another, so you should use a single bucket and delete logs with lifecycle rules if hosting on AWS S3.

Hosting the read path

The Static CT monitoring API can be implemented by simply serving the files uploaded to or stored in the storage backend by Sunlight.

If you’re using the S3 backend, that probably means simply placing a CDN in front of it, or making the bucket public. The monitoring prefix of the log can be completely different from the submission prefix, so you can point the read path directly at your static file serving infrastructure.

If you’re using the local filesystem backend, you could use any HTTP server, like nginx or Caddy. However, Sunlight provides a specialized HTTP file server with a number of Static CT friendly features.

  • Its configuration is nearly a subset of Sunlight’s.
  • It automatically rate-limits clients that don’t provide a contact through the User-Agent, to make it easier to report client issues and to reduce the impact of non-malicious misbehaving clients.
  • It provides the same monitoring and logging capabilities as Sunlight (see below), including the same debug endpoints and copious public metrics.
  • It exposes a /health endpoint which only returns 200 OK if all logs have produced validly-signed checkpoints in the last five seconds. This is what powers the Tuscolo status page.

If using a different HTTP server, you should take care of setting the right Content-Type, Content-Encoding, Cache-Control, and ideally Access-Control-Allow-Origin headers. Feel free to inquire on the #sunlight channel of the transparency.dev Slack for help configuring other Static CT read path servers.

You should expect more load on the read path than on the write path, as each certificate is generally only submitted once (or twice counting pre-certificates) but is fetched by many monitors. Be mindful of traffic charges if you run in the cloud! All assets except /checkpoint are immutable, so they are highly cacheable.

Monitoring and logging

JSON structured logs are produced on standard output, and human-readable logs are produced to standard error.

Numerous Prometheus metrics are exposed publicly at /metrics. (All hostnames serve all metrics, not just the ones of that log, if different logs have different hostnames.)

A private HTTP server listens on a random port of localhost, exposing the net/http/pprof endpoints, as well as the following.

GET /debug/heavyhitter/useragents
GET /debug/heavyhitter/ips

The 100 most common client IP addresses and User-Agents, tracked with the Space-Saving algorithm.

POST /debug/keylog/on
POST /debug/keylog/off

Toggles for SSLKEYLOG. When /debug/keylog/on is called, KeyLogWriter starts writing to a new temp file. When /debug/keylog/off is called, KeyLogWriter stops writing to the temp file and closes it. If /debug/keylog/off is not called, the temp file is closed after 15 minutes of inactivity.

POST /debug/logs/on
POST /debug/logs/off

Toggles for debug logging.

You can use the Tuscolo debug script to automatically obtain the random port for a systemd service and invoke an endpoint.

debug [-u unit] {useragents|ips|keylog={on|off}|logs={on|off}|port}

Partial tile garbage collection

Static CT chunks the log into “tiles” of 256 entries. If the pool is flushed and the final tiles is smaller than 256 entries, it’s written out as a partial tile. It is allowed to delete partial tiles once the corresponding full tile has been created.

partial-aftersun is a command designed to run as a cronjob which deletes superfluous partial tiles from a local storage backend, freeing up space. It reads the Sunlight config file directly, and has a number of safety measures to avoid deleting the wrong tiles.

The Tuscolo public configs include an example of how to schedule it with systemd timers.

Documentation

Overview

Package sunlight implements the Static Certificate Transparency API, including a Static CT log client.

Index

Examples

Constants

View Source
const TileHeight = 8
View Source
const TileWidth = 1 << TileHeight

Variables

View Source
var ErrWrongLogID = errors.New("sunlight: SCT log ID does not match public key")

ErrWrongLogID indicates that the log ID in the SCT does not match the public key of the log. Client.CheckInclusion can return an error wrapping this.

Functions

func AppendTileLeaf

func AppendTileLeaf(t []byte, e *LogEntry) []byte

AppendTileLeaf appends a LogEntry to a data tile.

func FormatCheckpoint

func FormatCheckpoint(c Checkpoint) string

func MarshalExtensions

func MarshalExtensions(e Extensions) ([]byte, error)

func NewRFC6962InjectedSigner added in v0.7.0

func NewRFC6962InjectedSigner(name string, key crypto.PublicKey, sig []byte, timestamp int64) (note.Signer, error)

NewRFC6962InjectedSigner constructs a note.Signer that uses the provided signature bytes as-is. It is useful to construct a signed checkpoint from a RFC 6962 TreeHeadSignature obtained from elsewhere.

func NewRFC6962Verifier

func NewRFC6962Verifier(name string, key crypto.PublicKey) (note.Verifier, error)

NewRFC6962Verifier constructs a new note.Verifier that verifies a RFC 6962 TreeHeadSignature formatted according to c2sp.org/static-ct-api.

func ParseTilePath added in v0.4.0

func ParseTilePath(path string) (tlog.Tile, error)

ParseTilePath parses a tile coordinate path according to c2sp.org/static-st-api. It differs from tlog.ParseTilePath in that it doesn't include an explicit tile height. It also supports names tiles at level -2.

func RFC6962SignatureTimestamp

func RFC6962SignatureTimestamp(sig note.Signature) (int64, error)

func TilePath

func TilePath(t tlog.Tile) string

TilePath returns a tile coordinate path describing t, according to c2sp.org/static-st-api. It differs from tlog.Tile.Path in that it doesn't include an explicit tile height. It also supports names tiles at level -2.

If t.Height is not TileHeight, TilePath panics.

Types

type Checkpoint

type Checkpoint = torchwood.Checkpoint

func ParseCheckpoint

func ParseCheckpoint(text string) (Checkpoint, error)

type Client added in v0.4.0

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

Client is a Certificate Transparency log client that fetches and authenticates tiles according to c2sp.org/static-ct-api, and exposes log entries as a Go iterator.

Example (LocalFilesystem)
package main

import (
	"context"
	"crypto/x509"
	"encoding/pem"

	"filippo.io/sunlight"
)

func main() {
	block, _ := pem.Decode([]byte(`-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4i7AmqGoGHsorn/eyclTMjrAnM0J
UUbyGJUxXqq1AjQ4qBC77wXkWt7s/HA8An2vrEBKIGQzqTjV8QIHrmpd4w==
-----END PUBLIC KEY-----`))
	key, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		panic(err)
	}

	client, err := sunlight.NewClient(&sunlight.ClientConfig{
		MonitoringPrefix: "gzip+file:///tank/logs/navigli2025h2/data/",
		PublicKey:        key,
	})
	if err != nil {
		panic(err)
	}

	checkpoint, _, err := client.Checkpoint(context.TODO())
	if err != nil {
		panic(err)
	}
	entry, _, err := client.Entry(context.TODO(), checkpoint.Tree, 142424242)
	if err != nil {
		panic(err)
	}
	println(entry.Timestamp)
}

func NewClient added in v0.4.0

func NewClient(config *ClientConfig) (*Client, error)

NewClient creates a new Client.

func (*Client) CheckInclusion added in v0.5.1

func (c *Client) CheckInclusion(ctx context.Context, tree tlog.Tree, sct []byte) (*LogEntry, tlog.RecordProof, error)

CheckInclusion fetches the log entry for the given SCT, and verifies that it is included in the given tree and that the SCT is valid for the entry.

The provided tree should have been verified by the caller, for example using Client.Checkpoint.

If the SCT log ID does not match [ClientConfig.PublicKey], CheckInclusion returns an error wrapping ErrWrongLogID.

Example
package main

import (
	"context"
	"crypto/x509"
	"encoding/pem"
	"errors"

	"filippo.io/sunlight"
	x509ct "github.com/google/certificate-transparency-go/x509"
	"golang.org/x/mod/sumdb/tlog"
)

func main() {
	block, _ := pem.Decode([]byte(`-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4i7AmqGoGHsorn/eyclTMjrAnM0J
UUbyGJUxXqq1AjQ4qBC77wXkWt7s/HA8An2vrEBKIGQzqTjV8QIHrmpd4w==
-----END PUBLIC KEY-----`))
	key, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		panic(err)
	}

	certificate, _ := pem.Decode([]byte(`-----BEGIN CERTIFICATE-----
MIID0DCCA1WgAwIBAgISLJBonEz2NlVeQFEPlG5vsIrMMAoGCCqGSM49BAMDMFMx
CzAJBgNVBAYTAlVTMSAwHgYDVQQKExcoU1RBR0lORykgTGV0J3MgRW5jcnlwdDEi
MCAGA1UEAxMZKFNUQUdJTkcpIEZhbHNlIEZlbm5lbCBFNjAeFw0yNTA3MjcyMjA4
NDlaFw0yNTEwMjUyMjA4NDhaMCMxITAfBgNVBAMTGGhhcmRlcnJhZGlvZm1qdW1w
LnJhZC5pbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCLE1WbEwJ1Y+k3bj+vf
R4s6nDem8eZea0vZ8sgJqh13mm89lHZZTr5l/qRRFbcl6fL8LJNw0vapzr3rpnTu
7NGjggI3MIICMzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG
CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFvoTvFTQNkRzVpfP8JF
v0YNc908MB8GA1UdIwQYMBaAFKF0GgZtULeGLUoswX60jYhJbM0WMDYGCCsGAQUF
BwEBBCowKDAmBggrBgEFBQcwAoYaaHR0cDovL3N0Zy1lNi5pLmxlbmNyLm9yZy8w
IwYDVR0RBBwwGoIYaGFyZGVycmFkaW9mbWp1bXAucmFkLmlvMBMGA1UdIAQMMAow
CAYGZ4EMAQIBMDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9zdGctZTYuYy5sZW5j
ci5vcmcvNzcuY3JsMIIBDQYKKwYBBAHWeQIEAgSB/gSB+wD5AHYA3Zk0/KXnJIDJ
Vmh9gTSZCEmySfe1adjHvKs/XMHzbmQAAAGYTiQDlAAABAMARzBFAiAZaM/o9pZJ
AoaMVTaHqM6aViSIjLam0CiEe8OK5M8RTAIhANS+35smBUCZvpM+zRNwSQ1siDDm
f2F8ayHSru9+BTeEAH8A5Pt3SiEkxYZAsYMvUKv63ISjiu1xke62aSI3ksv2KJEA
AAGYTiQDpAAIAAAFAATjOI4EAwBIMEYCIQC9ARnxeUgUL8Gkvl1lgKkuFVJaAOkv
TQ6H8sYzVcbliQIhAN5nTObp15PQSusjd0Qd+povk1DJ4tVA9rNKFEGOpTVoMAoG
CCqGSM49BAMDA2kAMGYCMQCuw26zAJbmCgvfsDu9ong073LppgwPWogX1DI050uS
scMeHBWmB0jXuic4zkVzVBQCMQD+IkFkLg8qOHNtipO+mtTCtdW8mEl7Ptb3yv04
ybky1bC4rbimZJIjvhnqMcMkf/I=
-----END CERTIFICATE-----`))

	client, err := sunlight.NewClient(&sunlight.ClientConfig{
		MonitoringPrefix: "https://navigli2025h2.skylight.geomys.org/",
		PublicKey:        key,
		UserAgent:        "ExampleClient ([email protected], +https://example.com)",
	})
	if err != nil {
		panic(err)
	}

	checkpoint, _, err := client.Checkpoint(context.TODO())
	if err != nil {
		panic(err)
	}

	cert, err := x509ct.ParseCertificate(certificate.Bytes)
	if err != nil {
		panic(err)
	}
	for _, sct := range cert.SCTList.SCTList {
		entry, proof, err := client.CheckInclusion(context.TODO(), checkpoint.Tree, sct.Val)
		if errors.Is(err, sunlight.ErrWrongLogID) {
			println("SCT log ID does not match public key, skipping")
			continue
		}
		if err != nil {
			panic(err)
		}
		println("Entry leaf index:", entry.LeafIndex)
		println("Entry timestamp:", entry.Timestamp)

		// There is no need to check the inclusion proof, but if provided to a third
		// party, it can be checked as follows.
		rh := tlog.RecordHash(entry.MerkleTreeLeaf())
		if err := tlog.CheckRecord(proof, checkpoint.N, checkpoint.Hash, entry.LeafIndex, rh); err != nil {
			panic(err)
		}
	}
}

func (*Client) Checkpoint added in v0.5.1

func (c *Client) Checkpoint(ctx context.Context) (torchwood.Checkpoint, *note.Note, error)

Checkpoint fetches the latest checkpoint and verifies it.

The signature by [ClientConfig.PublicKey] is always the first note.Note.Sigs entry. RFC6962SignatureTimestamp can be used to extract the STH timestamp from the signature.

func (*Client) Entries added in v0.4.0

func (c *Client) Entries(ctx context.Context, tree tlog.Tree, start int64) iter.Seq2[int64, *LogEntry]

Entries returns an iterator that yields entries from the given tree, starting at the given index. The first item in the yielded pair is the overall entry index in the log, starting at start.

The provided tree should have been verified by the caller, for example using Client.Checkpoint.

Iteration may stop before the size of the tree to avoid fetching a partial data tile. Resuming with the same tree will yield the remaining entries, however clients tailing a growing log are encouraged to fetch the next checkpoint and use that as the tree argument.

Callers must check Client.Err after the iteration breaks.

Example
package main

import (
	"context"
	"crypto/x509"
	"encoding/pem"
	"fmt"

	"filippo.io/sunlight"
)

func main() {
	// Note: clients that don't participate in the transparency ecosystem
	// and are only interested in a feed of names can consider using the
	// more efficient UnauthenticatedTrimmedEntries method instead.

	block, _ := pem.Decode([]byte(`-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4i7AmqGoGHsorn/eyclTMjrAnM0J
UUbyGJUxXqq1AjQ4qBC77wXkWt7s/HA8An2vrEBKIGQzqTjV8QIHrmpd4w==
-----END PUBLIC KEY-----`))
	key, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		panic(err)
	}

	client, err := sunlight.NewClient(&sunlight.ClientConfig{
		MonitoringPrefix: "https://navigli2025h2.skylight.geomys.org/",
		PublicKey:        key,
		UserAgent:        "ExampleClient ([email protected], +https://example.com)",
	})
	if err != nil {
		panic(err)
	}

	var start int64
	for {
		checkpoint, _, err := client.Checkpoint(context.TODO())
		if err != nil {
			panic(err)
		}
		for i, entry := range client.Entries(context.TODO(), checkpoint.Tree, start) {
			fmt.Printf("%d: %d %d %x\n", i, entry.LeafIndex, entry.Timestamp, entry.IssuerKeyHash)
			start = i + 1
		}
		if err := client.Err(); err != nil {
			panic(err)
		}
	}
}

func (*Client) Entry added in v0.7.0

func (c *Client) Entry(ctx context.Context, tree tlog.Tree, index int64) (*LogEntry, tlog.RecordProof, error)

Entry returns a log entry by its index, and an inclusion proof in the tree.

The provided tree should have been verified by the caller, for example using Client.Checkpoint.

func (*Client) Err added in v0.4.0

func (c *Client) Err() error

Err returns the error encountered by the latest Client.Entries call.

func (*Client) Fetcher deprecated added in v0.5.2

func (c *Client) Fetcher() torchwood.TileReader

Fetcher used to return the underlying torchwood.TileFetcher. It is now a compatibility alias of Client.TileReader, which now exposes a torchwood.TileReader.ReadEndpoint method.

Deprecated: use Client.TileReader instead.

func (*Client) Issuer added in v0.5.2

func (c *Client) Issuer(ctx context.Context, fp [32]byte) (*x509.Certificate, error)

Issuer returns the issuer matching the fingerprint from [LogEntry.ChainFingerprints].

func (*Client) TileReader added in v0.7.0

func (c *Client) TileReader() torchwood.TileReader

TileReader returns the underlying torchwood.TileReader, which can be used to fetch endpoints directly, or as a tlog.HashReader via torchwood.TileHashReaderWithContext.

Its torchwood.TileReader.ReadTiles method respects [ClientConfig.Cache] if set. torchwood.TileReader.ReadEndpoint does not.

func (*Client) UnauthenticatedTrimmedEntries added in v0.6.0

func (c *Client) UnauthenticatedTrimmedEntries(ctx context.Context, start, end int64) iter.Seq2[int64, *TrimmedEntry]

UnauthenticatedTrimmedEntries returns an iterator that yields trimmed entries, starting and ending at the given index. The first item in the yielded pair is the overall entry index in the log, starting at start.

Entries are NOT authenticated against a checkpoint and, if supported by the log, are fetched through a more efficient protocol than Client.Entries. This method is only suitable for clients that don't participate in the transparency ecosystem, and are only interested in a feed of names.

Callers must check Client.Err after the iteration breaks.

Example
package main

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"filippo.io/sunlight"
)

func main() {
	// Important: UnauthenticatedTrimmedEntries does NOT verify the signed tree
	// head. It is only suitable for clients that don't participate in the
	// transparency ecosystem, and are only interested in a feed of names.

	client, err := sunlight.NewClient(&sunlight.ClientConfig{
		MonitoringPrefix: "https://navigli2025h2.skylight.geomys.org/",
		UserAgent:        "ExampleClient ([email protected], +https://example.com)",
	})
	if err != nil {
		panic(err)
	}

	var start int64
	for {
		checkpoint, err := client.Fetcher().ReadEndpoint(context.TODO(), "checkpoint")
		if err != nil {
			panic(err)
		}

		_, rest, _ := strings.Cut(string(checkpoint), "\n")
		size, _, _ := strings.Cut(rest, "\n")
		end, err := strconv.ParseInt(size, 10, 64)
		if err != nil {
			panic(err)
		}

		for i, entry := range client.UnauthenticatedTrimmedEntries(context.TODO(), start, end) {
			fmt.Printf("%d: %s\n", i, entry.DNS)
			start = i + 1
		}
		if err := client.Err(); err != nil {
			panic(err)
		}
	}
}

type ClientConfig added in v0.4.0

type ClientConfig struct {
	// MonitoringPrefix is the c2sp.org/static-ct-api monitoring prefix.
	//
	// If the MonitoringPrefix has schema "file://", Client will read tiles from
	// the local filesystem, and most other settings will be ignored.
	//
	// If it has schema "gzip+file://", the data tiles are expected to be
	// gzip-compressed.
	//
	// If it has schema "archive+file://", Client will read tiles from a set of
	// [archival zip files] in the specified directory.
	//
	// [archival zip files]:
	// https://github.com/geomys/ct-archive/blob/main/README.md#archival-format
	MonitoringPrefix string

	// PublicKey is the public key of the log, used to verify checkpoints in
	// [Client.Checkpoint] and SCTs in [Client.CheckInclusion].
	PublicKey crypto.PublicKey

	// AllowRFC6962ArchivalLeafs indicates whether to accept leafs archived from
	// RFC 6962 logs, which lack the LeafIndex extension.
	//
	// See [LogEntry.RFC6962ArchivalLeaf] for details.
	AllowRFC6962ArchivalLeafs bool

	// HTTPClient is the HTTP client used to fetch tiles. If nil, a client is
	// created with default timeouts and settings.
	//
	// Note that Client may need to make multiple parallel requests to
	// the same host, more than the default MaxIdleConnsPerHost.
	HTTPClient *http.Client

	// UserAgent is the User-Agent string used for HTTP requests. It must be
	// set, and it must include an email address and/or an HTTPS URL.
	//
	// The library version will be appended to the User-Agent string.
	UserAgent string

	// Timeout is how long the Entries iterator can take to yield an entry.
	// This includes any Retry-After waits. If zero, it defaults to five minutes.
	Timeout time.Duration

	// ConcurrencyLimit is the maximum number of concurrent requests
	// made by the Client. If zero, there is no limit.
	ConcurrencyLimit int

	// Cache, if set, is a directory where the client will permanently cache
	// verified non-partial tiles, following the same structure as the URLs.
	//
	// This directory always grows and is never pruned by the client. Most
	// clients, especially those scanning a log sequentially, have no need to
	// set this. [Client.Entries] will still use in-memory caching for the
	// duration of the call.
	Cache string

	// Logger is the logger used to log errors and progress.
	// If nil, log lines are discarded.
	Logger *slog.Logger
}

ClientConfig is the configuration for a Client.

type Extensions

type Extensions struct {
	LeafIndex int64
}

Extensions is the CTExtensions field of SignedCertificateTimestamp and TimestampedEntry, according to c2sp.org/static-ct-api.

func ParseExtensions

func ParseExtensions(extensions []byte) (Extensions, error)

ParseExtensions parse a CTExtensions field, ignoring unknown extensions. It is an error if the leaf_index extension is missing.

type LogEntry

type LogEntry struct {
	// Certificate is either the TimestampedEntry.signed_entry, or the
	// PreCert.tbs_certificate for Precertificates.
	// It must be at most 2^24-1 bytes long.
	Certificate []byte

	// IsPrecert is true if LogEntryType is precert_entry. Otherwise, the
	// following three fields are zero and ignored.
	IsPrecert bool

	// IssuerKeyHash is the PreCert.issuer_key_hash.
	IssuerKeyHash [32]byte

	// ChainFingerprints are the SHA-256 hashes of the certificates in the
	// X509ChainEntry.certificate_chain or
	// PrecertChainEntry.precertificate_chain.
	ChainFingerprints [][32]byte

	// PreCertificate is the PrecertChainEntry.pre_certificate.
	// It must be at most 2^24-1 bytes long.
	PreCertificate []byte

	// LeafIndex is the zero-based index of the leaf in the log.
	// It must be between 0 and 2^40-1.
	//
	// If RFC6962ArchivalLeaf is true, this field is ignored and
	// should be set to 0.
	LeafIndex int64

	// RFC6962ArchivalLeaf is true if this LogEntry is an archived
	// RFC 6962 log leaf, which is missing the leaf index extension.
	//
	// [ReadTileLeaf] always sets this to false. To read such leaves,
	// use [ReadTileLeafMaybeArchival].
	RFC6962ArchivalLeaf bool

	// Timestamp is the TimestampedEntry.timestamp.
	Timestamp int64
}

func ReadTileLeaf

func ReadTileLeaf(tile []byte) (e *LogEntry, rest []byte, err error)

ReadTileLeaf reads a LogEntry from a data tile, and returns the remaining data in the tile.

func ReadTileLeafMaybeArchival added in v0.7.0

func ReadTileLeafMaybeArchival(tile []byte) (e *LogEntry, rest []byte, err error)

ReadTileLeafMaybeArchival reads a LogEntry from a data tile, and returns the remaining data in the tile.

If the leaf is missing the leaf index extension, the returned LogEntry has RFC6962ArchivalLeaf set to true, and LeafIndex set to zero.

func (*LogEntry) MerkleTreeLeaf

func (e *LogEntry) MerkleTreeLeaf() []byte

MerkleTreeLeaf returns a RFC 6962 MerkleTreeLeaf.

This is the Merkle tree leaf that can be passed, for example, to tlog.RecordHash for use with tlog.CheckRecord.

It also matches the digitally-signed data of an SCT, which is technically not a MerkleTreeLeaf, but a completely identical structure (except for the second field, which is a SignatureType of value 0 and length 1 instead of a MerkleLeafType of value 0 and length 1).

func (*LogEntry) TrimmedEntry added in v0.6.0

func (e *LogEntry) TrimmedEntry() (*TrimmedEntry, error)

type TrimmedEntry added in v0.6.0

type TrimmedEntry struct {
	// Timestamp is the UNIX timestamp in milliseconds of when the entry was
	// added to the log.
	Timestamp int64

	// Subject contains the parsed Subject information from the certificate.
	Subject struct {
		Country, Organization, OrganizationalUnit []string `json:",omitempty"`
		Locality, Province                        []string `json:",omitempty"`
		StreetAddress, PostalCode                 []string `json:",omitempty"`
		CommonName                                string   `json:",omitempty"`
	} `json:",omitzero"`

	// DNS and IP are the Subject Alternative Names of the certificate.
	DNS []string `json:",omitempty"`
	IP  []string `json:",omitempty"`
}

A TrimmedEntry is a subset of the information in a LogEntry, including names parsed from the certificate or pre-certificate.

Directories

Path Synopsis
cmd
names-prism command
Command names-prism creates missing names tiles for existing data tiles.
Command names-prism creates missing names tiles for existing data tiles.
partial-aftersun command
Command partial-aftersun deletes partial tiles from a Sunlight local backend where a corresponding full tile exists.
Command partial-aftersun deletes partial tiles from a Sunlight local backend where a corresponding full tile exists.
skylight command
Command skylight runs a Certificate Transparency log read-path server.
Command skylight runs a Certificate Transparency log read-path server.
sunlight command
Command sunlight runs a Certificate Transparency log write-path server.
Command sunlight runs a Certificate Transparency log write-path server.
sunlight-keygen command
internal
durable
Package durable provides equivalent functionality to the os package, but with additional guarantees for durability based on os.File.Sync.
Package durable provides equivalent functionality to the os package, but with additional guarantees for durability based on os.File.Sync.
frequent
Package frequent implements the Space Saving algorithm for counting appearances of the most frequent items in a stream of data (sometimes referred to as the heavy hitters).
Package frequent implements the Space Saving algorithm for counting appearances of the most frequent items in a stream of data (sometimes referred to as the heavy hitters).
heavyhitter
Package heavyhitter registers two endpoints /debug/heavyhitter/useragents and /debug/heavyhitter/ips as a side-effect.
Package heavyhitter registers two endpoints /debug/heavyhitter/useragents and /debug/heavyhitter/ips as a side-effect.
immutable
Package immutable provides best-effort support for setting and unsetting the immutable flag on files.
Package immutable provides best-effort support for setting and unsetting the immutable flag on files.
keylog
Package keylog registers two endpoints /debug/keylog/on and /debug/keylog/off as a side-effect.
Package keylog registers two endpoints /debug/keylog/on and /debug/keylog/off as a side-effect.
reused
Package reused allows tracking connection reuse in HTTP requests.
Package reused allows tracking connection reuse in HTTP requests.
stdlog
Package heavyhitter registers two endpoints, /debug/logs/on and /debug/logs/off, as a side-effect.
Package heavyhitter registers two endpoints, /debug/logs/on and /debug/logs/off, as a side-effect.

Jump to

Keyboard shortcuts

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