Skip to content
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

fix template signing singnature issue #5869

Merged
merged 4 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,32 @@
**/*-cache
**/*-config
**/.cache
*.DS_Store
*.exe
**/*.DS_Store
**/*.exe
.devcontainer
.gitignore
.idea
.vscode

# 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
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
114 changes: 114 additions & 0 deletions cmd/tools/signer/main.go
Original file line number Diff line number Diff line change
@@ -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)
tarunKoyalwar marked this conversation as resolved.
Show resolved Hide resolved

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
}
3 changes: 3 additions & 0 deletions pkg/templates/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions pkg/templates/signer/tmpl_signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down
Loading