fix template signing singnature issue (#5869)

* fix incorrect .gitignore

* template signer utility tool

* use yaml marhsal & unmarshal for normalization

* normalize before verification
This commit is contained in:
Tarun Koyalwar 2024-12-02 14:31:46 +05:30 committed by GitHub
parent 557b4fba38
commit 16735f5243
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 147 additions and 21 deletions

38
.gitignore vendored
View File

@ -2,8 +2,8 @@
**/*-cache **/*-cache
**/*-config **/*-config
**/.cache **/.cache
*.DS_Store **/*.DS_Store
*.exe **/*.exe
.devcontainer .devcontainer
.gitignore .gitignore
.idea .idea
@ -11,23 +11,23 @@
# Binaries # Binaries
/bin/* /bin/*
**/bindgen /bindgen
**/debug-* /debug-*
**/docgen /docgen
**/functional-test /functional-test
**/fuzzplayground /fuzzplayground
**/integration-test /integration-test
**/jsdocgen /jsdocgen
**/main /main
**/memogen /memogen
**/nuclei /nuclei
**/nuclei-stats* /nuclei-stats*
**/nuclei_dev /nuclei_dev
**/nuclei_main /nuclei_main
**/scan-charts /scan-charts
**/scrapefunc /scrapefunc
**/scrapefuncs /scrapefuncs
**/tsgen /tsgen
# Templates # Templates
/*.yaml /*.yaml

View File

@ -42,6 +42,10 @@ scan-charts: GOBUILD_OUTPUT = ./bin/scan-charts
scan-charts: GOBUILD_PACKAGES = cmd/scan-charts/main.go scan-charts: GOBUILD_PACKAGES = cmd/scan-charts/main.go
scan-charts: go-build 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_OUTPUT = ./bin/docgen
docgen: GOBUILD_PACKAGES = cmd/docgen/docgen.go docgen: GOBUILD_PACKAGES = cmd/docgen/docgen.go
docgen: bin = dstdocgen docgen: bin = dstdocgen

114
cmd/tools/signer/main.go Normal file
View File

@ -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
}

View File

@ -451,6 +451,9 @@ func parseTemplate(data []byte, options protocols.ExecutorOptions) (*Template, e
var verifier *signer.TemplateSigner var verifier *signer.TemplateSigner
for _, verifier = range signer.DefaultTemplateVerifiers { for _, verifier = range signer.DefaultTemplateVerifiers {
template.Verified, _ = verifier.Verify(data, template) 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 { if template.Verified {
template.TemplateVerifier = verifier.Identifier() template.TemplateVerifier = verifier.Identifier()
break break

View File

@ -30,11 +30,12 @@ func ExtractSignatureAndContent(data []byte) (signature, content []byte) {
dataStr := string(data) dataStr := string(data)
if idx := strings.LastIndex(dataStr, SignaturePattern); idx != -1 { if idx := strings.LastIndex(dataStr, SignaturePattern); idx != -1 {
signature = []byte(strings.TrimSpace(dataStr[idx:])) signature = []byte(strings.TrimSpace(dataStr[idx:]))
content = []byte(strings.TrimSpace(dataStr[:idx])) content = bytes.TrimSpace(data[:idx])
} else { } else {
content = data content = data
} }
return content = bytes.TrimSpace(content)
return signature, content
} }
// SignableTemplate is a template that can be signed // 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 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) buff := bytes.NewBuffer(content)
// if file has any imports process them // if file has any imports process them
for _, file := range tmpl.GetFileImports() { for _, file := range tmpl.GetFileImports() {