-
Notifications
You must be signed in to change notification settings - Fork 3.1k
perf: cache template signature verification #6779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
perf: cache template signature verification #6779
Conversation
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 <[email protected]>
WalkthroughA template verification caching mechanism is introduced across the codebase. A new Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
|
@coderabbitai create an issue for this. Prefixed with |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain
🏁 Script executed: Length of output: 54
The issue summarizes the caching work for template signature verification and includes a reference to this PR. |
|
Context: since we already introduced persistent metadata cache (#6630), though there's room to take advantage of it further for perf gains. |
Proposed changes
perf: cache template signature verification
to avoid redundant ECDSA checks.
Add
protocols.TemplateVerification& callbackmechanism to
protocols.ExecutorOptionsto enablereusing cached verification data from the metadata
index. Also updating internal
templates.parseTemplatefunc to skip ECDSAverification when cached data is any, and wire the
callback in
loader.Newfor metadata-backedlookups.
Proof
This eliminates
TemplateSigner.Verify(~1.75s)and
crypto/ecdsa.VerifyASN1(~1.50s) from thehot path (read: reduces startup time).
Top 10 deltas:
Checklist
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.