mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-01-31 15:53:10 +08:00
to avoid redundant ECDSA checks.
Add `protocols.TemplateVerification` & callback
mechanism to `protocols.ExecutorOptions` to enable
reusing cached verification data from the metadata
index. Also updating internal
`templates.parseTemplate` func to skip ECDSA
verification when cached data is any, and wire the
callback in `loader.New` for metadata-backed
lookups.
Proof:
```
$ go tool pprof -list "signer\..*" -base 3.6.2.cpu patch.cpu
Total: 34.78s
ROUTINE ======================== github.com/projectdiscovery/nuclei/v3/pkg/templates/signer.(*TemplateSigner).Verify in /home/dw1/Development/PD/nuclei/pkg/templates/signer/tmpl_signer.go
0 -1.75s (flat, cum) 5.03% of Total
. . 131:func (t *TemplateSigner) Verify(data []byte, tmpl SignableTemplate) (bool, error) {
. -70ms 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())
. -20ms 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
. -40ms 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:
. -1.62s 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
ROUTINE ======================== github.com/projectdiscovery/nuclei/v3/pkg/templates/signer.(*TemplateSigner).verify in /home/dw1/Development/PD/nuclei/pkg/templates/signer/tmpl_signer.go
0 -1.62s (flat, cum) 4.66% of Total
. . 170:func (t *TemplateSigner) verify(data, signatureData []byte) (bool, error) {
. -50ms 171: dataHash := sha256.Sum256(data)
. . 172:
. . 173: var signature []byte
. -70ms 174: if err := gob.NewDecoder(bytes.NewReader(signatureData)).Decode(&signature); err != nil {
. . 175: return false, err
. . 176: }
. -1.50s 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{}
ROUTINE ======================== github.com/projectdiscovery/nuclei/v3/pkg/templates/signer.ExtractSignatureAndContent in /home/dw1/Development/PD/nuclei/pkg/templates/signer/tmpl_signer.go
0 -70ms (flat, cum) 0.2% of Total
. . 29:func ExtractSignatureAndContent(data []byte) (signature, content []byte) {
. -50ms 30: dataStr := string(data)
. -20ms 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: }
$ go tool pprof -list "crypto/ecdsa\.VerifyASN1" 3.6.2.cpu patch.cpu
Total: 34.80s
ROUTINE ======================== crypto/ecdsa.VerifyASN1 in /usr/local/go/src/crypto/ecdsa/ecdsa.go
0 1.50s (flat, cum) 4.31% of Total
. . 500:func VerifyASN1(pub *PublicKey, hash, sig []byte) bool {
. . 501: if boring.Enabled {
. . 502: key, err := boringPublicKey(pub)
. . 503: if err != nil {
. . 504: return false
. . 505: }
. . 506: return boring.VerifyECDSA(key, hash, sig)
. . 507: }
. . 508: boring.UnreachableExceptTests()
. . 509:
. . 510: switch pub.Curve.Params() {
. . 511: case elliptic.P224().Params():
. . 512: return verifyFIPS(ecdsa.P224(), pub, hash, sig)
. . 513: case elliptic.P256().Params():
. 1.50s 514: return verifyFIPS(ecdsa.P256(), pub, hash, sig)
. . 515: case elliptic.P384().Params():
. . 516: return verifyFIPS(ecdsa.P384(), pub, hash, sig)
. . 517: case elliptic.P521().Params():
. . 518: return verifyFIPS(ecdsa.P521(), pub, hash, sig)
. . 519: default:
```
This eliminates `TemplateSigner.Verify` (~1.75s)
and `crypto/ecdsa.VerifyASN1` (~1.50s) from the
hot path (read: reduces startup time).
Signed-off-by: Dwi Siswanto <git@dw1.io>