-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathvoucher.go
587 lines (534 loc) · 20.3 KB
/
voucher.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
580
581
582
583
584
585
586
587
// SPDX-FileCopyrightText: (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache 2.0
package fdo
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/hmac"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"errors"
"fmt"
"hash"
"slices"
"github.com/fido-device-onboard/go-fdo/cbor"
"github.com/fido-device-onboard/go-fdo/cose"
"github.com/fido-device-onboard/go-fdo/protocol"
)
// ErrCryptoVerifyFailed indicates that the wrapping error originated from a
// case of cryptographic verification failing rather than a broken invariant.
var ErrCryptoVerifyFailed = errors.New("cryptographic verification failed")
// Voucher is the top level structure.
//
// OwnershipVoucher = [
// OVProtVer: protver, ;; protocol version
// OVHeaderTag: bstr .cbor OVHeader,
// OVHeaderHMac: HMac, ;; hmac[DCHmacSecret, OVHeader]
// OVDevCertChain: OVDevCertChainOrNull,
// OVEntryArray: OVEntries
// ]
//
// ;; Device certificate chain
// ;; use null for Intel® EPID.
// OVDevCertChainOrNull = X5CHAIN / null ;; CBOR null for Intel® EPID device key
//
// ;; Ownership voucher entries array
// OVEntries = [ * OVEntry ]
type Voucher struct {
Version uint16
Header cbor.Bstr[VoucherHeader]
Hmac protocol.Hmac
CertChain *[]*cbor.X509Certificate
Entries []cose.Sign1Tag[VoucherEntryPayload, []byte]
}
// VoucherHeader is the Ownership Voucher header, also used in TO1 protocol.
//
// OVHeader = [
// OVHProtVer: protver, ;; protocol version
// OVGuid: Guid, ;; guid
// OVRVInfo: RendezvousInfo, ;; rendezvous instructions
// OVDeviceInfo: tstr, ;; DeviceInfo
// OVPubKey: PublicKey, ;; mfg public key
// OVDevCertChainHash:OVDevCertChainHashOrNull
// ]
//
// ;; Hash of Device certificate chain
// ;; use null for Intel® EPID
// OVDevCertChainHashOrNull = Hash / null ;; CBOR null for Intel® EPID device key
type VoucherHeader struct {
Version uint16
GUID protocol.GUID
RvInfo [][]protocol.RvInstruction
DeviceInfo string
ManufacturerKey protocol.PublicKey
CertChainHash *protocol.Hash
}
// Equal compares two ownership voucher headers for equality.
//
//nolint:gocyclo
func (ovh *VoucherHeader) Equal(otherOVH *VoucherHeader) bool {
if ovh.Version != otherOVH.Version {
return false
}
if !bytes.Equal(ovh.GUID[:], otherOVH.GUID[:]) {
return false
}
if !slices.EqualFunc(ovh.RvInfo, otherOVH.RvInfo, func(directivesA, directivesB []protocol.RvInstruction) bool {
return slices.EqualFunc(directivesA, directivesB, func(instA, instB protocol.RvInstruction) bool {
return instA.Variable == instB.Variable && bytes.Equal(instA.Value, instB.Value)
})
}) {
return false
}
if ovh.DeviceInfo != otherOVH.DeviceInfo {
return false
}
if ovh.ManufacturerKey.Type != otherOVH.ManufacturerKey.Type {
return false
}
if ovh.ManufacturerKey.Encoding != otherOVH.ManufacturerKey.Encoding {
return false
}
if !bytes.Equal(ovh.ManufacturerKey.Body, otherOVH.ManufacturerKey.Body) {
return false
}
if (ovh.CertChainHash == nil && otherOVH.CertChainHash != nil) || (ovh.CertChainHash != nil && otherOVH.CertChainHash == nil) {
return false
}
if ovh.CertChainHash != nil {
if ovh.CertChainHash.Algorithm != otherOVH.CertChainHash.Algorithm {
return false
}
if !bytes.Equal(ovh.CertChainHash.Value, otherOVH.CertChainHash.Value) {
return false
}
}
return true
}
// VoucherEntryPayload is an entry in a voucher's list of recorded transfers.
//
// ;; ...each entry is a COSE Sign1 object with a payload
// OVEntry = CoseSignature
// $COSEProtectedHeaders //= (
//
// 1: OVSignType
//
// )
// $COSEPayloads /= (
//
// OVEntryPayload
//
// )
// ;; ... each payload contains the hash of the previous entry
// ;; and the signature of the public key to verify the next signature
// ;; (or the Owner, in the last entry).
// OVEntryPayload = [
//
// OVEHashPrevEntry: Hash,
// OVEHashHdrInfo: Hash, ;; hash[GUID||DeviceInfo] in header
// OVEExtra: null / bstr .cbor OVEExtraInfo
// OVEPubKey: PublicKey
//
// ]
//
// OVEExtraInfo = { * OVEExtraInfoType: bstr }
// OVEExtraInfoType = int
//
// ;;OVSignType = Supporting COSE signature types
type VoucherEntryPayload struct {
PreviousHash protocol.Hash
HeaderHash protocol.Hash
Extra *cbor.Bstr[map[int][]byte]
PublicKey protocol.PublicKey
}
func (v *Voucher) shallowClone() *Voucher {
return &Voucher{
Version: v.Version,
Header: *cbor.NewBstr(VoucherHeader{
Version: v.Header.Val.Version,
GUID: v.Header.Val.GUID,
RvInfo: v.Header.Val.RvInfo,
DeviceInfo: v.Header.Val.DeviceInfo,
ManufacturerKey: v.Header.Val.ManufacturerKey,
CertChainHash: v.Header.Val.CertChainHash,
}),
Hmac: v.Hmac,
CertChain: v.CertChain,
Entries: v.Entries,
}
}
// DevicePublicKey extracts the device's public key from from the certificate
// chain. Before calling this method, the voucher must be fully verified. For
// certain key types, such as Intel EPID, the public key will be nil.
func (v *Voucher) DevicePublicKey() (crypto.PublicKey, error) {
if v.CertChain == nil {
return nil, nil
}
if len(*v.CertChain) == 0 {
return nil, errors.New("empty cert chain")
}
return (*v.CertChain)[0].PublicKey, nil
}
// OwnerPublicKey extracts the voucher owner's public key from either the
// header or the entries list.
func (v *Voucher) OwnerPublicKey() (crypto.PublicKey, error) {
if len(v.Entries) == 0 {
return v.Header.Val.ManufacturerKey.Public()
}
return v.Entries[len(v.Entries)-1].Payload.Val.PublicKey.Public()
}
// VerifyHeader checks that the OVHeader was not modified by comparing the HMAC
// generated using the secret from the device credentials.
func (v *Voucher) VerifyHeader(hmacSha256, hmacSha384 hash.Hash) error {
return hmacVerify(hmacSha256, hmacSha384, v.Hmac, &v.Header.Val)
}
// VerifyDeviceCertChain using trusted roots. If roots is nil then the last
// certificate in the chain will be implicitly trusted.
func (v *Voucher) VerifyDeviceCertChain(roots *x509.CertPool) error {
if v.CertChain == nil {
return nil
}
if len(*v.CertChain) == 0 {
return errors.New("empty cert chain")
}
chain := make([]*x509.Certificate, len(*v.CertChain))
for i, cert := range *v.CertChain {
chain[i] = (*x509.Certificate)(cert)
}
return verifyCertChain(chain, roots)
}
// VerifyCertChainHash uses the hash in the voucher header to verify that the
// certificate chain of the voucher has not been tampered with. This method
// should therefore not be called before VerifyHeader.
func (v *Voucher) VerifyCertChainHash() error {
switch {
case v.CertChain == nil && v.Header.Val.CertChainHash == nil:
return nil
case v.CertChain == nil || v.Header.Val.CertChainHash == nil:
return errors.New("device cert chain and hash must both be present or both be absent")
}
cchash := v.Header.Val.CertChainHash
digest := cchash.Algorithm.HashFunc().New()
for _, cert := range *v.CertChain {
if _, err := digest.Write(cert.Raw); err != nil {
return fmt.Errorf("error computing hash: %w", err)
}
}
if !hmac.Equal(digest.Sum(nil), cchash.Value) {
return fmt.Errorf("%w: certificate chain hash did not match", ErrCryptoVerifyFailed)
}
return nil
}
// VerifyManufacturerKey by using a public key hash (generally stored as part
// of the device credential).
func (v *Voucher) VerifyManufacturerKey(keyHash protocol.Hash) error {
var digest hash.Hash
switch keyHash.Algorithm {
case protocol.Sha256Hash:
digest = sha256.New()
case protocol.Sha384Hash:
digest = sha512.New384()
default:
return fmt.Errorf("unsupported hash algorithm for hashing manufacturer public key: %s", keyHash.Algorithm)
}
if err := cbor.NewEncoder(digest).Encode(&v.Header.Val.ManufacturerKey); err != nil {
return fmt.Errorf("error computing hash of manufacturer public key: %w", err)
}
if !hmac.Equal(digest.Sum(nil), keyHash.Value) {
return fmt.Errorf("%w: manufacturer public key hash did not match", ErrCryptoVerifyFailed)
}
return nil
}
// VerifyManufacturerCertChain using trusted roots. If roots is nil then the
// last certificate in the chain will be implicitly trusted.
//
// If the manufacturer public key is X509 encoded rather than X5Chain, then
// this method will fail if a non-nil root certificate pool is given.
func (v *Voucher) VerifyManufacturerCertChain(roots *x509.CertPool) error {
chain, err := v.Header.Val.ManufacturerKey.Chain()
if err != nil {
return fmt.Errorf("error parsing manufacturer public key: %w", err)
}
if chain == nil {
if roots == nil {
return nil
}
return fmt.Errorf("manufacturer public key could not be verified against given roots, because it was not an X5Chain")
}
return verifyCertChain(chain, roots)
}
// VerifyEntries checks the chain of signatures on each voucher entry payload.
func (v *Voucher) VerifyEntries() error {
// Parse the public key from the voucher header
mfgPubKey, err := v.Header.Val.ManufacturerKey.Public()
if err != nil {
return fmt.Errorf("error parsing manufacturer public key: %w", err)
}
// Voucher may have never been extended since manufacturing
if len(v.Entries) == 0 {
return nil
}
// Header info is the concatenation of GUID and DeviceInfo
headerInfo := append(v.Header.Val.GUID[:], []byte(v.Header.Val.DeviceInfo)...)
// The algorithm used for hashing entries should always match the one used
// during the very first extension
alg := v.Entries[0].Payload.Val.PreviousHash.Algorithm
var initialHash hash.Hash
var headerInfoHash []byte
switch alg {
case protocol.Sha256Hash:
initialHash = sha256.New()
sum := sha256.Sum256(headerInfo)
headerInfoHash = sum[:]
case protocol.Sha384Hash:
initialHash = sha512.New384()
sum := sha512.Sum384(headerInfo)
headerInfoHash = sum[:]
default:
return fmt.Errorf("unsupported hash algorithm for hashing initial previous hash of entry list: %s", alg)
}
// For entry 0, the previous hash is computed on OVHeader||OVHeaderHMac
if err := cbor.NewEncoder(initialHash).Encode(&v.Header.Val); err != nil {
return fmt.Errorf("error computing initial entry hash, writing encoded header: %w", err)
}
if err := cbor.NewEncoder(initialHash).Encode(v.Hmac); err != nil {
return fmt.Errorf("error computing initial entry hash, writing encoded header hmac: %w", err)
}
// Validate all entries
return validateNextEntry(mfgPubKey, alg, initialHash, headerInfoHash, 0, v.Entries)
}
// Validate each entry recursively
func validateNextEntry(prevOwnerKey crypto.PublicKey, alg protocol.HashAlg, prevHash hash.Hash, headerInfoHash []byte, i int, entries []cose.Sign1Tag[VoucherEntryPayload, []byte]) error {
entry := entries[0].Untag()
// Check payload has a valid COSE signature from the previous owner key
if ok, err := entry.Verify(prevOwnerKey, nil, nil); err != nil {
return fmt.Errorf("COSE signature for entry %d could not be verified: %w", i, err)
} else if !ok {
return fmt.Errorf("%w: COSE signature for entry %d did not match previous owner key", ErrCryptoVerifyFailed, i)
}
// Check payload's HeaderHash matches voucher header as hash[GUID||DeviceInfo]
headerHash := entry.Payload.Val.HeaderHash
if headerHash.Algorithm != alg {
return fmt.Errorf("%w: voucher entry payload %d header hash was computed with %s instead of %s",
ErrCryptoVerifyFailed, i-1, headerHash.Algorithm, alg)
}
if !hmac.Equal(headerHash.Value, headerInfoHash) {
return fmt.Errorf("%w: voucher entry payload %d header hash did not match", ErrCryptoVerifyFailed, i-1)
}
// Check payload's PreviousHash matches the previous entry
if !hmac.Equal(prevHash.Sum(nil), entry.Payload.Val.PreviousHash.Value) {
return fmt.Errorf("%w: voucher entry payload %d previous hash did not match", ErrCryptoVerifyFailed, i-1)
}
// Succeed if no more entries
if len(entries[1:]) == 0 {
return nil
}
// Parse owner key for next iteration
ownerKey, err := entry.Payload.Val.PublicKey.Public()
if err != nil {
return fmt.Errorf("error parsing public key of entry %d: %w", i-1, err)
}
// Hash payload for next iteration
prevHash.Reset()
if err := cbor.NewEncoder(prevHash).Encode(entry.Tag()); err != nil {
return fmt.Errorf("error computing hash of voucher entry payload: %w", err)
}
// Validate the next entry recursively
return validateNextEntry(ownerKey, alg, prevHash, headerInfoHash, i+1, entries[1:])
}
// VerifyOwnerCertChain validates the certificate chain of the owner public key
// using trusted roots. If roots is nil then the last certificate in the chain
// will be implicitly trusted. If the public key is X509 encoded rather than
// X5Chain, then this method will fail if a non-nil root certificate pool is
// given.
func (e *VoucherEntryPayload) VerifyOwnerCertChain(roots *x509.CertPool) error {
chain, err := e.PublicKey.Chain()
if err != nil {
return fmt.Errorf("error parsing voucher entry's owner public key: %w", err)
}
if chain == nil {
if roots == nil {
return nil
}
return fmt.Errorf("voucher entry's owner public key could not be verified against given roots, because it was not an X5Chain")
}
return verifyCertChain(chain, roots)
}
func verifyCertChain(chain []*x509.Certificate, roots *x509.CertPool) error {
// All all intermediates (if any) to a pool
intermediates := x509.NewCertPool()
if len(chain) > 2 {
for _, cert := range chain[1 : len(chain)-1] {
intermediates.AddCert(cert)
}
}
// Trust last certificate in chain if roots is nil
if roots == nil {
roots = x509.NewCertPool()
roots.AddCert(chain[len(chain)-1])
}
// Return the result of (*x509.Certificate).Verify
if _, err := chain[0].Verify(x509.VerifyOptions{
Roots: roots,
Intermediates: intermediates,
}); err != nil {
return fmt.Errorf("%w: %w", ErrCryptoVerifyFailed, err)
}
return nil
}
// ExtendVoucher adds a new signed voucher entry to the list and returns the
// new extended voucher. Vouchers should be treated as immutable structures.
//
// ExtraInfo may be used to pass additional supply-chain information along with
// the Ownership Voucher. The Device implicitly verifies the plaintext of
// OVEExtra along with the verification of the Ownership Voucher. An Owner
// which trusts the Device' verification of the Ownership Voucher may also
// choose to trust OVEExtra.
func ExtendVoucher[T protocol.PublicKeyOrChain](v *Voucher, owner crypto.Signer, nextOwner T, extra map[int][]byte) (*Voucher, error) { //nolint:gocyclo
// This performs a shallow clone, which allows arrays, maps, and pointers
// to have their contents modified and both the original and copied voucher
// will see the modification. However, this function does not perform a
// deep copy/clone of the voucher, because vouchers are generally not used
// as mutable entities. Every reference type in a voucher - keys, device
// certificate chain, etc. - is protected by some other signature or hash,
// so it doesn't make sense to modify.
xv := v.shallowClone()
// Each key in the Ownership Voucher must copy the public key type from the
// manufacturer’s key in OVHeader.OVPubKey, hash, and encoding (e.g., all
// RSA2048RESTR, all RSAPKCS 3072, all ECDSA secp256r1 or all ECDSA
// secp384r1). This restriction permits a Device with limited crypto
// capabilities to verify all the signatures.
ownerPubKey := owner.Public()
switch ownerPub := ownerPubKey.(type) {
case *ecdsa.PublicKey:
if mfgKey, err := v.Header.Val.ManufacturerKey.Public(); err != nil {
return nil, fmt.Errorf("error parsing manufacturer key from header: %w", err)
} else if mfgPubKey, ok := mfgKey.(*ecdsa.PublicKey); !ok {
return nil, fmt.Errorf("owner key for voucher extension did not match the type of the manufacturer key")
} else if mfgPubKey.Curve != ownerPub.Curve {
return nil, fmt.Errorf("owner key for voucher extension did not match the type and size/curve of the manufacturer key")
}
case *rsa.PublicKey:
if mfgKey, err := v.Header.Val.ManufacturerKey.Public(); err != nil {
return nil, fmt.Errorf("error parsing manufacturer key from header: %w", err)
} else if mfgPubKey, ok := mfgKey.(*rsa.PublicKey); !ok {
return nil, fmt.Errorf("owner key for voucher extension did not match the type of the manufacturer key")
} else if mfgPubKey.Size() != ownerPub.Size() {
return nil, fmt.Errorf("owner key for voucher extension did not match the type and size/curve of the manufacturer key")
}
default:
return nil, fmt.Errorf("unsupported key type: %T", ownerPub)
}
// Owner key must match the last signature
expectedOwnerPubKey, err := v.OwnerPublicKey()
if err != nil {
return nil, fmt.Errorf("error getting owner public key of voucher to extend: %w", err)
}
if !ownerPubKey.(interface {
Equal(x crypto.PublicKey) bool
}).Equal(expectedOwnerPubKey) {
return nil, fmt.Errorf("owner key for signing does not match the last signature of the voucher to be extended")
}
// Create the next owner PublicKey structure
asCOSE := v.Header.Val.ManufacturerKey.Encoding == protocol.CoseKeyEnc
if _, ok := any(nextOwner).([]*x509.Certificate); ok {
asCOSE = false
}
nextOwnerPublicKey, err := protocol.NewPublicKey(v.Header.Val.ManufacturerKey.Type, nextOwner, asCOSE)
if err != nil {
return nil, fmt.Errorf("error marshaling next owner public key: %w", err)
}
// Select the appropriate hash algorithm
devicePubKey := (*v.CertChain)[0].PublicKey
alg, err := hashAlgFor(devicePubKey, ownerPubKey)
if err != nil {
return nil, fmt.Errorf("error selecting the appropriate hash algorithm: %w", err)
}
// Calculate the hash of the voucher header info
headerInfo := append(v.Header.Val.GUID[:], []byte(v.Header.Val.DeviceInfo)...)
digest := alg.HashFunc().New()
_, _ = digest.Write(headerInfo)
headerHash := protocol.Hash{Algorithm: alg, Value: digest.Sum(nil)}
// Calculate the hash of the previous entry
digest.Reset()
if len(v.Entries) == 0 {
// For entry 0, the previous hash is computed on OVHeader||OVHeaderHMac
if err := cbor.NewEncoder(digest).Encode(&v.Header.Val); err != nil {
return nil, fmt.Errorf("error computing initial entry hash, writing encoded header: %w", err)
}
if err := cbor.NewEncoder(digest).Encode(v.Hmac); err != nil {
return nil, fmt.Errorf("error computing initial entry hash, writing encoded header hmac: %w", err)
}
} else {
if err := cbor.NewEncoder(digest).Encode(v.Entries[len(v.Entries)-1].Tag()); err != nil {
return nil, fmt.Errorf("error computing hash of voucher entry payload: %w", err)
}
}
prevHash := protocol.Hash{Algorithm: alg, Value: digest.Sum(nil)}
// Create and sign next entry
usePSS := v.Header.Val.ManufacturerKey.Type == protocol.RsaPssKeyType
entry, err := newSignedEntry(owner, usePSS, VoucherEntryPayload{
PreviousHash: prevHash,
HeaderHash: headerHash,
Extra: cbor.NewBstr(extra),
PublicKey: *nextOwnerPublicKey,
})
if err != nil {
return nil, err
}
xv.Entries = append(xv.Entries, *entry)
return xv, nil
}
// hashAlgFor determines the appropriate hash algorithm to use based on the
// table in section 3.2.2 of the FDO spec
func hashAlgFor(devicePubKey, ownerPubKey crypto.PublicKey) (protocol.HashAlg, error) {
deviceSize, err := hashSizeForPubKey(devicePubKey)
if err != nil {
return 0, fmt.Errorf("device attestation key: %w", err)
}
ownerSize, err := hashSizeForPubKey(ownerPubKey)
if err != nil {
return 0, fmt.Errorf("owner attestation key: %w", err)
}
switch min(deviceSize, ownerSize) {
case 256:
return protocol.Sha256Hash, nil
case 384:
return protocol.Sha384Hash, nil
default:
panic("only hash sizes of 256 and 384 are included in FDO")
}
}
func hashSizeForPubKey(pubKey crypto.PublicKey) (int, error) {
switch key := pubKey.(type) {
case *ecdsa.PublicKey:
switch curve := key.Curve; curve {
case elliptic.P256():
return 256, nil
case elliptic.P384():
return 384, nil
default:
return 0, fmt.Errorf("unsupported elliptic curve: %s", curve.Params().Name)
}
case *rsa.PublicKey:
return key.Size(), nil
default:
return 0, fmt.Errorf("unsupported key type: %T", key)
}
}
func newSignedEntry(owner crypto.Signer, usePSS bool, payload VoucherEntryPayload) (*cose.Sign1Tag[VoucherEntryPayload, []byte], error) {
var entry cose.Sign1Tag[VoucherEntryPayload, []byte]
entry.Payload = cbor.NewByteWrap(payload)
signOpts, err := signOptsFor(owner, usePSS)
if err != nil {
return nil, err
}
if err := entry.Sign(owner, nil, nil, signOpts); err != nil {
return nil, fmt.Errorf("error signing voucher entry payload: %w", err)
}
return &entry, nil
}