diff --git a/opts/opts.go b/opts/opts.go
index 92703b66b6f82d2f10a6f96bb1822c3641b90870..b76011208a282d6a3604e521d457671f8709f395 100644
--- a/opts/opts.go
+++ b/opts/opts.go
@@ -2,7 +2,6 @@ package opts
 
 import (
 	"crypto/x509"
-	"encoding/json"
 	"encoding/pem"
 	"errors"
 	"fmt"
@@ -12,23 +11,8 @@ import (
 	"filippo.io/age"
 
 	"system-transparency.org/stboot/stlog"
-	"system-transparency.org/stboot/trust"
 )
 
-func ReadTrustPolicy(filename string) (*trust.Policy, error) {
-	f, err := os.Open(filename)
-	if err != nil {
-		return nil, fmt.Errorf("opening trust policy failed: %w", err)
-	}
-	defer f.Close()
-
-	trustPolicy := trust.Policy{}
-	if err := json.NewDecoder(f).Decode(&trustPolicy); err != nil {
-		return nil, err
-	}
-	return &trustPolicy, nil
-}
-
 // Read a certificate file, checking and logging validity dates. Skip
 // invalid certs, but return error if no valid certs are found.
 func ReadCertsFile(filename string, now time.Time) ([]*x509.Certificate, error) {
diff --git a/opts/opts_test.go b/opts/opts_test.go
index d34d5b532bd5ce0df46ba58c5e14e503ca7dffdb..d43db8b3794e09330e27110bb71ac575cb32bdee 100644
--- a/opts/opts_test.go
+++ b/opts/opts_test.go
@@ -16,45 +16,6 @@ import (
 	"testing"
 )
 
-func TestReadTrustPolicy(t *testing.T) {
-	tests := []struct {
-		name, file string
-		wantErr    bool
-	}{
-		{
-			name: "Successful loading",
-			file: "testdata/trust_policy_good_all_set.json",
-		},
-		{
-			name:    "Empty",
-			file:    "testdata/empty",
-			wantErr: true,
-		},
-		{
-			name:    "Bad content",
-			file:    "testdata/trust_policy_bad_unset.json",
-			wantErr: true,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			trustPolicy, err := ReadTrustPolicy(tt.file)
-
-			if tt.wantErr {
-				if err != nil {
-					t.Logf("%s: err (expected): %v", tt.name, err)
-				} else {
-					t.Errorf("%s: invalid input, but no error", tt.name)
-				}
-			} else if err != nil {
-				t.Errorf("%s: failed: %v", tt.name, err)
-			} else if trustPolicy == nil {
-				t.Errorf("%s: failed, nil trustPolicy", tt.name)
-			}
-		})
-	}
-}
-
 func TestReadCertsFile(t *testing.T) {
 	log.SetFlags(0)
 
diff --git a/stboot.go b/stboot.go
index c8513c635a16f13e3136546cf607eb3b1e733f0b..1a2b251f65ee6341420080ebed387bee4b591763 100644
--- a/stboot.go
+++ b/stboot.go
@@ -6,7 +6,6 @@ package main
 
 import (
 	"context"
-	"crypto/x509"
 	"errors"
 	"flag"
 	"fmt"
@@ -23,7 +22,6 @@ import (
 
 	"golang.org/x/sys/unix"
 
-	"filippo.io/age"
 	"github.com/u-root/u-root/pkg/boot"
 	"github.com/u-root/u-root/pkg/libinit"
 
@@ -32,7 +30,6 @@ import (
 	di "system-transparency.org/stboot/internal/dependency"
 	"system-transparency.org/stboot/internal/ui"
 	"system-transparency.org/stboot/internal/wctx"
-	"system-transparency.org/stboot/opts"
 	"system-transparency.org/stboot/ospkg"
 	"system-transparency.org/stboot/stlog"
 	"system-transparency.org/stboot/trust"
@@ -46,11 +43,7 @@ const (
 
 // Files at initramfs.
 const (
-	trustPolicyFile = "/etc/trust_policy/trust_policy.json"
-	signingRootFile = "/etc/trust_policy/ospkg_signing_root.pem"
-	httpsRootsFile  = "/etc/trust_policy/tls_roots.pem"
-	// Age decryption identities (optional).
-	decryptionIdentitiesFile = "/etc/trust_policy/decryption_identities"
+	trustPolicyDir = "/etc/trust_policy"
 
 	promptTimeout    = 30 * time.Second
 	interruptTimeout = 5 * time.Second
@@ -220,49 +213,24 @@ func main() {
 	///////////////////////////////////////
 	// Setup of trust policy and cert roots
 	///////////////////////////////////////
-	trustPolicy, err := opts.ReadTrustPolicy(trustPolicyFile)
+	trustRoot, err := trust.ReadTrustRoot(trustPolicyDir, time.Now())
 	if err != nil {
 		stlog.Error("trust policy: %v", err)
 		host.Recover()
 	}
-
-	now := time.Now()
-	certs, err := opts.ReadCertsFile(signingRootFile, now)
-	if err != nil {
-		stlog.Error("signing root certificate: %v", err)
-		host.Recover()
-	}
-	if got := len(certs); got != 1 {
-		stlog.Error("exactly one root certificate is expected, got %d", got)
-		host.Recover()
-	}
-	signingRootCert := certs[0]
-
-	httpsRoots, err := opts.ReadOptionalCertsFile(httpsRootsFile, now)
-	if err != nil {
-		stlog.Error("https root certificates: %v", err)
-		host.Recover()
-	}
-
-	decryptionIdentities, err := opts.ReadDecryptionIdentities(decryptionIdentitiesFile)
-	if err != nil {
-		stlog.Error("decryption identities: %v", err)
-		host.Recover()
-	}
-
 	deadlineDuration := time.Duration(*deadline) * time.Minute
 
 	// The boot*Image functions are not expected to return,
 	// and they return with a nil error only in dryrun mode,
 	// i.e., getting a nil error is the exception.
-	err = bootConfiguredImage(ctx, trustPolicy, signingRootCert, httpsRoots, decryptionIdentities, deadlineDuration, *dryRun, hasProvisionImage())
+	err = bootConfiguredImage(ctx, &trustRoot, deadlineDuration, *dryRun, hasProvisionImage())
 	if err == nil {
 		return
 	}
 	stlog.Error("booting configured OS package failed: %v", err)
 	if err == host.ErrConfigNotFound || userWantsProvision() {
 		stlog.Info("PROVISION MODE! Attempting to boot /ospkg/provision.{jzon,zip}")
-		err := bootProvisionImage(ctx, trustPolicy, signingRootCert, *dryRun)
+		err := bootProvisionImage(ctx, &trustRoot, *dryRun)
 		if err == nil {
 			return
 		}
@@ -272,7 +240,7 @@ func main() {
 	host.Recover()
 }
 
-func bootConfiguredImage(ctx context.Context, trustPolicy *trust.Policy, signingRootCert *x509.Certificate, httpsRoots []*x509.Certificate, decryptionIdentities []age.Identity, deadline time.Duration, dryRun, enableInterrupt bool) error {
+func bootConfiguredImage(ctx context.Context, trustRoot *trust.Root, deadline time.Duration, dryRun, enableInterrupt bool) error {
 	//////////////////
 	// Get host config
 	//////////////////
@@ -301,11 +269,11 @@ func bootConfiguredImage(ctx context.Context, trustPolicy *trust.Policy, signing
 	}
 
 	// System appears provisioned, apply host config.
-	if trustPolicy.FetchMethod == ospkg.FetchFromNetwork {
+	if trustRoot.Policy.FetchMethod == trust.FetchFromNetwork {
 		// It's possible to have a http url for the ospkg_pointer, but a https
 		// url in the downloaded descriptor. We don't detect that case here, but
 		// it will fail later if no HTTPS roots are configured.
-		if needsHTTPS(*hostCfg.OSPkgPointer) && len(httpsRoots) == 0 {
+		if needsHTTPS(*hostCfg.OSPkgPointer) && len(trustRoot.HTTPSRootCerts) == 0 {
 			return fmt.Errorf("network boot with HTTPS is configured, but HTTPS root certificates are missing")
 		}
 
@@ -314,17 +282,18 @@ func bootConfiguredImage(ctx context.Context, trustPolicy *trust.Policy, signing
 			return fmt.Errorf("failed to setup network interfaces: %v", err)
 		}
 	}
-	return getAndBootImage(ctx, trustPolicy, signingRootCert, httpsRoots, decryptionIdentities, hostCfg, deadline, dryRun, timer)
+	return getAndBootImage(ctx, trustRoot, hostCfg, deadline, dryRun, timer)
 }
 
-func bootProvisionImage(ctx context.Context, trustPolicy *trust.Policy, signingRootCert *x509.Certificate, dryRun bool) error {
-	provTrustPolicy := *trustPolicy
-	provTrustPolicy.FetchMethod = ospkg.FetchFromInitramfs
-	return getAndBootImage(ctx, &provTrustPolicy, signingRootCert, nil, nil, provisionHostConfig(), 0, dryRun, nil)
+func bootProvisionImage(ctx context.Context, trustRoot *trust.Root, dryRun bool) error {
+	provTrustRoot := *trustRoot
+	provTrustRoot.Policy.FetchMethod = trust.FetchFromInitramfs
+	provTrustRoot.HTTPSRootCerts = nil
+	return getAndBootImage(ctx, &provTrustRoot, provisionHostConfig(), 0, dryRun, nil)
 }
 
-func getAndBootImage(ctx context.Context, trustPolicy *trust.Policy, signingRootCert *x509.Certificate, httpsRoots []*x509.Certificate, decryptionIdentities []age.Identity, hostCfg host.Config, deadline time.Duration, dryRun bool, timer <-chan time.Time) error {
-	img, err := getImage(ctx, trustPolicy, signingRootCert, httpsRoots, decryptionIdentities, hostCfg, deadline)
+func getAndBootImage(ctx context.Context, trustRoot *trust.Root, hostCfg host.Config, deadline time.Duration, dryRun bool, timer <-chan time.Time) error {
+	img, err := getImage(ctx, trustRoot, hostCfg, deadline)
 	if err != nil {
 		return err
 	}
@@ -354,7 +323,7 @@ func userWantsProvision() bool {
 }
 
 // Fetch, verify, unpack, and measure an OS package, specified by the host config.
-func getImage(ctx context.Context, trustPolicy *trust.Policy, signingRootCert *x509.Certificate, httpsRoots []*x509.Certificate, decryptionIdentities []age.Identity, hostCfg host.Config, deadline time.Duration) (*boot.LinuxImage, error) {
+func getImage(ctx context.Context, trustRoot *trust.Root, hostCfg host.Config, deadline time.Duration) (*boot.LinuxImage, error) {
 	fsys := di.DefaultFilesystem(ctx)
 
 	//////////////////
@@ -363,11 +332,11 @@ func getImage(ctx context.Context, trustPolicy *trust.Policy, signingRootCert *x
 
 	var osp *ospkg.OSPackage
 
-	switch trustPolicy.FetchMethod {
-	case ospkg.FetchFromNetwork:
+	switch trustRoot.Policy.FetchMethod {
+	case trust.FetchFromNetwork:
 		stlog.Info("Loading OS package via network")
 
-		client := network.NewHTTPClient(httpsRoots, false, network.WithDecryption(decryptionIdentities))
+		client := network.NewHTTPClient(trustRoot.HTTPSRootCerts, false, network.WithDecryption(trustRoot.DecryptionIdentities))
 
 		stlog.Debug("OS package pointer: %s", *hostCfg.OSPkgPointer)
 
@@ -381,7 +350,7 @@ func getImage(ctx context.Context, trustPolicy *trust.Policy, signingRootCert *x
 
 			return nil, errRecover
 		}
-	case ospkg.FetchFromInitramfs:
+	case trust.FetchFromInitramfs:
 		stlog.Info("Loading OS package from initramfs")
 
 		var err error
@@ -392,7 +361,7 @@ func getImage(ctx context.Context, trustPolicy *trust.Policy, signingRootCert *x
 			return nil, errRecover
 		}
 	default:
-		stlog.Error("unknown OS package fetch method %q", trustPolicy.FetchMethod)
+		stlog.Error("unknown OS package fetch method %q", trustRoot.Policy.FetchMethod)
 
 		return nil, errRecover
 	}
@@ -403,14 +372,14 @@ func getImage(ctx context.Context, trustPolicy *trust.Policy, signingRootCert *x
 
 	// TODO: write ospkg.info method for debug output
 
-	numSig, valid, err := osp.Verify(signingRootCert, time.Now())
+	numSig, valid, err := osp.Verify(trustRoot.SigningRootCert, time.Now())
 	if err != nil {
 		stlog.Error("Verifying OS package: %v", err)
 
 		return nil, errRecover
 	}
 
-	threshold := trustPolicy.SignatureThreshold
+	threshold := trustRoot.Policy.SignatureThreshold
 	if valid < threshold {
 		stlog.Error("Not enough valid signatures: %d found, %d valid, %d required", numSig, valid, threshold)
 
diff --git a/stboot_test.go b/stboot_test.go
index b2c92a87bfe0afece3575fc6ed082bba1f3c7ee8..3f67b70f2b2f004d1ca683bc98497eef065c06dc 100644
--- a/stboot_test.go
+++ b/stboot_test.go
@@ -278,7 +278,7 @@ func TestGetImage(t *testing.T) {
 	mockFsys := test.NewMockFS()
 	trustPolicy := trust.Policy{
 		SignatureThreshold: 1,
-		FetchMethod:        ospkg.FetchFromInitramfs,
+		FetchMethod:        trust.FetchFromInitramfs,
 	}
 	assert.NoError(t, err)
 	err = mockFsys.Add("/ospkg/provision.zip", &fstest.MapFile{Data: ospkgZip})
@@ -294,7 +294,7 @@ func TestGetImage(t *testing.T) {
 	hostCfg := provisionHostConfig()
 
 	t.Run("no UX identity", func(t *testing.T) {
-		_, err = getImage(ctx, &trustPolicy, root.Cert, httpsRoots, nil, hostCfg, 20*time.Minute)
+		_, err = getImage(ctx, &trust.Root{Policy: trustPolicy, SigningRootCert: root.Cert, HTTPSRootCerts: httpsRoots}, hostCfg, 20*time.Minute)
 		assert.NoError(t, err)
 	})
 
@@ -306,7 +306,7 @@ func TestGetImage(t *testing.T) {
 
 		ctx = di.WithEFIVar(ctx, mockEfiVar)
 
-		_, err = getImage(ctx, &trustPolicy, root.Cert, httpsRoots, nil, hostCfg, 20*time.Minute)
+		_, err = getImage(ctx, &trust.Root{Policy: trustPolicy, SigningRootCert: root.Cert, HTTPSRootCerts: httpsRoots}, hostCfg, 20*time.Minute)
 		assert.NoError(t, err)
 	})
 
@@ -318,7 +318,7 @@ func TestGetImage(t *testing.T) {
 			assert.NoError(t, err)
 			ctx = di.WithFilesystem(ctx, myMockFs)
 
-			_, err = getImage(ctx, &trustPolicy, root.Cert, httpsRoots, nil, hostCfg, 20*time.Minute)
+			_, err = getImage(ctx, &trust.Root{Policy: trustPolicy, SigningRootCert: root.Cert, HTTPSRootCerts: httpsRoots}, hostCfg, 20*time.Minute)
 			assert.Error(t, err)
 		})
 	}
@@ -326,7 +326,7 @@ func TestGetImage(t *testing.T) {
 	t.Run("optional https roots", func(t *testing.T) {
 		ctx = di.WithFilesystem(ctx, mockFsys)
 
-		_, err = getImage(ctx, &trustPolicy, root.Cert, nil, nil, hostCfg, 20*time.Minute)
+		_, err = getImage(ctx, &trust.Root{Policy: trustPolicy, SigningRootCert: root.Cert, HTTPSRootCerts: nil}, hostCfg, 20*time.Minute)
 		assert.NoError(t, err)
 	})
 
@@ -342,19 +342,27 @@ func TestGetImage(t *testing.T) {
 		}
 		ctx = di.WithFilesystem(ctx, myMockFs)
 
-		_, err = getImage(ctx, &trust.Policy{
-			SignatureThreshold: 1,
-			FetchMethod:        ospkg.FetchFromNetwork,
-		}, root.Cert, nil, nil, hostCfg, 20*time.Minute)
+		_, err = getImage(ctx, &trust.Root{
+			Policy: trust.Policy{
+				SignatureThreshold: 1,
+				FetchMethod:        trust.FetchFromNetwork,
+			},
+			SigningRootCert: root.Cert,
+			HTTPSRootCerts:  nil,
+		}, hostCfg, 20*time.Minute)
 		assert.Error(t, err)
 	})
 	t.Run("invalid signature threshold", func(t *testing.T) {
 		ctx = di.WithFilesystem(ctx, mockFsys)
 
-		_, err = getImage(ctx, &trust.Policy{
-			SignatureThreshold: 4,
-			FetchMethod:        ospkg.FetchFromInitramfs,
-		}, root.Cert, httpsRoots, nil, hostCfg, 20*time.Minute)
+		_, err = getImage(ctx, &trust.Root{
+			Policy: trust.Policy{
+				SignatureThreshold: 4,
+				FetchMethod:        trust.FetchFromInitramfs,
+			},
+			SigningRootCert: root.Cert,
+			HTTPSRootCerts:  httpsRoots,
+		}, hostCfg, 20*time.Minute)
 		assert.Error(t, err)
 	})
 }
diff --git a/ospkg/fetchmethod.go b/trust/fetchmethod.go
similarity index 99%
rename from ospkg/fetchmethod.go
rename to trust/fetchmethod.go
index 7f6cf7af4e0559dbf80944d51515fcb58b53024a..f37e967df50128346cf1710bcf56b9cba60302b8 100644
--- a/ospkg/fetchmethod.go
+++ b/trust/fetchmethod.go
@@ -1,4 +1,4 @@
-package ospkg
+package trust
 
 import (
 	"encoding/json"
diff --git a/ospkg/fetchmethod_test.go b/trust/fetchmethod_test.go
similarity index 99%
rename from ospkg/fetchmethod_test.go
rename to trust/fetchmethod_test.go
index 84cecb5afae6729ebc37d39c54c8d9974477394a..4cdbe9ecda90cb8c35f346da95d7a16efea545df 100644
--- a/ospkg/fetchmethod_test.go
+++ b/trust/fetchmethod_test.go
@@ -1,4 +1,4 @@
-package ospkg
+package trust
 
 import (
 	"encoding/json"
diff --git a/trust/policy.go b/trust/policy.go
index fea5e96cfca15ff0af0f108e26ddfa133c61dce1..3238221baa09bca0bbdf548df5e0fef58c16f50a 100644
--- a/trust/policy.go
+++ b/trust/policy.go
@@ -4,16 +4,15 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-
-	"system-transparency.org/stboot/ospkg"
+	"os"
 )
 
 var ErrInvalidPolicy = errors.New("invalid policy")
 
 // Policy holds security configuration.
 type Policy struct {
-	SignatureThreshold int               `json:"ospkg_signature_threshold"`
-	FetchMethod        ospkg.FetchMethod `json:"ospkg_fetch_method"`
+	SignatureThreshold int         `json:"ospkg_signature_threshold"`
+	FetchMethod        FetchMethod `json:"ospkg_fetch_method"`
 }
 
 // NewPolicy creates a Policy from template.
@@ -32,8 +31,8 @@ func NewPolicy(template Policy) (Policy, error) {
 
 // policy is used as an alias in Policy.UnmarshalJSON.
 type policy struct {
-	SignatureThreshold int               `json:"ospkg_signature_threshold"`
-	FetchMethod        ospkg.FetchMethod `json:"ospkg_fetch_method"`
+	SignatureThreshold int         `json:"ospkg_signature_threshold"`
+	FetchMethod        FetchMethod `json:"ospkg_fetch_method"`
 }
 
 // UnmarshalJSON implements json.Unmarshaler. It initializes p from a JSON data
@@ -59,6 +58,20 @@ func (p *Policy) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
+func ReadTrustPolicy(filename string) (Policy, error) {
+	f, err := os.Open(filename)
+	if err != nil {
+		return Policy{}, fmt.Errorf("opening trust policy failed: %w", err)
+	}
+	defer f.Close()
+
+	trustPolicy := Policy{}
+	if err := json.NewDecoder(f).Decode(&trustPolicy); err != nil {
+		return Policy{}, err
+	}
+	return trustPolicy, nil
+}
+
 func (p *Policy) validate() error {
 	var validationSet = []func() error{
 		p.checkOSPKGSignatureThreshold,
diff --git a/trust/policy_test.go b/trust/policy_test.go
index 0169e4d1faf26ac52883ddd2caa4db76ea6f714d..7a8a5657d94366a064931550033936db92f2bd5d 100644
--- a/trust/policy_test.go
+++ b/trust/policy_test.go
@@ -5,8 +5,6 @@ import (
 	"errors"
 	"reflect"
 	"testing"
-
-	"system-transparency.org/stboot/ospkg"
 )
 
 func TestPolicyNew(t *testing.T) {
@@ -19,11 +17,11 @@ func TestPolicyNew(t *testing.T) {
 			name: "All set",
 			template: Policy{
 				SignatureThreshold: 1,
-				FetchMethod:        ospkg.FetchFromNetwork,
+				FetchMethod:        FetchFromNetwork,
 			},
 			want: Policy{
 				SignatureThreshold: 1,
-				FetchMethod:        ospkg.FetchFromNetwork,
+				FetchMethod:        FetchFromNetwork,
 			},
 		},
 	}
@@ -39,21 +37,21 @@ func TestPolicyNew(t *testing.T) {
 		{
 			name: "SignaturesThreshold missing",
 			template: Policy{
-				FetchMethod: ospkg.FetchFromNetwork,
+				FetchMethod: FetchFromNetwork,
 			},
 		},
 		{
 			name: "SignaturesThreshold 0",
 			template: Policy{
 				SignatureThreshold: 0,
-				FetchMethod:        ospkg.FetchFromNetwork,
+				FetchMethod:        FetchFromNetwork,
 			},
 		},
 		{
 			name: "SignaturesThreshold negative",
 			template: Policy{
 				SignatureThreshold: -1,
-				FetchMethod:        ospkg.FetchFromNetwork,
+				FetchMethod:        FetchFromNetwork,
 			},
 		},
 		{
@@ -110,7 +108,7 @@ func TestPolicyUnmarshalJSON(t *testing.T) {
 			}`,
 			want: Policy{
 				SignatureThreshold: 1,
-				FetchMethod:        ospkg.FetchFromNetwork,
+				FetchMethod:        FetchFromNetwork,
 			},
 		},
 		{
@@ -122,7 +120,7 @@ func TestPolicyUnmarshalJSON(t *testing.T) {
 			}`,
 			want: Policy{
 				SignatureThreshold: 1,
-				FetchMethod:        ospkg.FetchFromNetwork,
+				FetchMethod:        FetchFromNetwork,
 			},
 		},
 	}
@@ -217,3 +215,40 @@ func TestPolicyUnmarshalJSON(t *testing.T) {
 		})
 	}
 }
+
+func TestReadTrustPolicy(t *testing.T) {
+	tests := []struct {
+		name, file string
+		wantErr    bool
+	}{
+		{
+			name: "Successful loading",
+			file: "testdata/trust_policy_good_all_set.json",
+		},
+		{
+			name:    "Empty",
+			file:    "testdata/empty",
+			wantErr: true,
+		},
+		{
+			name:    "Bad content",
+			file:    "testdata/trust_policy_bad_unset.json",
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			_, err := ReadTrustPolicy(tt.file)
+
+			if tt.wantErr {
+				if err != nil {
+					t.Logf("%s: err (expected): %v", tt.name, err)
+				} else {
+					t.Errorf("%s: invalid input, but no error", tt.name)
+				}
+			} else if err != nil {
+				t.Errorf("%s: failed: %v", tt.name, err)
+			}
+		})
+	}
+}
diff --git a/trust/root.go b/trust/root.go
new file mode 100644
index 0000000000000000000000000000000000000000..499054559ec04bafb47b559039af24ec19df59b5
--- /dev/null
+++ b/trust/root.go
@@ -0,0 +1,56 @@
+package trust
+
+import (
+	"crypto/x509"
+	"fmt"
+	"path/filepath"
+	"time"
+
+	"filippo.io/age"
+
+	"system-transparency.org/stboot/opts"
+)
+
+const (
+	trustPolicyFile = "trust_policy.json"
+	signingRootFile = "ospkg_signing_root.pem"
+	httpsRootsFile  = "tls_roots.pem"
+	// Age decryption identities (optional).
+	decryptionIdentitiesFile = "decryption_identities"
+)
+
+type Root struct {
+	Policy               Policy
+	SigningRootCert      *x509.Certificate
+	HTTPSRootCerts       []*x509.Certificate
+	DecryptionIdentities []age.Identity
+}
+
+func ReadTrustRoot(dir string, now time.Time) (Root, error) {
+	trustPolicy, err := ReadTrustPolicy(filepath.Join(dir, trustPolicyFile))
+	if err != nil {
+		return Root{}, err
+	}
+
+	signingRootCerts, err := opts.ReadCertsFile(filepath.Join(dir, signingRootFile), now)
+	if err != nil {
+		return Root{}, err
+	}
+	if got := len(signingRootCerts); got != 1 {
+		return Root{}, fmt.Errorf("exactly one signing root certificate is expected, got %d", got)
+	}
+	httpsRoots, err := opts.ReadOptionalCertsFile(filepath.Join(dir, httpsRootsFile), now)
+	if err != nil {
+		return Root{}, err
+	}
+	decryptionIdentities, err := opts.ReadDecryptionIdentities(filepath.Join(dir, decryptionIdentitiesFile))
+	if err != nil {
+		return Root{}, fmt.Errorf("decryption identities: %v", err)
+	}
+	return Root{
+		Policy:               trustPolicy,
+		SigningRootCert:      signingRootCerts[0],
+		HTTPSRootCerts:       httpsRoots,
+		DecryptionIdentities: decryptionIdentities,
+	}, nil
+}
diff --git a/opts/testdata/trust_policy_bad_unset.json b/trust/testdata/trust_policy_bad_unset.json
similarity index 100%
rename from opts/testdata/trust_policy_bad_unset.json
rename to trust/testdata/trust_policy_bad_unset.json
diff --git a/opts/testdata/trust_policy_good_all_set.json b/trust/testdata/trust_policy_good_all_set.json
similarity index 100%
rename from opts/testdata/trust_policy_good_all_set.json
rename to trust/testdata/trust_policy_good_all_set.json