podman

Форк
0
678 строк · 20.1 Кб
1
// Copyright 2019 The Go Authors. All rights reserved.
2
// Use of this source code is governed by a BSD-style
3
// license that can be found in the LICENSE file.
4

5
// Package note defines the notes signed by the Go module database server.
6
//
7
// A note is text signed by one or more server keys.
8
// The text should be ignored unless the note is signed by
9
// a trusted server key and the signature has been verified
10
// using the server's public key.
11
//
12
// A server's public key is identified by a name, typically the "host[/path]"
13
// giving the base URL of the server's transparency log.
14
// The syntactic restrictions on a name are that it be non-empty,
15
// well-formed UTF-8 containing neither Unicode spaces nor plus (U+002B).
16
//
17
// A Go module database server signs texts using public key cryptography.
18
// A given server may have multiple public keys, each
19
// identified by a 32-bit hash of the public key.
20
//
21
// # Verifying Notes
22
//
23
// A [Verifier] allows verification of signatures by one server public key.
24
// It can report the name of the server and the uint32 hash of the key,
25
// and it can verify a purported signature by that key.
26
//
27
// The standard implementation of a Verifier is constructed
28
// by [NewVerifier] starting from a verifier key, which is a
29
// plain text string of the form "<name>+<hash>+<keydata>".
30
//
31
// A [Verifiers] allows looking up a Verifier by the combination
32
// of server name and key hash.
33
//
34
// The standard implementation of a Verifiers is constructed
35
// by VerifierList from a list of known verifiers.
36
//
37
// A [Note] represents a text with one or more signatures.
38
// An implementation can reject a note with too many signatures
39
// (for example, more than 100 signatures).
40
//
41
// A [Signature] represents a signature on a note, verified or not.
42
//
43
// The [Open] function takes as input a signed message
44
// and a set of known verifiers. It decodes and verifies
45
// the message signatures and returns a [Note] structure
46
// containing the message text and (verified or unverified) signatures.
47
//
48
// # Signing Notes
49
//
50
// A [Signer] allows signing a text with a given key.
51
// It can report the name of the server and the hash of the key
52
// and can sign a raw text using that key.
53
//
54
// The standard implementation of a Signer is constructed
55
// by [NewSigner] starting from an encoded signer key, which is a
56
// plain text string of the form "PRIVATE+KEY+<name>+<hash>+<keydata>".
57
// Anyone with an encoded signer key can sign messages using that key,
58
// so it must be kept secret. The encoding begins with the literal text
59
// "PRIVATE+KEY" to avoid confusion with the public server key.
60
//
61
// The [Sign] function takes as input a Note and a list of Signers
62
// and returns an encoded, signed message.
63
//
64
// # Signed Note Format
65
//
66
// A signed note consists of a text ending in newline (U+000A),
67
// followed by a blank line (only a newline),
68
// followed by one or more signature lines of this form:
69
// em dash (U+2014), space (U+0020),
70
// server name, space, base64-encoded signature, newline.
71
//
72
// Signed notes must be valid UTF-8 and must not contain any
73
// ASCII control characters (those below U+0020) other than newline.
74
//
75
// A signature is a base64 encoding of 4+n bytes.
76
//
77
// The first four bytes in the signature are the uint32 key hash
78
// stored in big-endian order.
79
//
80
// The remaining n bytes are the result of using the specified key
81
// to sign the note text (including the final newline but not the
82
// separating blank line).
83
//
84
// # Generating Keys
85
//
86
// There is only one key type, Ed25519 with algorithm identifier 1.
87
// New key types may be introduced in the future as needed,
88
// although doing so will require deploying the new algorithms to all clients
89
// before starting to depend on them for signatures.
90
//
91
// The [GenerateKey] function generates and returns a new signer
92
// and corresponding verifier.
93
//
94
// # Example
95
//
96
// Here is a well-formed signed note:
97
//
98
//	If you think cryptography is the answer to your problem,
99
//	then you don't know what your problem is.
100
//
101
//	— PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=
102
//
103
// It can be constructed and displayed using:
104
//
105
//	skey := "PRIVATE+KEY+PeterNeumann+c74f20a3+AYEKFALVFGyNhPJEMzD1QIDr+Y7hfZx09iUvxdXHKDFz"
106
//	text := "If you think cryptography is the answer to your problem,\n" +
107
//		"then you don't know what your problem is.\n"
108
//
109
//	signer, err := note.NewSigner(skey)
110
//	if err != nil {
111
//		log.Fatal(err)
112
//	}
113
//
114
//	msg, err := note.Sign(&note.Note{Text: text}, signer)
115
//	if err != nil {
116
//		log.Fatal(err)
117
//	}
118
//	os.Stdout.Write(msg)
119
//
120
// The note's text is two lines, including the final newline,
121
// and the text is purportedly signed by a server named
122
// "PeterNeumann". (Although server names are canonically
123
// base URLs, the only syntactic requirement is that they
124
// not contain spaces or newlines).
125
//
126
// If [Open] is given access to a [Verifiers] including the
127
// [Verifier] for this key, then it will succeed at verifying
128
// the encoded message and returning the parsed [Note]:
129
//
130
//	vkey := "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW"
131
//	msg := []byte("If you think cryptography is the answer to your problem,\n" +
132
//		"then you don't know what your problem is.\n" +
133
//		"\n" +
134
//		"— PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=\n")
135
//
136
//	verifier, err := note.NewVerifier(vkey)
137
//	if err != nil {
138
//		log.Fatal(err)
139
//	}
140
//	verifiers := note.VerifierList(verifier)
141
//
142
//	n, err := note.Open([]byte(msg), verifiers)
143
//	if err != nil {
144
//		log.Fatal(err)
145
//	}
146
//	fmt.Printf("%s (%08x):\n%s", n.Sigs[0].Name, n.Sigs[0].Hash, n.Text)
147
//
148
// You can add your own signature to this message by re-signing the note:
149
//
150
//	skey, vkey, err := note.GenerateKey(rand.Reader, "EnochRoot")
151
//	if err != nil {
152
//		log.Fatal(err)
153
//	}
154
//	_ = vkey // give to verifiers
155
//
156
//	me, err := note.NewSigner(skey)
157
//	if err != nil {
158
//		log.Fatal(err)
159
//	}
160
//
161
//	msg, err := note.Sign(n, me)
162
//	if err != nil {
163
//		log.Fatal(err)
164
//	}
165
//	os.Stdout.Write(msg)
166
//
167
// This will print a doubly-signed message, like:
168
//
169
//	If you think cryptography is the answer to your problem,
170
//	then you don't know what your problem is.
171
//
172
//	— PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=
173
//	— EnochRoot rwz+eBzmZa0SO3NbfRGzPCpDckykFXSdeX+MNtCOXm2/5n2tiOHp+vAF1aGrQ5ovTG01oOTGwnWLox33WWd1RvMc+QQ=
174
package note
175

