Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/templates/signer/tmpl_signer.go
2070 views
1
package signer
2
3
import (
4
"bytes"
5
"crypto/ecdsa"
6
"crypto/md5"
7
"crypto/rand"
8
"crypto/sha256"
9
"encoding/gob"
10
"encoding/hex"
11
"errors"
12
"fmt"
13
"os"
14
"strings"
15
"sync"
16
17
"github.com/projectdiscovery/gologger"
18
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
19
"github.com/projectdiscovery/utils/errkit"
20
)
21
22
var (
23
ErrUnknownAlgorithm = errors.New("unknown algorithm")
24
SignaturePattern = "# digest: "
25
SignatureFmt = SignaturePattern + "%x" + ":%v" // `#digest: <signature>:<fragment>`
26
)
27
28
// ExtractSignatureAndContent extracts the signature (if present) and returns the content without the signature
29
func ExtractSignatureAndContent(data []byte) (signature, content []byte) {
30
dataStr := string(data)
31
if idx := strings.LastIndex(dataStr, SignaturePattern); idx != -1 {
32
signature = []byte(strings.TrimSpace(dataStr[idx:]))
33
content = bytes.TrimSpace(data[:idx])
34
} else {
35
content = data
36
}
37
content = bytes.TrimSpace(content)
38
return signature, content
39
}
40
41
// SignableTemplate is a template that can be signed
42
type SignableTemplate interface {
43
// GetFileImports returns a list of files that are imported by the template
44
GetFileImports() []string
45
// HasCodeProtocol returns true if the template has a code protocol section
46
HasCodeProtocol() bool
47
}
48
49
type TemplateSigner struct {
50
sync.Once
51
handler *KeyHandler
52
fragment string
53
}
54
55
// Identifier returns the identifier for the template signer
56
func (t *TemplateSigner) Identifier() string {
57
return t.handler.cert.Subject.CommonName
58
}
59
60
// fragment is optional part of signature that is used to identify the user
61
// who signed the template via md5 hash of public key
62
func (t *TemplateSigner) GetUserFragment() string {
63
// wrap with sync.Once to reduce unnecessary md5 hashing
64
t.Do(func() {
65
if t.handler.ecdsaPubKey != nil {
66
hashed := md5.Sum(t.handler.ecdsaPubKey.X.Bytes())
67
t.fragment = fmt.Sprintf("%x", hashed)
68
}
69
})
70
return t.fragment
71
}
72
73
// Sign signs the given template with the template signer and returns the signature
74
func (t *TemplateSigner) Sign(data []byte, tmpl SignableTemplate) (string, error) {
75
existingSignature, content := ExtractSignatureAndContent(data)
76
77
// while re-signing template check if it has a code protocol
78
// if it does then verify that it is signed by current signer
79
// if not then return error
80
if tmpl.HasCodeProtocol() {
81
if len(existingSignature) > 0 {
82
arr := strings.SplitN(string(existingSignature), ":", 3)
83
if len(arr) == 2 {
84
// signature has no fragment
85
return "", errkit.New("re-signing code templates are not allowed for security reasons.")
86
}
87
if len(arr) == 3 {
88
// signature has fragment verify if it is equal to current fragment
89
fragment := t.GetUserFragment()
90
if fragment != arr[2] {
91
return "", errkit.New("re-signing code templates are not allowed for security reasons.")
92
}
93
}
94
}
95
}
96
97
buff := bytes.NewBuffer(content)
98
// if file has any imports process them
99
for _, file := range tmpl.GetFileImports() {
100
bin, err := os.ReadFile(file)
101
if err != nil {
102
return "", err
103
}
104
buff.WriteRune('\n')
105
buff.Write(bin)
106
}
107
signatureData, err := t.sign(buff.Bytes())
108
if err != nil {
109
return "", err
110
}
111
return signatureData, nil
112
}
113
114
// Signs given data with the template signer
115
// Note: this should not be used for signing templates as file references
116
// in templates are not processed use template.SignTemplate() instead
117
func (t *TemplateSigner) sign(data []byte) (string, error) {
118
dataHash := sha256.Sum256(data)
119
ecdsaSignature, err := ecdsa.SignASN1(rand.Reader, t.handler.ecdsaKey, dataHash[:])
120
if err != nil {
121
return "", err
122
}
123
var signatureData bytes.Buffer
124
if err := gob.NewEncoder(&signatureData).Encode(ecdsaSignature); err != nil {
125
return "", err
126
}
127
return fmt.Sprintf(SignatureFmt, signatureData.Bytes(), t.GetUserFragment()), nil
128
}
129
130
// Verify verifies the given template with the template signer
131
func (t *TemplateSigner) Verify(data []byte, tmpl SignableTemplate) (bool, error) {
132
signature, content := ExtractSignatureAndContent(data)
133
if len(signature) == 0 {
134
return false, errors.New("no signature found")
135
}
136
137
if !bytes.HasPrefix(signature, []byte(SignaturePattern)) {
138
return false, errors.New("signature must be at the end of the template")
139
}
140
141
digestData := bytes.TrimSpace(bytes.TrimPrefix(signature, []byte(SignaturePattern)))
142
// remove fragment from digest as it is used for re-signing purposes only
143
digestString := strings.TrimSuffix(string(digestData), ":"+t.GetUserFragment())
144
digest, err := hex.DecodeString(digestString)
145
if err != nil {
146
return false, err
147
}
148
149
// normalize content by removing \r\n everywhere since this only done for verification
150
// it does not affect the actual template
151
content = bytes.ReplaceAll(content, []byte("\r\n"), []byte("\n"))
152
153
buff := bytes.NewBuffer(content)
154
// if file has any imports process them
155
for _, file := range tmpl.GetFileImports() {
156
bin, err := os.ReadFile(file)
157
if err != nil {
158
return false, err
159
}
160
buff.WriteRune('\n')
161
buff.Write(bin)
162
}
163
164
return t.verify(buff.Bytes(), digest)
165
}
166
167
// Verify verifies the given data with the template signer
168
// Note: this should not be used for verifying templates as file references
169
// in templates are not processed
170
func (t *TemplateSigner) verify(data, signatureData []byte) (bool, error) {
171
dataHash := sha256.Sum256(data)
172
173
var signature []byte
174
if err := gob.NewDecoder(bytes.NewReader(signatureData)).Decode(&signature); err != nil {
175
return false, err
176
}
177
return ecdsa.VerifyASN1(t.handler.ecdsaPubKey, dataHash[:], signature), nil
178
}
179
180
// NewTemplateSigner creates a new signer for signing templates
181
func NewTemplateSigner(cert, privateKey []byte) (*TemplateSigner, error) {
182
handler := &KeyHandler{}
183
var err error
184
if cert != nil || privateKey != nil {
185
handler.UserCert = cert
186
handler.PrivateKey = privateKey
187
} else {
188
err = handler.ReadCert(CertEnvVarName, config.DefaultConfig.GetKeysDir())
189
if err == nil {
190
err = handler.ReadPrivateKey(PrivateKeyEnvName, config.DefaultConfig.GetKeysDir())
191
}
192
}
193
if err != nil && !SkipGeneratingKeys {
194
if err != ErrNoCertificate && err != ErrNoPrivateKey {
195
gologger.Info().Msgf("Invalid user cert found : %s\n", err)
196
}
197
// generating new keys
198
handler.GenerateKeyPair()
199
if err := handler.SaveToDisk(config.DefaultConfig.GetKeysDir()); err != nil {
200
gologger.Fatal().Msgf("could not save generated keys to disk: %s\n", err)
201
}
202
// do not continue further let user re-run the command
203
os.Exit(0)
204
} else if err != nil && SkipGeneratingKeys {
205
return nil, err
206
}
207
208
if err := handler.ParseUserCert(); err != nil {
209
return nil, err
210
}
211
if err := handler.ParsePrivateKey(); err != nil {
212
return nil, err
213
}
214
return &TemplateSigner{
215
handler: handler,
216
}, nil
217
}
218
219
// NewTemplateSignerFromFiles creates a new signer for signing templates
220
func NewTemplateSignerFromFiles(cert, privKey string) (*TemplateSigner, error) {
221
certData, err := os.ReadFile(cert)
222
if err != nil {
223
return nil, err
224
}
225
privKeyData, err := os.ReadFile(privKey)
226
if err != nil {
227
return nil, err
228
}
229
return NewTemplateSigner(certData, privKeyData)
230
}
231
232
// NewTemplateSigVerifier creates a new signer for verifying templates
233
func NewTemplateSigVerifier(cert []byte) (*TemplateSigner, error) {
234
handler := &KeyHandler{}
235
if cert != nil {
236
handler.UserCert = cert
237
} else {
238
if err := handler.ReadCert(CertEnvVarName, config.DefaultConfig.GetKeysDir()); err != nil {
239
return nil, err
240
}
241
}
242
if err := handler.ParseUserCert(); err != nil {
243
return nil, err
244
}
245
return &TemplateSigner{
246
handler: handler,
247
}, nil
248
}
249
250