Path: blob/dev/pkg/templates/signer/tmpl_signer.go
2070 views
package signer12import (3"bytes"4"crypto/ecdsa"5"crypto/md5"6"crypto/rand"7"crypto/sha256"8"encoding/gob"9"encoding/hex"10"errors"11"fmt"12"os"13"strings"14"sync"1516"github.com/projectdiscovery/gologger"17"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"18"github.com/projectdiscovery/utils/errkit"19)2021var (22ErrUnknownAlgorithm = errors.New("unknown algorithm")23SignaturePattern = "# digest: "24SignatureFmt = SignaturePattern + "%x" + ":%v" // `#digest: <signature>:<fragment>`25)2627// ExtractSignatureAndContent extracts the signature (if present) and returns the content without the signature28func ExtractSignatureAndContent(data []byte) (signature, content []byte) {29dataStr := string(data)30if idx := strings.LastIndex(dataStr, SignaturePattern); idx != -1 {31signature = []byte(strings.TrimSpace(dataStr[idx:]))32content = bytes.TrimSpace(data[:idx])33} else {34content = data35}36content = bytes.TrimSpace(content)37return signature, content38}3940// SignableTemplate is a template that can be signed41type SignableTemplate interface {42// GetFileImports returns a list of files that are imported by the template43GetFileImports() []string44// HasCodeProtocol returns true if the template has a code protocol section45HasCodeProtocol() bool46}4748type TemplateSigner struct {49sync.Once50handler *KeyHandler51fragment string52}5354// Identifier returns the identifier for the template signer55func (t *TemplateSigner) Identifier() string {56return t.handler.cert.Subject.CommonName57}5859// fragment is optional part of signature that is used to identify the user60// who signed the template via md5 hash of public key61func (t *TemplateSigner) GetUserFragment() string {62// wrap with sync.Once to reduce unnecessary md5 hashing63t.Do(func() {64if t.handler.ecdsaPubKey != nil {65hashed := md5.Sum(t.handler.ecdsaPubKey.X.Bytes())66t.fragment = fmt.Sprintf("%x", hashed)67}68})69return t.fragment70}7172// Sign signs the given template with the template signer and returns the signature73func (t *TemplateSigner) Sign(data []byte, tmpl SignableTemplate) (string, error) {74existingSignature, content := ExtractSignatureAndContent(data)7576// while re-signing template check if it has a code protocol77// if it does then verify that it is signed by current signer78// if not then return error79if tmpl.HasCodeProtocol() {80if len(existingSignature) > 0 {81arr := strings.SplitN(string(existingSignature), ":", 3)82if len(arr) == 2 {83// signature has no fragment84return "", errkit.New("re-signing code templates are not allowed for security reasons.")85}86if len(arr) == 3 {87// signature has fragment verify if it is equal to current fragment88fragment := t.GetUserFragment()89if fragment != arr[2] {90return "", errkit.New("re-signing code templates are not allowed for security reasons.")91}92}93}94}9596buff := bytes.NewBuffer(content)97// if file has any imports process them98for _, file := range tmpl.GetFileImports() {99bin, err := os.ReadFile(file)100if err != nil {101return "", err102}103buff.WriteRune('\n')104buff.Write(bin)105}106signatureData, err := t.sign(buff.Bytes())107if err != nil {108return "", err109}110return signatureData, nil111}112113// Signs given data with the template signer114// Note: this should not be used for signing templates as file references115// in templates are not processed use template.SignTemplate() instead116func (t *TemplateSigner) sign(data []byte) (string, error) {117dataHash := sha256.Sum256(data)118ecdsaSignature, err := ecdsa.SignASN1(rand.Reader, t.handler.ecdsaKey, dataHash[:])119if err != nil {120return "", err121}122var signatureData bytes.Buffer123if err := gob.NewEncoder(&signatureData).Encode(ecdsaSignature); err != nil {124return "", err125}126return fmt.Sprintf(SignatureFmt, signatureData.Bytes(), t.GetUserFragment()), nil127}128129// Verify verifies the given template with the template signer130func (t *TemplateSigner) Verify(data []byte, tmpl SignableTemplate) (bool, error) {131signature, content := ExtractSignatureAndContent(data)132if len(signature) == 0 {133return false, errors.New("no signature found")134}135136if !bytes.HasPrefix(signature, []byte(SignaturePattern)) {137return false, errors.New("signature must be at the end of the template")138}139140digestData := bytes.TrimSpace(bytes.TrimPrefix(signature, []byte(SignaturePattern)))141// remove fragment from digest as it is used for re-signing purposes only142digestString := strings.TrimSuffix(string(digestData), ":"+t.GetUserFragment())143digest, err := hex.DecodeString(digestString)144if err != nil {145return false, err146}147148// normalize content by removing \r\n everywhere since this only done for verification149// it does not affect the actual template150content = bytes.ReplaceAll(content, []byte("\r\n"), []byte("\n"))151152buff := bytes.NewBuffer(content)153// if file has any imports process them154for _, file := range tmpl.GetFileImports() {155bin, err := os.ReadFile(file)156if err != nil {157return false, err158}159buff.WriteRune('\n')160buff.Write(bin)161}162163return t.verify(buff.Bytes(), digest)164}165166// Verify verifies the given data with the template signer167// Note: this should not be used for verifying templates as file references168// in templates are not processed169func (t *TemplateSigner) verify(data, signatureData []byte) (bool, error) {170dataHash := sha256.Sum256(data)171172var signature []byte173if err := gob.NewDecoder(bytes.NewReader(signatureData)).Decode(&signature); err != nil {174return false, err175}176return ecdsa.VerifyASN1(t.handler.ecdsaPubKey, dataHash[:], signature), nil177}178179// NewTemplateSigner creates a new signer for signing templates180func NewTemplateSigner(cert, privateKey []byte) (*TemplateSigner, error) {181handler := &KeyHandler{}182var err error183if cert != nil || privateKey != nil {184handler.UserCert = cert185handler.PrivateKey = privateKey186} else {187err = handler.ReadCert(CertEnvVarName, config.DefaultConfig.GetKeysDir())188if err == nil {189err = handler.ReadPrivateKey(PrivateKeyEnvName, config.DefaultConfig.GetKeysDir())190}191}192if err != nil && !SkipGeneratingKeys {193if err != ErrNoCertificate && err != ErrNoPrivateKey {194gologger.Info().Msgf("Invalid user cert found : %s\n", err)195}196// generating new keys197handler.GenerateKeyPair()198if err := handler.SaveToDisk(config.DefaultConfig.GetKeysDir()); err != nil {199gologger.Fatal().Msgf("could not save generated keys to disk: %s\n", err)200}201// do not continue further let user re-run the command202os.Exit(0)203} else if err != nil && SkipGeneratingKeys {204return nil, err205}206207if err := handler.ParseUserCert(); err != nil {208return nil, err209}210if err := handler.ParsePrivateKey(); err != nil {211return nil, err212}213return &TemplateSigner{214handler: handler,215}, nil216}217218// NewTemplateSignerFromFiles creates a new signer for signing templates219func NewTemplateSignerFromFiles(cert, privKey string) (*TemplateSigner, error) {220certData, err := os.ReadFile(cert)221if err != nil {222return nil, err223}224privKeyData, err := os.ReadFile(privKey)225if err != nil {226return nil, err227}228return NewTemplateSigner(certData, privKeyData)229}230231// NewTemplateSigVerifier creates a new signer for verifying templates232func NewTemplateSigVerifier(cert []byte) (*TemplateSigner, error) {233handler := &KeyHandler{}234if cert != nil {235handler.UserCert = cert236} else {237if err := handler.ReadCert(CertEnvVarName, config.DefaultConfig.GetKeysDir()); err != nil {238return nil, err239}240}241if err := handler.ParseUserCert(); err != nil {242return nil, err243}244return &TemplateSigner{245handler: handler,246}, nil247}248249250