summaryrefslogtreecommitdiffstats
path: root/dsse/verify.go
blob: a36146b82a7d372458f45a88cc319b405a787184 (plain)
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
package dsse

import (
	"context"
	"crypto"
	"errors"
	"fmt"

	"golang.org/x/crypto/ssh"
)

// ErrNoSignature indicates that an envelope did not contain any signatures.
var ErrNoSignature = errors.New("no signature found")

type EnvelopeVerifier struct {
	providers []Verifier
	threshold int
}

type AcceptedKey struct {
	Public crypto.PublicKey
	KeyID  string
	Sig    Signature
}

func (ev *EnvelopeVerifier) Verify(ctx context.Context, e *Envelope) ([]AcceptedKey, error) {
	if e == nil {
		return nil, errors.New("cannot verify a nil envelope")
	}

	if len(e.Signatures) == 0 {
		return nil, ErrNoSignature
	}

	// Decode payload (i.e serialized body)
	body, err := e.DecodeB64Payload()
	if err != nil {
		return nil, err
	}
	// Generate PAE(payloadtype, serialized body)
	paeEnc := PAE(e.PayloadType, body)

	// If *any* signature is found to be incorrect, it is skipped
	var acceptedKeys []AcceptedKey
	usedKeyids := make(map[string]string)
	unverified_providers := ev.providers
	for _, s := range e.Signatures {
		sig, err := b64Decode(s.Sig)
		if err != nil {
			return nil, err
		}

		// Loop over the providers.
		// If provider and signature include key IDs but do not match skip.
		// If a provider recognizes the key, we exit
		// the loop and use the result.
		providers := unverified_providers
		for i, v := range providers {
			keyID, err := v.KeyID()

			// Verifiers that do not provide a keyid will be generated one using public.
			if err != nil || keyID == "" {
				keyID, err = SHA256KeyID(v.Public())
				if err != nil {
					keyID = ""
				}
			}

			if s.KeyID != "" && keyID != "" && err == nil && s.KeyID != keyID {
				continue
			}

			err = v.Verify(ctx, paeEnc, sig)
			if err != nil {
				continue
			}

			acceptedKey := AcceptedKey{
				Public: v.Public(),
				KeyID:  keyID,
				Sig:    s,
			}
			unverified_providers = removeIndex(providers, i)

			// See https://github.com/in-toto/in-toto/pull/251
			if _, ok := usedKeyids[keyID]; ok {
				fmt.Printf("Found envelope signed by different subkeys of the same main key, Only one of them is counted towards the step threshold, KeyID=%s\n", keyID)
				continue
			}

			usedKeyids[keyID] = ""
			acceptedKeys = append(acceptedKeys, acceptedKey)
			break
		}
	}

	// Sanity if with some reflect magic this happens.
	if ev.threshold <= 0 || ev.threshold > len(ev.providers) {
		return nil, errors.New("invalid threshold")
	}

	if len(usedKeyids) < ev.threshold {
		return acceptedKeys, fmt.Errorf("accepted signatures do not match threshold, Found: %d, Expected %d", len(acceptedKeys), ev.threshold)
	}

	return acceptedKeys, nil
}

func NewEnvelopeVerifier(v ...Verifier) (*EnvelopeVerifier, error) {
	return NewMultiEnvelopeVerifier(1, v...)
}

func NewMultiEnvelopeVerifier(threshold int, p ...Verifier) (*EnvelopeVerifier, error) {
	if threshold <= 0 || threshold > len(p) {
		return nil, errors.New("invalid threshold")
	}

	ev := EnvelopeVerifier{
		providers: p,
		threshold: threshold,
	}

	return &ev, nil
}

func SHA256KeyID(pub crypto.PublicKey) (string, error) {
	// Generate public key fingerprint
	sshpk, err := ssh.NewPublicKey(pub)
	if err != nil {
		return "", err
	}
	fingerprint := ssh.FingerprintSHA256(sshpk)
	return fingerprint, nil
}

func removeIndex(v []Verifier, index int) []Verifier {
	return append(v[:index], v[index+1:]...)
}