File: truststore.go

package info (click to toggle)
golang-github-smallstep-truststore 0.12.1-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 188 kB
  • sloc: makefile: 30; sh: 21
file content (247 lines) | stat: -rw-r--r-- 5,731 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// Copyright (c) 2018 The truststore Authors. All rights reserved.

package truststore

import (
	"bytes"
	"crypto/x509"
	"encoding/pem"
	"io"
	"log"
	"os"
)

var prefix = ""
var enableDebug bool

func debug(format string, args ...interface{}) {
	if enableDebug {
		log.Printf(format, args...)
	}
}

// Trust is the interface that non-system trustores implement to add and remove
// a certificate on its trustore. Right now we there are two implementations of
// trust NSS (Firefox) and Java.
type Trust interface {
	Name() string
	Install(filename string, cert *x509.Certificate) error
	Uninstall(filename string, cert *x509.Certificate) error
	Exists(cert *x509.Certificate) bool
	PreCheck() error
}

// Install installs the given certificate into the system truststore, and
// optionally to the Firefox and Java trustores.
func Install(cert *x509.Certificate, opts ...Option) error {
	filename, fn, err := saveTempCert(cert)
	defer fn()
	if err != nil {
		return err
	}
	return installCertificate(filename, cert, opts)
}

// InstallFile will read the certificate in the given file and install it to the
// system truststore, and optionally to the Firefox and Java truststores.
func InstallFile(filename string, opts ...Option) error {
	cert, err := ReadCertificate(filename)
	if err != nil {
		return err
	}
	return installCertificate(filename, cert, opts)
}

func installCertificate(filename string, cert *x509.Certificate, opts []Option) error {
	o := newOptions(opts)

	for _, t := range o.trusts {
		if err := t.PreCheck(); err != nil {
			debug(err.Error())
			continue
		}
		if !t.Exists(cert) {
			if err := t.Install(filename, cert); err != nil {
				return err
			}
		}
	}

	if o.withNoSystem {
		return nil
	}

	return installPlatform(filename, cert)
}

// Uninstall removes the given certificate from the system truststore, and
// optionally from the Firefox and Java truststres.
func Uninstall(cert *x509.Certificate, opts ...Option) error {
	filename, fn, err := saveTempCert(cert)
	defer fn()
	if err != nil {
		return err
	}
	return uninstallCertificate(filename, cert, opts)
}

// UninstallFile reads the certificate in the given file and removes it from the
// system truststore, and optionally to the Firefox and Java truststores.
func UninstallFile(filename string, opts ...Option) error {
	cert, err := ReadCertificate(filename)
	if err != nil {
		return err
	}
	return uninstallCertificate(filename, cert, opts)
}

func uninstallCertificate(filename string, cert *x509.Certificate, opts []Option) error {
	o := newOptions(opts)

	for _, t := range o.trusts {
		if err := t.PreCheck(); err != nil {
			debug(err.Error())
			continue
		}
		if err := t.Uninstall(filename, cert); err != nil {
			return err
		}
	}

	if o.withNoSystem {
		return nil
	}

	return uninstallPlatform(filename, cert)
}

// ReadCertificate reads a certificate file and returns a x509.Certificate struct.
func ReadCertificate(filename string) (*x509.Certificate, error) {
	b, err := os.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	// PEM format
	if bytes.HasPrefix(b, []byte("-----BEGIN ")) {
		b, err = os.ReadFile(filename)
		if err != nil {
			return nil, err
		}

		block, _ := pem.Decode(b)
		if block == nil || block.Type != "CERTIFICATE" {
			return nil, ErrInvalidCertificate
		}
		b = block.Bytes
	}

	// DER format (binary)
	crt, err := x509.ParseCertificate(b)
	return crt, wrapError(err, "error parsing "+filename)
}

// SaveCertificate saves the given x509.Certificate with the given filename.
func SaveCertificate(filename string, cert *x509.Certificate) error {
	block := &pem.Block{
		Type:  "CERTIFICATE",
		Bytes: cert.Raw,
	}
	return os.WriteFile(filename, pem.EncodeToMemory(block), 0600)
}

type options struct {
	withNoSystem bool
	trusts       map[string]Trust
}

func newOptions(opts []Option) *options {
	o := &options{
		trusts: make(map[string]Trust),
	}

	for _, fn := range opts {
		fn(o)
	}
	return o
}

// Option is the type used to pass custom options.
type Option func(*options)

// WithTrust enables the given trust.
func WithTrust(t Trust) Option {
	return func(o *options) {
		o.trusts[t.Name()] = t
	}
}

// WithJava enables the install or uninstall of a certificate in the Java
// truststore.
func WithJava() Option {
	t, _ := NewJavaTrust()
	return WithTrust(t)
}

// WithFirefox enables the install or uninstall of a certificate in the Firefox
// truststore.
func WithFirefox() Option {
	t, _ := NewNSSTrust()
	return WithTrust(t)
}

// WithNoSystem disables the install or uninstall of a certificate in the system
// truststore.
func WithNoSystem() Option {
	return func(o *options) {
		o.withNoSystem = true
	}
}

// WithDebug enables debug logging messages.
func WithDebug() Option {
	return func(o *options) {
		enableDebug = true
	}
}

// WithPrefix sets a custom prefix for the truststore name.
func WithPrefix(s string) Option {
	return func(o *options) {
		prefix = s
	}
}

func uniqueName(cert *x509.Certificate) string {
	switch {
	case prefix != "":
		return prefix + cert.SerialNumber.String()
	case cert.Subject.CommonName != "":
		return cert.Subject.CommonName + " " + cert.SerialNumber.String()
	default:
		return "Truststore Development CA " + cert.SerialNumber.String()
	}
}

func saveTempCert(cert *x509.Certificate) (string, func(), error) {
	f, err := os.CreateTemp(os.TempDir(), "truststore.*.pem")
	if err != nil {
		return "", func() {}, err
	}
	name := f.Name()
	clean := func() {
		os.Remove(name)
	}
	data := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: cert.Raw,
	})
	n, err := f.Write(data)
	if err == nil && n < len(data) {
		err = io.ErrShortWrite
	}
	if err1 := f.Close(); err == nil {
		err = err1
	}
	return name, clean, err
}