-
Notifications
You must be signed in to change notification settings - Fork 33
/
pkcs11helpers.go
579 lines (505 loc) · 17.3 KB
/
pkcs11helpers.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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
/*
Copyright The ocicrypt Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pkcs11
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"hash"
"os"
"strconv"
"strings"
"github.com/miekg/pkcs11"
"github.com/pkg/errors"
pkcs11uri "github.com/stefanberger/go-pkcs11uri"
"gopkg.in/yaml.v2"
)
var (
// OAEPLabel defines the label we use for OAEP encryption; this cannot be changed
OAEPLabel = []byte("")
// OAEPDefaultHash defines the default hash used for OAEP encryption; this cannot be changed
OAEPDefaultHash = "sha1"
// OAEPSha1Params describes the OAEP parameters with sha1 hash algorithm; needed by SoftHSM
OAEPSha1Params = &pkcs11.OAEPParams{
HashAlg: pkcs11.CKM_SHA_1,
MGF: pkcs11.CKG_MGF1_SHA1,
SourceType: pkcs11.CKZ_DATA_SPECIFIED,
SourceData: OAEPLabel,
}
// OAEPSha256Params describes the OAEP parameters with sha256 hash algorithm
OAEPSha256Params = &pkcs11.OAEPParams{
HashAlg: pkcs11.CKM_SHA256,
MGF: pkcs11.CKG_MGF1_SHA256,
SourceType: pkcs11.CKZ_DATA_SPECIFIED,
SourceData: OAEPLabel,
}
)
// Pkcs11KeyFile describes the format of the pkcs11 (private) key file.
// It also carries pkcs11 module related environment variables that are transferred to the
// Pkcs11URI object and activated when the pkcs11 module is used.
type Pkcs11KeyFile struct {
Pkcs11 struct {
Uri string `yaml:"uri"`
} `yaml:"pkcs11"`
Module struct {
Env map[string]string `yaml:"env,omitempty"`
} `yaml:"module"`
}
// Pkcs11KeyFileObject is a representation of the Pkcs11KeyFile with the pkcs11 URI as an object
type Pkcs11KeyFileObject struct {
Uri *pkcs11uri.Pkcs11URI
}
// ParsePkcs11Uri parses a pkcs11 URI
func ParsePkcs11Uri(uri string) (*pkcs11uri.Pkcs11URI, error) {
p11uri := pkcs11uri.New()
err := p11uri.Parse(uri)
if err != nil {
return nil, errors.Wrapf(err, "Could not parse Pkcs11URI from file")
}
return p11uri, err
}
// ParsePkcs11KeyFile parses a pkcs11 key file holding a pkcs11 URI describing a private key.
// The file has the following yaml format:
// pkcs11:
// - uri : <pkcs11 uri>
// An error is returned if the pkcs11 URI is malformed
func ParsePkcs11KeyFile(yamlstr []byte) (*Pkcs11KeyFileObject, error) {
p11keyfile := Pkcs11KeyFile{}
err := yaml.Unmarshal([]byte(yamlstr), &p11keyfile)
if err != nil {
return nil, errors.Wrapf(err, "Could not unmarshal pkcs11 keyfile")
}
p11uri, err := ParsePkcs11Uri(p11keyfile.Pkcs11.Uri)
if err != nil {
return nil, err
}
p11uri.SetEnvMap(p11keyfile.Module.Env)
return &Pkcs11KeyFileObject{Uri: p11uri}, err
}
// IsPkcs11PrivateKey checks whether the given YAML represents a Pkcs11 private key
func IsPkcs11PrivateKey(yamlstr []byte) bool {
_, err := ParsePkcs11KeyFile(yamlstr)
return err == nil
}
// IsPkcs11PublicKey checks whether the given YAML represents a Pkcs11 public key
func IsPkcs11PublicKey(yamlstr []byte) bool {
_, err := ParsePkcs11KeyFile(yamlstr)
return err == nil
}
// Pkcs11Config describes the layout of a pkcs11 config file
// The file has the following yaml format:
// module-directories:
// - /usr/lib64/pkcs11/
// allowd-module-paths
// - /usr/lib64/pkcs11/libsofthsm2.so
type Pkcs11Config struct {
ModuleDirectories []string `yaml:"module-directories"`
AllowedModulePaths []string `yaml:"allowed-module-paths"`
}
// GetDefaultModuleDirectories returns module directories covering
// a variety of Linux distros
func GetDefaultModuleDirectories() []string {
dirs := []string{
"/usr/lib64/pkcs11/", // Fedora,RHEL,openSUSE
"/usr/lib/pkcs11/", // Fedora,ArchLinux
"/usr/local/lib/pkcs11/",
"/usr/lib/softhsm/", // Debian,Ubuntu
}
// Debian directory: /usr/lib/(x86_64|aarch64|arm|powerpc64le|s390x)-linux-gnu/
hosttype, ostype, q := getHostAndOsType()
if len(hosttype) > 0 {
dir := fmt.Sprintf("/usr/lib/%s-%s-%s/", hosttype, ostype, q)
dirs = append(dirs, dir)
}
return dirs
}
// GetDefaultModuleDirectoresFormatted returns the default module directories formatted for YAML
func GetDefaultModuleDirectoriesYaml(indent string) string {
res := ""
for _, dir := range GetDefaultModuleDirectories() {
res += indent + "- " + dir + "\n"
}
return res
}
// ParsePkcs11ConfigFile parses a pkcs11 config file hat influences the module search behavior
// as well as the set of modules that users are allowed to use
func ParsePkcs11ConfigFile(yamlstr []byte) (*Pkcs11Config, error) {
p11conf := Pkcs11Config{}
err := yaml.Unmarshal([]byte(yamlstr), &p11conf)
if err != nil {
return &p11conf, errors.Wrapf(err, "Could not parse Pkcs11Config")
}
return &p11conf, nil
}
// rsaPublicEncryptOAEP encrypts the given plaintext with the given *rsa.PublicKey; the
// environment variable OCICRYPT_OAEP_HASHALG can be set to 'sha1' to force usage of sha1 for OAEP (SoftHSM).
// This function is needed by clients who are using a public key file for pkcs11 encryption
func rsaPublicEncryptOAEP(pubKey *rsa.PublicKey, plaintext []byte) ([]byte, string, error) {
var (
hashfunc hash.Hash
hashalg string
)
oaephash := os.Getenv("OCICRYPT_OAEP_HASHALG")
// The default is 'sha1'
switch strings.ToLower(oaephash) {
case "sha1", "":
hashfunc = sha1.New()
hashalg = "sha1"
case "sha256":
hashfunc = sha256.New()
hashalg = "sha256"
default:
return nil, "", errors.Errorf("Unsupported OAEP hash '%s'", oaephash)
}
ciphertext, err := rsa.EncryptOAEP(hashfunc, rand.Reader, pubKey, plaintext, OAEPLabel)
if err != nil {
return nil, "", errors.Wrapf(err, "rss.EncryptOAEP failed")
}
return ciphertext, hashalg, nil
}
// pkcs11UriGetLoginParameters gets the parameters necessary for login from the Pkcs11URI
// PIN and module are mandatory; slot-id is optional and if not found -1 will be returned
// For a privateKeyOperation a PIN is required and if none is given, this function will return an error
func pkcs11UriGetLoginParameters(p11uri *pkcs11uri.Pkcs11URI, privateKeyOperation bool) (string, string, int64, error) {
var (
pin string
err error
)
if privateKeyOperation {
if !p11uri.HasPIN() {
return "", "", 0, errors.New("Missing PIN for private key operation")
}
}
// some devices require a PIN to find a *public* key object, others don't
pin, _ = p11uri.GetPIN()
module, err := p11uri.GetModule()
if err != nil {
return "", "", 0, errors.Wrap(err, "No module available in pkcs11 URI")
}
slotid := int64(-1)
slot, ok := p11uri.GetPathAttribute("slot-id", false)
if ok {
slotid, err = strconv.ParseInt(slot, 10, 64)
if err != nil {
return "", "", 0, errors.Wrap(err, "slot-id is not a valid number")
}
if slotid < 0 {
return "", "", 0, fmt.Errorf("slot-id is a negative number")
}
if uint64(slotid) > 0xffffffff {
return "", "", 0, fmt.Errorf("slot-id is larger than 32 bit")
}
}
return pin, module, slotid, nil
}
// pkcs11UriGetKeyIdAndLabel gets the key label by retrieving the value of the 'object' attribute
func pkcs11UriGetKeyIdAndLabel(p11uri *pkcs11uri.Pkcs11URI) (string, string, error) {
keyid, ok2 := p11uri.GetPathAttribute("id", false)
label, ok1 := p11uri.GetPathAttribute("object", false)
if !ok1 && !ok2 {
return "", "", errors.New("Neither 'id' nor 'object' attributes were found in pkcs11 URI")
}
return keyid, label, nil
}
// pkcs11OpenSession opens a session with a pkcs11 device at the given slot and logs in with the given PIN
func pkcs11OpenSession(p11ctx *pkcs11.Ctx, slotid uint, pin string) (session pkcs11.SessionHandle, err error) {
session, err = p11ctx.OpenSession(uint(slotid), pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
if err != nil {
return 0, errors.Wrapf(err, "OpenSession to slot %d failed", slotid)
}
if len(pin) > 0 {
err = p11ctx.Login(session, pkcs11.CKU_USER, pin)
if err != nil {
_ = p11ctx.CloseSession(session)
return 0, errors.Wrap(err, "Could not login to device")
}
}
return session, nil
}
// pkcs11UriLogin uses the given pkcs11 URI to select the pkcs11 module (share libary) and to get
// the PIN to use for login; if the URI contains a slot-id, the given slot-id will be used, otherwise
// one slot after the other will be attempted and the first one where login succeeds will be used
func pkcs11UriLogin(p11uri *pkcs11uri.Pkcs11URI, privateKeyOperation bool) (ctx *pkcs11.Ctx, session pkcs11.SessionHandle, err error) {
pin, module, slotid, err := pkcs11UriGetLoginParameters(p11uri, privateKeyOperation)
if err != nil {
return nil, 0, err
}
p11ctx := pkcs11.New(module)
if p11ctx == nil {
return nil, 0, errors.New("Please check module path, input is: " + module)
}
err = p11ctx.Initialize()
if err != nil {
p11Err := err.(pkcs11.Error)
if p11Err != pkcs11.CKR_CRYPTOKI_ALREADY_INITIALIZED {
return nil, 0, errors.Wrap(err, "Initialize failed")
}
}
if slotid >= 0 {
session, err := pkcs11OpenSession(p11ctx, uint(slotid), pin)
return p11ctx, session, err
} else {
slots, err := p11ctx.GetSlotList(true)
if err != nil {
return nil, 0, errors.Wrap(err, "GetSlotList failed")
}
tokenlabel, ok := p11uri.GetPathAttribute("token", false)
if !ok {
return nil, 0, errors.New("Missing 'token' attribute since 'slot-id' was not given")
}
for _, slot := range slots {
ti, err := p11ctx.GetTokenInfo(slot)
if err != nil || ti.Label != tokenlabel {
continue
}
session, err = pkcs11OpenSession(p11ctx, slot, pin)
if err == nil {
return p11ctx, session, err
}
}
if len(pin) > 0 {
return nil, 0, errors.New("Could not create session to any slot and/or log in")
}
return nil, 0, errors.New("Could not create session to any slot")
}
}
func pkcs11Logout(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) {
_ = ctx.Logout(session)
_ = ctx.CloseSession(session)
_ = ctx.Finalize()
ctx.Destroy()
}
// findObject finds an object of the given class with the given keyid and/or label
func findObject(p11ctx *pkcs11.Ctx, session pkcs11.SessionHandle, class uint, keyid, label string) (pkcs11.ObjectHandle, error) {
msg := ""
template := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, class),
}
if len(label) > 0 {
template = append(template, pkcs11.NewAttribute(pkcs11.CKA_LABEL, label))
msg = fmt.Sprintf("label '%s'", label)
}
if len(keyid) > 0 {
template = append(template, pkcs11.NewAttribute(pkcs11.CKA_ID, keyid))
if len(msg) > 0 {
msg += " and "
}
msg += fmt.Sprintf("id '%s'", keyid)
}
if err := p11ctx.FindObjectsInit(session, template); err != nil {
return 0, errors.Wrap(err, "FindObjectsInit failed")
}
obj, _, err := p11ctx.FindObjects(session, 100)
if err != nil {
return 0, errors.Wrap(err, "FindObjects failed")
}
if err := p11ctx.FindObjectsFinal(session); err != nil {
return 0, errors.Wrap(err, "FindObjectsFinal failed")
}
if len(obj) > 1 {
return 0, errors.Errorf("There are too many (=%d) keys with %s", len(obj), msg)
} else if len(obj) == 1 {
return obj[0], nil
}
return 0, errors.Errorf("Could not find any object with %s", msg)
}
// publicEncryptOAEP uses a public key described by a pkcs11 URI to OAEP encrypt the given plaintext
func publicEncryptOAEP(pubKey *Pkcs11KeyFileObject, plaintext []byte) ([]byte, string, error) {
oldenv, err := setEnvVars(pubKey.Uri.GetEnvMap())
if err != nil {
return nil, "", err
}
defer restoreEnv(oldenv)
p11ctx, session, err := pkcs11UriLogin(pubKey.Uri, false)
if err != nil {
return nil, "", err
}
defer pkcs11Logout(p11ctx, session)
keyid, label, err := pkcs11UriGetKeyIdAndLabel(pubKey.Uri)
if err != nil {
return nil, "", err
}
p11PubKey, err := findObject(p11ctx, session, pkcs11.CKO_PUBLIC_KEY, keyid, label)
if err != nil {
return nil, "", err
}
var hashalg string
var oaep *pkcs11.OAEPParams
oaephash := os.Getenv("OCICRYPT_OAEP_HASHALG")
// the default is sha1
switch strings.ToLower(oaephash) {
case "sha1", "":
oaep = OAEPSha1Params
hashalg = "sha1"
case "sha256":
oaep = OAEPSha256Params
hashalg = "sha256"
default:
return nil, "", errors.Errorf("Unsupported OAEP hash '%s'", oaephash)
}
err = p11ctx.EncryptInit(session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_OAEP, oaep)}, p11PubKey)
if err != nil {
return nil, "", errors.Wrap(err, "EncryptInit error")
}
ciphertext, err := p11ctx.Encrypt(session, plaintext)
if err != nil {
return nil, "", errors.Wrap(err, "Encrypt failed")
}
return ciphertext, hashalg, nil
}
// privateDecryptOAEP uses a pkcs11 URI describing a private key to OAEP decrypt a ciphertext
func privateDecryptOAEP(privKeyObj *Pkcs11KeyFileObject, ciphertext []byte, hashalg string) ([]byte, error) {
oldenv, err := setEnvVars(privKeyObj.Uri.GetEnvMap())
if err != nil {
return nil, err
}
defer restoreEnv(oldenv)
p11ctx, session, err := pkcs11UriLogin(privKeyObj.Uri, true)
if err != nil {
return nil, err
}
defer pkcs11Logout(p11ctx, session)
keyid, label, err := pkcs11UriGetKeyIdAndLabel(privKeyObj.Uri)
if err != nil {
return nil, err
}
p11PrivKey, err := findObject(p11ctx, session, pkcs11.CKO_PRIVATE_KEY, keyid, label)
if err != nil {
return nil, err
}
var oaep *pkcs11.OAEPParams
// the default is sha1
switch hashalg {
case "sha1", "":
oaep = OAEPSha1Params
case "sha256":
oaep = OAEPSha256Params
default:
return nil, errors.Errorf("Unsupported hash algorithm '%s' for decryption", hashalg)
}
err = p11ctx.DecryptInit(session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_OAEP, oaep)}, p11PrivKey)
if err != nil {
return nil, errors.Wrapf(err, "DecryptInit failed")
}
plaintext, err := p11ctx.Decrypt(session, ciphertext)
if err != nil {
return nil, errors.Wrapf(err, "Decrypt failed")
}
return plaintext, err
}
//
// The following part deals with the JSON formatted message for multiple pkcs11 recipients
//
// Pkcs11Blob holds the encrypted blobs for all recipients; this is what we will put into the image's annotations
type Pkcs11Blob struct {
Recipients []Pkcs11Recipient `json:"recipients"`
}
// Pkcs11Recipient holds the b64-encoded and encrypted blob for a particular recipient
type Pkcs11Recipient struct {
Blob string `json:"blob"`
Hash string `json:"hash,omitempty"`
}
// EncryptMultiple encrypts for one or multiple pkcs11 devices; the public keys passed to this function
// may either be *rsa.PublicKey or *pkcs11uri.Pkcs11URI; the returned byte array is a JSON string of the
// following format:
// {
// recipients: [ // recipient list
// {
// "blob": <base64 encoded RSA OAEP encrypted blob>
// "hash": <hash used for OAEP other than 'sha256'>
// } ,
// {
// "blob": <base64 encoded RSA OAEP encrypted blob>
// "hash": <hash used for OAEP other than 'sha256'>
// } ,
// [...]
// ]
// }
func EncryptMultiple(pubKeys []interface{}, data []byte) ([]byte, error) {
var (
ciphertext []byte
err error
pkcs11blob Pkcs11Blob = Pkcs11Blob{}
hashalg string
)
for _, pubKey := range pubKeys {
switch pkey := pubKey.(type) {
case *rsa.PublicKey:
ciphertext, hashalg, err = rsaPublicEncryptOAEP(pkey, data)
case *Pkcs11KeyFileObject:
ciphertext, hashalg, err = publicEncryptOAEP(pkey, data)
default:
err = errors.Errorf("Unsupported key object type for pkcs11 public key")
}
if err != nil {
return nil, err
}
if hashalg == OAEPDefaultHash {
hashalg = ""
}
recipient := Pkcs11Recipient{
Blob: base64.StdEncoding.EncodeToString(ciphertext),
Hash: hashalg,
}
pkcs11blob.Recipients = append(pkcs11blob.Recipients, recipient)
}
return json.Marshal(&pkcs11blob)
}
// Decrypt tries to decrypt one of the recipients' blobs using a pkcs11 private key.
// The input pkcs11blobstr is a string with the following format:
// {
// recipients: [ // recipient list
// {
// "blob": <base64 encoded RSA OAEP encrypted blob>
// "hash": <hash used for OAEP other than 'sha256'>
// } ,
// {
// "blob": <base64 encoded RSA OAEP encrypted blob>
// "hash": <hash used for OAEP other than 'sha256'>
// } ,
// [...]
// }
func Decrypt(privKeyObjs []*Pkcs11KeyFileObject, pkcs11blobstr []byte) ([]byte, error) {
pkcs11blob := Pkcs11Blob{}
err := json.Unmarshal(pkcs11blobstr, &pkcs11blob)
if err != nil {
return nil, errors.Wrapf(err, "Could not parse Pkcs11Blob")
}
// since we do trial and error, collect all encountered errors
errs := ""
for _, recipient := range pkcs11blob.Recipients {
ciphertext, err := base64.StdEncoding.DecodeString(recipient.Blob)
if err != nil || len(ciphertext) == 0 {
// This should never happen... we skip over decoding issues
errs += fmt.Sprintf("Base64 decoding failed: %s\n", err)
continue
}
// try all keys until one works
for _, privKeyObj := range privKeyObjs {
plaintext, err := privateDecryptOAEP(privKeyObj, ciphertext, recipient.Hash)
if err == nil {
return plaintext, nil
}
if uri, err2 := privKeyObj.Uri.Format(); err2 == nil {
errs += fmt.Sprintf("%s : %s\n", uri, err)
} else {
errs += fmt.Sprintf("%s\n", err)
}
}
}
return nil, errors.Errorf("Could not find a pkcs11 key for decryption:\n%s", errs)
}