176
import (
177
	"bytes"
178
	"crypto/ed25519"
179
	"crypto/sha256"
180
	"encoding/base64"
181
	"encoding/binary"
182
	"errors"
183
	"fmt"
184
	"io"
185
	"strconv"
186
	"strings"
187
	"unicode"
188
	"unicode/utf8"
189
)
190

191
// A Verifier verifies messages signed with a specific key.
192
type Verifier interface {
193
	// Name returns the server name associated with the key.
194
	Name() string
195

196
	// KeyHash returns the key hash.
197
	KeyHash() uint32
198

199
	// Verify reports whether sig is a valid signature of msg.
200
	Verify(msg, sig []byte) bool
201
}
202

203
// A Signer signs messages using a specific key.
204
type Signer interface {
205
	// Name returns the server name associated with the key.
206
	Name() string
207

208
	// KeyHash returns the key hash.
209
	KeyHash() uint32
210

211
	// Sign returns a signature for the given message.
212
	Sign(msg []byte) ([]byte, error)
213
}
214

215
// keyHash computes the key hash for the given server name and encoded public key.
216
func keyHash(name string, key []byte) uint32 {
217
	h := sha256.New()
218
	h.Write([]byte(name))
219
	h.Write([]byte("\n"))
220
	h.Write(key)
221
	sum := h.Sum(nil)
222
	return binary.BigEndian.Uint32(sum)
223
}
224

