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() {