-
Notifications
You must be signed in to change notification settings - Fork 6
/
signatures.go
305 lines (276 loc) · 9.39 KB
/
signatures.go
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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
package signatures
import (
"bytes"
"crypto/x509"
"encoding/hex"
"errors"
"fmt"
iofs "io/fs"
"os"
"github.com/edsrzf/mmap-go"
"github.com/foxboron/go-uefi/authenticode"
"github.com/foxboron/go-uefi/efi"
"github.com/foxboron/go-uefi/efi/signature"
"github.com/foxboron/go-uefi/efi/util"
"github.com/foxboron/go-uefi/pkcs7"
"github.com/kairos-io/kairos-sdk/types"
peparser "github.com/saferwall/pe"
)
// GetKeyDatabase returns a single signature.SignatureDatabase for a given type
func GetKeyDatabase(sigType string) (*signature.SignatureDatabase, error) {
var err error
var sig *signature.SignatureDatabase
switch sigType {
case "PK", "pk":
sig, err = efi.GetPK()
case "KEK", "kek":
sig, err = efi.GetKEK()
case "DB", "db":
sig, err = efi.Getdb()
default:
return nil, fmt.Errorf("signature type unkown (%s). Valid signature types are PK,KEK,DB", sigType)
}
return sig, err
}
// GetAllFullCerts returns a list of certs in the system. Full cert, including raw data of the cert
func GetAllFullCerts() (types.CertListFull, error) {
var certList types.CertListFull
pk, err := GetKeyDatabase("PK")
if err != nil {
return certList, err
}
kek, err := GetKeyDatabase("KEK")
if err != nil {
return certList, err
}
db, err := GetKeyDatabase("DB")
if err != nil {
return certList, err
}
certList.PK = ExtractCertsFromSignatureDatabase(pk)
certList.KEK = ExtractCertsFromSignatureDatabase(kek)
certList.DB = ExtractCertsFromSignatureDatabase(db)
return certList, nil
}
// ExtractCertsFromSignatureDatabase returns a []*x509.Certificate from a *signature.SignatureDatabase
func ExtractCertsFromSignatureDatabase(database *signature.SignatureDatabase) []*x509.Certificate {
var result []*x509.Certificate
for _, k := range *database {
if isValidSignature(k.SignatureType) {
for _, k1 := range k.Signatures {
// Note the S at the end of the function, we are parsing multiple certs, not just one
certificates, err := x509.ParseCertificates(k1.Data)
if err != nil {
continue
}
result = append(result, certificates...)
}
}
}
return result
}
// GetAllCerts returns a list of certs in the system
func GetAllCerts() (types.CertList, error) {
var certList types.CertList
pk, err := GetKeyDatabase("PK")
if err != nil {
return certList, err
}
kek, err := GetKeyDatabase("KEK")
if err != nil {
return certList, err
}
db, err := GetKeyDatabase("DB")
if err != nil {
return certList, err
}
for _, k := range *pk {
if isValidSignature(k.SignatureType) {
for _, k1 := range k.Signatures {
// Note the S at the end of the function, we are parsing multiple certs, not just one
certificates, err := x509.ParseCertificates(k1.Data)
if err != nil {
continue
}
for _, cert := range certificates {
certList.PK = append(certList.PK, types.CertDetail{Owner: cert.Subject, Issuer: cert.Issuer})
}
}
}
}
for _, k := range *kek {
if isValidSignature(k.SignatureType) {
for _, k1 := range k.Signatures {
// Note the S at the end of the function, we are parsing multiple certs, not just one
certificates, err := x509.ParseCertificates(k1.Data)
if err != nil {
continue
}
for _, cert := range certificates {
certList.KEK = append(certList.KEK, types.CertDetail{Owner: cert.Subject, Issuer: cert.Issuer})
}
}
}
}
for _, k := range *db {
if isValidSignature(k.SignatureType) {
for _, k1 := range k.Signatures {
// Note the S at the end of the function, we are parsing multiple certs, not just one
certificates, err := x509.ParseCertificates(k1.Data)
if err != nil {
continue
}
for _, cert := range certificates {
certList.DB = append(certList.DB, types.CertDetail{Owner: cert.Subject, Issuer: cert.Issuer})
}
}
}
}
return certList, nil
}
// isValidSignature identifies a signature based as a DER-encoded X.509 certificate
func isValidSignature(sign util.EFIGUID) bool {
return sign == signature.CERT_X509_GUID
}
// CheckArtifactSignatureIsValid checks that a given efi artifact is signed properly with a signature that would allow it to
// boot correctly in the current node if secureboot is enabled
func CheckArtifactSignatureIsValid(fs types.KairosFS, artifact string, logger types.KairosLogger) error {
var err error
logger.Logger.Info().Str("what", artifact).Msg("Checking artifact for valid signature")
info, err := fs.Stat(artifact)
if errors.Is(err, os.ErrNotExist) {
logger.Warnf("%s does not exist", artifact)
return fmt.Errorf("%s does not exist", artifact)
} else if errors.Is(err, os.ErrPermission) {
logger.Warnf("%s permission denied. Can't read file", artifact)
return fmt.Errorf("%s permission denied. Can't read file", artifact)
} else if err != nil {
return err
}
if info.Size() == 0 {
logger.Warnf("%s file is empty denied", artifact)
return fmt.Errorf("%s file has zero size", artifact)
}
logger.Logger.Debug().Str("what", artifact).Msg("Reading artifact")
// MMAP the file, seems to save memory rather than reading the full file
// Unfortunately we have to do some type conversion to keep using the v1.Fs
f, err := fs.Open(artifact)
defer func(f iofs.File) {
_ = f.Close()
}(f)
if err != nil {
return err
}
// type conversion, ugh
fOS := f.(*os.File)
data, err := mmap.Map(fOS, mmap.RDONLY, 0)
defer func(data *mmap.MMap) {
_ = data.Unmap()
}(&data)
if err != nil {
return err
}
// Get sha256 of the artifact
// Note that this is a PEFile, so it's a bit different from a normal file as there are some sections that need to be
// excluded when calculating the sha
logger.Logger.Debug().Str("what", artifact).Msg("Parsing PE artifact")
file, _ := peparser.NewBytes(data, &peparser.Options{Fast: true})
err = file.Parse()
if err != nil {
logger.Logger.Error().Err(err).Msg("parsing PE file for hash")
return err
}
logger.Logger.Debug().Str("what", artifact).Msg("Checking if its an EFI file")
// Check for proper header in the efi file
if file.DOSHeader.Magic != peparser.ImageDOSZMSignature && file.DOSHeader.Magic != peparser.ImageDOSSignature {
logger.Error(fmt.Errorf("no pe file header: %d", file.DOSHeader.Magic))
return fmt.Errorf("no pe file header: %d", file.DOSHeader.Magic)
}
// Get hash to compare in dbx if we have hashes
hashArtifact := hex.EncodeToString(file.Authentihash())
logger.Logger.Debug().Str("what", artifact).Msg("Getting DB certs")
// We need to read the current db database to have the proper certs to check against
db, err := efi.Getdb()
if err != nil {
logger.Logger.Error().Err(err).Msg("Getting DB certs")
return err
}
dbCerts := ExtractCertsFromSignatureDatabase(db)
logger.Logger.Debug().Str("what", artifact).Msg("Getting signatures from artifact")
// Get signatures from the artifact
binary, err := authenticode.Parse(bytes.NewReader(data))
if err != nil {
return fmt.Errorf("%s: %w", artifact, err)
}
if binary.Datadir.Size == 0 {
return fmt.Errorf("no signatures in the file %s", artifact)
}
sigs, err := binary.Signatures()
if err != nil {
return fmt.Errorf("%s: %w", artifact, err)
}
logger.Logger.Debug().Str("what", artifact).Msg("Getting DBX certs")
dbx, err := efi.Getdbx()
if err != nil {
logger.Logger.Error().Err(err).Msg("getting DBX certs")
return err
}
// First check the dbx database as it has precedence, on match, return immediately
for _, k := range *dbx {
switch k.SignatureType {
case signature.CERT_SHA256_GUID: // SHA256 hash
// Compare it against the dbx
for _, k1 := range k.Signatures {
shaSign := hex.EncodeToString(k1.Data)
logger.Logger.Debug().Str("artifact", string(hashArtifact)).Str("signature", shaSign).Msg("Comparing hashes")
if hashArtifact == shaSign {
return fmt.Errorf("hash appears on DBX: %s", hashArtifact)
}
}
case signature.CERT_X509_GUID: // Certificate
var result []*x509.Certificate
for _, k1 := range k.Signatures {
certificates, err := x509.ParseCertificates(k1.Data)
if err != nil {
continue
}
result = append(result, certificates...)
}
for _, sig := range sigs {
for _, cert := range result {
logger.Logger.Debug().Str("what", artifact).Str("subject", cert.Subject.CommonName).Msg("checking signature")
p, err := pkcs7.ParsePKCS7(sig.Certificate)
if err != nil {
logger.Logger.Info().Str("error", err.Error()).Msg("parsing signature")
return err
}
ok, _ := p.Verify(cert)
// If cert matches then it means its blacklisted so return error
if ok {
return fmt.Errorf("artifact is signed with a blacklisted cert")
}
}
}
default:
logger.Logger.Debug().Str("what", artifact).Str("cert type", string(signature.ValidEFISignatureSchemes[k.SignatureType])).Msg("not supported type of cert")
}
}
// Now check against the DB to see if its allowed
for _, sig := range sigs {
for _, cert := range dbCerts {
logger.Logger.Debug().Str("what", artifact).Str("subject", cert.Subject.CommonName).Msg("checking signature")
p, err := pkcs7.ParsePKCS7(sig.Certificate)
if err != nil {
logger.Logger.Info().Str("error", err.Error()).Msg("parsing signature")
return err
}
ok, _ := p.Verify(cert)
if ok {
logger.Logger.Info().Str("what", artifact).Str("subject", cert.Subject.CommonName).Msg("verified")
return nil
}
}
}
// If we reach this point, we need to fail as we haven't matched anything, so default is to fail
return fmt.Errorf("could not find a signature in EFIVars DB that matches the artifact")
}