diff --git a/.gitignore b/.gitignore
index 3148d8bbdc..f61794b191 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,8 +2,8 @@
 **/*-cache
 **/*-config
 **/.cache
-*.DS_Store
-*.exe
+**/*.DS_Store
+**/*.exe
 .devcontainer
 .gitignore
 .idea
@@ -11,23 +11,23 @@
 
 # Binaries
 /bin/*
-**/bindgen
-**/debug-*
-**/docgen
-**/functional-test
-**/fuzzplayground
-**/integration-test
-**/jsdocgen
-**/main
-**/memogen
-**/nuclei
-**/nuclei-stats*
-**/nuclei_dev
-**/nuclei_main
-**/scan-charts
-**/scrapefunc
-**/scrapefuncs
-**/tsgen
+/bindgen
+/debug-*
+/docgen
+/functional-test
+/fuzzplayground
+/integration-test
+/jsdocgen
+/main
+/memogen
+/nuclei
+/nuclei-stats*
+/nuclei_dev
+/nuclei_main
+/scan-charts
+/scrapefunc
+/scrapefuncs
+/tsgen
 
 # Templates
 /*.yaml
diff --git a/Makefile b/Makefile
index a35a4b61f5..e1ee516cf6 100644
--- a/Makefile
+++ b/Makefile
@@ -42,6 +42,10 @@ scan-charts: GOBUILD_OUTPUT = ./bin/scan-charts
 scan-charts: GOBUILD_PACKAGES = cmd/scan-charts/main.go
 scan-charts: go-build
 
+template-signer: GOBUILD_OUTPUT = ./bin/template-signer
+template-signer: GOBUILD_PACKAGES = cmd/tools/signer/main.go
+template-signer: go-build
+
 docgen: GOBUILD_OUTPUT = ./bin/docgen
 docgen: GOBUILD_PACKAGES = cmd/docgen/docgen.go
 docgen: bin = dstdocgen
diff --git a/cmd/tools/signer/main.go b/cmd/tools/signer/main.go
new file mode 100644
index 0000000000..290572efb7
--- /dev/null
+++ b/cmd/tools/signer/main.go
@@ -0,0 +1,114 @@
+package main
+
+import (
+	"crypto/sha256"
+	"encoding/hex"
+	"flag"
+	"os"
+	"path/filepath"
+
+	"github.com/projectdiscovery/gologger"
+	"github.com/projectdiscovery/gologger/levels"
+	"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
+	"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
+	"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
+	"github.com/projectdiscovery/nuclei/v3/pkg/templates"
+	"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"
+	"github.com/projectdiscovery/nuclei/v3/pkg/types"
+	fileutil "github.com/projectdiscovery/utils/file"
+	folderutil "github.com/projectdiscovery/utils/folder"
+)
+
+var (
+	appConfigDir    = folderutil.AppConfigDirOrDefault(".config", "nuclei")
+	defaultCertFile = filepath.Join(appConfigDir, "keys", "nuclei-user.crt")
+	defaultPrivKey  = filepath.Join(appConfigDir, "keys", "nuclei-user-private-key.pem")
+)
+
+var (
+	template string
+	cert     string
+	privKey  string
+)
+
+func main() {
+	flag.StringVar(&template, "template", "", "template to sign (file only)")
+	flag.StringVar(&cert, "cert", defaultCertFile, "certificate file")
+	flag.StringVar(&privKey, "priv-key", defaultPrivKey, "private key file")
+	flag.Parse()
+
+	config.DefaultConfig.LogAllEvents = true
+	gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)
+
+	if template == "" {
+		gologger.Fatal().Msg("template is required")
+	}
+	if !fileutil.FileExists(template) {
+		gologger.Fatal().Msgf("template file %s does not exist or not a file", template)
+	}
+
+	// get signer
+	tmplSigner, err := signer.NewTemplateSignerFromFiles(cert, privKey)
+	if err != nil {
+		gologger.Fatal().Msgf("failed to create signer: %s", err)
+	}
+	gologger.Info().Msgf("Template Signer: %v\n", tmplSigner.Identifier())
+
+	// read file
+	bin, err := os.ReadFile(template)
+	if err != nil {
+		gologger.Fatal().Msgf("failed to read template file %s: %s", template, err)
+	}
+
+	// extract signature and content
+	sig, content := signer.ExtractSignatureAndContent(bin)
+	hash := sha256.Sum256(content)
+
+	gologger.Info().Msgf("Signature Details:")
+	gologger.Info().Msgf("----------------")
+	gologger.Info().Msgf("Signature: %s", sig)
+	gologger.Info().Msgf("Content Hash (SHA256): %s\n", hex.EncodeToString(hash[:]))
+
+	execOpts := defaultExecutorOpts(template)
+
+	tmpl, err := templates.Parse(template, nil, execOpts)
+	if err != nil {
+		gologger.Fatal().Msgf("failed to parse template: %s", err)
+	}
+	gologger.Info().Msgf("Template Verified: %v\n", tmpl.Verified)
+
+	if !tmpl.Verified {
+		gologger.Info().Msgf("------------------------")
+		gologger.Info().Msg("Template is not verified, signing template")
+		if err := templates.SignTemplate(tmplSigner, template); err != nil {
+			gologger.Fatal().Msgf("Failed to sign template: %s", err)
+		}
+		// verify again by reading file what the new signature and hash is
+		bin2, err := os.ReadFile(template)
+		if err != nil {
+			gologger.Fatal().Msgf("failed to read signed template file %s: %s", template, err)
+		}
+		sig2, content2 := signer.ExtractSignatureAndContent(bin2)
+		hash2 := sha256.Sum256(content2)
+
+		gologger.Info().Msgf("Updated Signature Details:")
+		gologger.Info().Msgf("------------------------")
+		gologger.Info().Msgf("Signature: %s", sig2)
+		gologger.Info().Msgf("Content Hash (SHA256): %s\n", hex.EncodeToString(hash2[:]))
+	}
+	gologger.Info().Msgf("✓ Template signed & verified successfully")
+}
+
+func defaultExecutorOpts(templatePath string) protocols.ExecutorOptions {
+	// use parsed options when initializing signer instead of default options
+	options := types.DefaultOptions()
+	templates.UseOptionsForSigner(options)
+	catalog := disk.NewCatalog(filepath.Dir(templatePath))
+	executerOpts := protocols.ExecutorOptions{
+		Catalog:      catalog,
+		Options:      options,
+		TemplatePath: templatePath,
+		Parser:       templates.NewParser(),
+	}
+	return executerOpts
+}
diff --git a/pkg/templates/compile.go b/pkg/templates/compile.go
index af83d071a4..0278e979bb 100644
--- a/pkg/templates/compile.go
+++ b/pkg/templates/compile.go
@@ -451,6 +451,9 @@ func parseTemplate(data []byte, options protocols.ExecutorOptions) (*Template, e
 	var verifier *signer.TemplateSigner
 	for _, verifier = range signer.DefaultTemplateVerifiers {
 		template.Verified, _ = verifier.Verify(data, template)
+		if config.DefaultConfig.LogAllEvents {
+			gologger.Verbose().Msgf("template %v verified by %s : %v", template.ID, verifier.Identifier(), template.Verified)
+		}
 		if template.Verified {
 			template.TemplateVerifier = verifier.Identifier()
 			break
diff --git a/pkg/templates/signer/tmpl_signer.go b/pkg/templates/signer/tmpl_signer.go
index a530da1fe3..35536ab6ec 100644
--- a/pkg/templates/signer/tmpl_signer.go
+++ b/pkg/templates/signer/tmpl_signer.go
@@ -30,11 +30,12 @@ func ExtractSignatureAndContent(data []byte) (signature, content []byte) {
 	dataStr := string(data)
 	if idx := strings.LastIndex(dataStr, SignaturePattern); idx != -1 {
 		signature = []byte(strings.TrimSpace(dataStr[idx:]))
-		content = []byte(strings.TrimSpace(dataStr[:idx]))
+		content = bytes.TrimSpace(data[:idx])
 	} else {
 		content = data
 	}
-	return
+	content = bytes.TrimSpace(content)
+	return signature, content
 }
 
 // SignableTemplate is a template that can be signed
@@ -145,6 +146,10 @@ func (t *TemplateSigner) Verify(data []byte, tmpl SignableTemplate) (bool, error
 		return false, err
 	}
 
+	// normalize content by removing \r\n everywhere since this only done for verification
+	// it does not affect the actual template
+	content = bytes.ReplaceAll(content, []byte("\r\n"), []byte("\n"))
+
 	buff := bytes.NewBuffer(content)
 	// if file has any imports process them
 	for _, file := range tmpl.GetFileImports() {