225
var (
226
	errVerifierID   = errors.New("malformed verifier id")
227
	errVerifierAlg  = errors.New("unknown verifier algorithm")
228
	errVerifierHash = errors.New("invalid verifier hash")
229
)
230

231
const (
232
	algEd25519 = 1
233
)
234

235
// isValidName reports whether name is valid.
236
// It must be non-empty and not have any Unicode spaces or pluses.
237
func isValidName(name string) bool {
238
	return name != "" && utf8.ValidString(name) && strings.IndexFunc(name, unicode.IsSpace) < 0 && !strings.Contains(name, "+")
239
}
240

241
// NewVerifier construct a new [Verifier] from an encoded verifier key.
242
func NewVerifier(vkey string) (Verifier, error) {
243
	name, vkey := chop(vkey, "+")
244
	hash16, key64 := chop(vkey, "+")
245
	hash, err1 := strconv.ParseUint(hash16, 16, 32)
246
	key, err2 := base64.StdEncoding.DecodeString(key64)
247
	if len(hash16) != 8 || err1 != nil || err2 != nil || !isValidName(name) || len(key) == 0 {
248
		return nil, errVerifierID
249
	}
250
	if uint32(hash) != keyHash(name, key) {
251
		return nil, errVerifierHash
252
	}
253

254
	v := &verifier{
255
		name: name,
256
		hash: uint32(hash),
257
	}
258

259
	alg, key := key[0], key[1:]
260
	switch alg {
261
	default:
262
		return nil, errVerifierAlg
263

264
	case algEd25519:
265
		if len(key) != 32 {
266
			return nil, errVerifierID
267
		}
268
		v.verify = func(msg, sig []byte) bool {
269
			return ed25519.Verify(key, msg, sig)
270
		}
271
	}
272

273
	return v, nil
274
}
275

276
// chop chops s at the first instance of sep, if any,
277
// and returns the text before and after sep.
278
// If sep is not present, chop returns before is s and after is empty.
279
func chop(s, sep string) (before, after string) {
280
	i := strings.Index(s, sep)
281
	if i < 0 {
282
		return s, ""
283
	}
284
	return s[:i], s[i+len(sep):]
285
}
286

287
// verifier is a trivial Verifier implementation.
288
type verifier struct {
289
	name   string
290
	hash   uint32
291
	verify func([]byte, []byte) bool
292
}
293

294
func (v *verifier) Name() string                { return v.name }
295
func (v *verifier) KeyHash() uint32             { return v.hash }
296
func (v *verifier) Verify(msg, sig []byte) bool { return v.verify(msg, sig) }
297

298
// NewSigner constructs a new [Signer] from an encoded signer key.
299
func NewSigner(skey string) (Signer, error) {
300
	priv1, skey := chop(skey, "+")
301
	priv2, skey := chop(skey, "+")
302
	name, skey := chop(skey, "+")
303
	hash16, key64 := chop(skey, "+")
304
	hash, err1 := strconv.ParseUint(hash16, 16, 32)
305
	key, err2 := base64.StdEncoding.DecodeString(key64)
306
	if priv1 != "PRIVATE" || priv2 != "KEY" || len(hash16) != 8 || err1 != nil || err2 != nil || !isValidName(name) || len(key) == 0 {
307
		return nil, errSignerID
308
	}
309

310
	// Note: hash is the hash of the public key and we have the private key.
311
	// Must verify hash after deriving public key.
312

313
	s := &signer{
314
		name: name,
315
		hash: uint32(hash),
316
	}
317

318
	var pubkey []byte
319

320
	alg, key := key[0], key[1:]
321
	switch alg {
322
	default:
323
		return nil, errSignerAlg
324

325
	case algEd25519:
326
		if len(key) != 32 {
327
			return nil, errSignerID
328
		}
329
		key = ed25519.NewKeyFromSeed(key)
330
		pubkey = append([]byte{algEd25519}, key[32:]...)
331
		s.sign = func(msg []byte) ([]byte, error) {
332
			return ed25519.Sign(key, msg), nil
333
		}
334
	}
335

336
	if uint32(hash) != keyHash(name, pubkey) {
337
		return nil, errSignerHash
338
	}
339

340
	return s, nil
341
}
342

343
var (
344
	errSignerID   = errors.New("malformed verifier id")
345
	errSignerAlg  = errors.New("unknown verifier algorithm")
346
	errSignerHash = errors.New("invalid verifier hash")
347
)
348

349
// signer is a trivial Signer implementation.
350
type signer struct {
351
	name string
352
	hash uint32
353
	sign func([]byte) ([]byte, error)
354
}
355

356
func (s *signer) Name() string                    { return s.name }
357
func (s *signer) KeyHash() uint32                 { return s.hash }
358
func (s *signer) Sign(msg []byte) ([]byte, error) { return s.sign(msg) }
359

360
// GenerateKey generates a signer and verifier key pair for a named server.
361
// The signer key skey is private and must be kept secret.
362
func GenerateKey(rand io.Reader, name string) (skey, vkey string, err error) {
363
	pub, priv, err := ed25519.GenerateKey(rand)
364
	if err != nil {
365
		return "", "", err
366
	}
367
	pubkey := append([]byte{algEd25519}, pub...)
368
	privkey := append([]byte{algEd25519}, priv.Seed()...)
369
	h := keyHash(name, pubkey)
370

371
	skey = fmt.Sprintf("PRIVATE+KEY+%s+%08x+%s", name, h, base64.StdEncoding.EncodeToString(privkey))
372
	vkey = fmt.Sprintf("%s+%08x+%s", name, h, base64.StdEncoding.EncodeToString(pubkey))
373
	return skey, vkey, nil
374
}
375

376
// NewEd25519VerifierKey returns an encoded verifier key using the given name
377
// and Ed25519 public key.
378
func NewEd25519VerifierKey(name string, key ed25519.PublicKey) (string, error) {
379
	if len(key) != ed25519.PublicKeySize {
380
		return "", fmt.Errorf("invalid public key size %d, expected %d", len(key), ed25519.PublicKeySize)
381
	}
382

383
	pubkey := append([]byte{algEd25519}, key...)
384
	hash := keyHash(name, pubkey)
385

386
	b64Key := base64.StdEncoding.EncodeToString(pubkey)
387
	return fmt.Sprintf("%s+%08x+%s", name, hash, b64Key), nil
388
}
389

390
// A Verifiers is a collection of known verifier keys.
391
type Verifiers interface {
392
	// Verifier returns the Verifier associated with the key
393
	// identified by the name and hash.
394
	// If the name, hash pair is unknown, Verifier should return
395
	// an UnknownVerifierError.
396
	Verifier(name string, hash uint32) (Verifier, error)
397
}
398

399
// An UnknownVerifierError indicates that the given key is not known.
400
// The Open function records signatures without associated verifiers as
401
// unverified signatures.
402
type UnknownVerifierError struct {
403
	Name    string
404
	KeyHash uint32
405
}
406

407
func (e *UnknownVerifierError) Error() string {
408
	return fmt.Sprintf("unknown key %s+%08x", e.Name, e.KeyHash)
409
}
410

411
// An ambiguousVerifierError indicates that the given name and hash
412
// match multiple keys passed to [VerifierList].
413
// (If this happens, some malicious actor has taken control of the
414
// verifier list, at which point we may as well give up entirely,
415
// but we diagnose the problem instead.)
416
type ambiguousVerifierError struct {
417
	name string
418
	hash uint32
419
}
420

421
func (e *ambiguousVerifierError) Error() string {
422
	return fmt.Sprintf("ambiguous key %s+%08x", e.name, e.hash)
423
}
424

425
// VerifierList returns a [Verifiers] implementation that uses the given list of verifiers.
426
func VerifierList(list ...Verifier) Verifiers {
427
	m := make(verifierMap)
428
	for _, v := range list {
429
		k := nameHash{v.Name(), v.KeyHash()}
430
		m[k] = append(m[k], v)
431
	}
432
	return m
433
}
434

435
type nameHash struct {
436
	name string
437
	hash uint32
438
}
439

440
type verifierMap map[nameHash][]Verifier
441

442
func (m verifierMap) Verifier(name string, hash uint32) (Verifier, error) {
443
	v, ok := m[nameHash{name, hash}]
444
	if !ok {
445
		return nil, &UnknownVerifierError{name, hash}
446
	}
447
	if len(v) > 1 {
448
		return nil, &ambiguousVerifierError{name, hash}
449
	}
450
	return v[0], nil
451
}
452

453
// A Note is a text and signatures.
454
type Note struct {
455
	Text           string      // text of note
456
	Sigs           []Signature // verified signatures
457
	UnverifiedSigs []Signature // unverified signatures
458
}
459

460
// A Signature is a single signature found in a note.
461
type Signature struct {
462
	// Name and Hash give the name and key hash
463
	// for the key that generated the signature.
464
	Name string
465
	Hash uint32
466

467
	// Base64 records the base64-encoded signature bytes.
468
	Base64 string
469
}
470

471
// An UnverifiedNoteError indicates that the note
472
// successfully parsed but had no verifiable signatures.
473
type UnverifiedNoteError struct {
474
	Note *Note
475
}
476

477
func (e *UnverifiedNoteError) Error() string {
478
	return "note has no verifiable signatures"
479
}
480

481
// An InvalidSignatureError indicates that the given key was known
482
// and the associated Verifier rejected the signature.
483
type InvalidSignatureError struct {
484
	Name string
485
	Hash uint32
486
}
487

488
func (e *InvalidSignatureError) Error() string {
489
	return fmt.Sprintf("invalid signature for key %s+%08x", e.Name, e.Hash)
490
}
491

492
var (
493
	errMalformedNote      = errors.New("malformed note")
494
	errInvalidSigner      = errors.New("invalid signer")
495
	errMismatchedVerifier = errors.New("verifier name or hash doesn't match signature")
496

497
	sigSplit  = []byte("\n\n")
498
	sigPrefix = []byte("— ")
499
)
500

501
// Open opens and parses the message msg, checking signatures from the known verifiers.
502
//
503
// For each signature in the message, Open calls known.Verifier to find a verifier.
504
// If known.Verifier returns a verifier and the verifier accepts the signature,
505
// Open records the signature in the returned note's Sigs field.
506
// If known.Verifier returns a verifier but the verifier rejects the signature,
507
// Open returns an InvalidSignatureError.
508
// If known.Verifier returns an UnknownVerifierError,
509
// Open records the signature in the returned note's UnverifiedSigs field.
510
// If known.Verifier returns any other error, Open returns that error.
511
//
512
// If no known verifier has signed an otherwise valid note,
513
// Open returns an [UnverifiedNoteError].
514
// In this case, the unverified note can be fetched from inside the error.
515
func Open(msg []byte, known Verifiers) (*Note, error) {
516
	if known == nil {
517
		// Treat nil Verifiers as empty list, to produce useful error instead of crash.
518
		known = VerifierList()
519
	}
520

521
	// Must have valid UTF-8 with no non-newline ASCII control characters.
522
	for i := 0; i < len(msg); {
523
		r, size := utf8.DecodeRune(msg[i:])
524
		if r < 0x20 && r != '\n' || r == utf8.RuneError && size == 1 {
525
			return nil, errMalformedNote
526
		}
527
		i += size
528
	}
529

530
	// Must end with signature block preceded by blank line.
531
	split := bytes.LastIndex(msg, sigSplit)
532
	if split < 0 {
533
		return nil, errMalformedNote
534
	}
535
	text, sigs := msg[:split+1], msg[split+2:]
536
	if len(sigs) == 0 || sigs[len(sigs)-1] != '\n' {
537
		return nil, errMalformedNote
538
	}
539

540
	n := &Note{
541
		Text: string(text),
542
	}
543

544
	// Parse and verify signatures.
545
	// Ignore duplicate signatures.
546
	seen := make(map[nameHash]bool)
547
	seenUnverified := make(map[string]bool)
548
	numSig := 0
549
	for len(sigs) > 0 {
550
		// Pull out next signature line.
551
		// We know sigs[len(sigs)-1] == '\n', so IndexByte always finds one.
552
		i := bytes.IndexByte(sigs, '\n')
553
		line := sigs[:i]
554
		sigs = sigs[i+1:]
555

556
		if !bytes.HasPrefix(line, sigPrefix) {
557
			return nil, errMalformedNote
558
		}
559
		line = line[len(sigPrefix):]
560
		name, b64 := chop(string(line), " ")
561
		sig, err := base64.StdEncoding.DecodeString(b64)
562
		if err != nil || !isValidName(name) || b64 == "" || len(sig) < 5 {
563
			return nil, errMalformedNote
564
		}
565
		hash := binary.BigEndian.Uint32(sig[0:4])
566
		sig = sig[4:]
567

568
		if numSig++; numSig > 100 {
569
			// Avoid spending forever parsing a note with many signatures.
570
			return nil, errMalformedNote
571
		}
572

573
		v, err := known.Verifier(name, hash)
574
		if _, ok := err.(*UnknownVerifierError); ok {
575
			// Drop repeated identical unverified signatures.
576
			if seenUnverified[string(line)] {
577
				continue
578
			}
579
			seenUnverified[string(line)] = true
580
			n.UnverifiedSigs = append(n.UnverifiedSigs, Signature{Name: name, Hash: hash, Base64: b64})
581
			continue
582
		}
583
		if err != nil {
584
			return nil, err
585
		}
586

587
		// Check that known.Verifier returned the right verifier.
588
		if v.Name() != name || v.KeyHash() != hash {
589
			return nil, errMismatchedVerifier
590
		}
591

592
		// Drop repeated signatures by a single verifier.
593
		if seen[nameHash{name, hash}] {
594
			continue
595
		}
596
		seen[nameHash{name, hash}] = true
597

598
		ok := v.Verify(text, sig)
599
		if !ok {
600
			return nil, &InvalidSignatureError{name, hash}
601
		}
602

603
		n.Sigs = append(n.Sigs, Signature{Name: name, Hash: hash, Base64: b64})
604
	}
605

606
	// Parsed and verified all the signatures.
607
	if len(n.Sigs) == 0 {
608
		return nil, &UnverifiedNoteError{n}
609
	}
610
	return n, nil
611
}
612

613
// Sign signs the note with the given signers and returns the encoded message.
614
// The new signatures from signers are listed in the encoded message after
615
// the existing signatures already present in n.Sigs.
616
// If any signer uses the same key as an existing signature,
617
// the existing signature is elided from the output.
618
func Sign(n *Note, signers ...Signer) ([]byte, error) {
619
	var buf bytes.Buffer
620
	if !strings.HasSuffix(n.Text, "\n") {
621
		return nil, errMalformedNote
622
	}
623
	buf.WriteString(n.Text)
624

625
	// Prepare signatures.
626
	var sigs bytes.Buffer
627
	have := make(map[nameHash]bool)
628
	for _, s := range signers {
629
		name := s.Name()
630
		hash := s.KeyHash()
631
		have[nameHash{name, hash}] = true
632
		if !isValidName(name) {
633
			return nil, errInvalidSigner
634
		}
635

636
		sig, err := s.Sign(buf.Bytes()) // buf holds n.Text
637
		if err != nil {
638
			return nil, err
639
		}
640

641
		var hbuf [4]byte
642
		binary.BigEndian.PutUint32(hbuf[:], hash)
643
		b64 := base64.StdEncoding.EncodeToString(append(hbuf[:], sig...))
644
		sigs.WriteString("— ")
645
		sigs.WriteString(name)
646
		sigs.WriteString(" ")
647
		sigs.WriteString(b64)
648
		sigs.WriteString("\n")
649
	}
650

651
	buf.WriteString("\n")
652

653
	// Emit existing signatures not replaced by new ones.
654
	for _, list := range [][]Signature{n.Sigs, n.UnverifiedSigs} {
655
		for _, sig := range list {
656
			name, hash := sig.Name, sig.Hash
657
			if !isValidName(name) {
658
				return nil, errMalformedNote
659
			}
660
			if have[nameHash{name, hash}] {
661
				continue
662
			}
663
			// Double-check hash against base64.
664
			raw, err := base64.StdEncoding.DecodeString(sig.Base64)
665
			if err != nil || len(raw) < 4 || binary.BigEndian.Uint32(raw) != hash {
666
				return nil, errMalformedNote
667
			}
668
			buf.WriteString("— ")
669
			buf.WriteString(sig.Name)
670
			buf.WriteString(" ")
671
			buf.WriteString(sig.Base64)
672
			buf.WriteString("\n")
673
		}
674
	}
675
	buf.Write(sigs.Bytes())
676

677
	return buf.Bytes(), nil
678
}
679

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.