2023-10-13 13:17:27 +05:30
|
|
|
package signer
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"crypto/ecdsa"
|
|
|
|
|
"crypto/md5"
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
"encoding/gob"
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
|
|
"github.com/projectdiscovery/gologger"
|
2023-10-17 17:44:13 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
2023-10-13 13:17:27 +05:30
|
|
|
errorutil "github.com/projectdiscovery/utils/errors"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
ErrUnknownAlgorithm = errors.New("unknown algorithm")
|
|
|
|
|
SignaturePattern = "# digest: "
|
|
|
|
|
SignatureFmt = SignaturePattern + "%x" + ":%v" // `#digest: <signature>:<fragment>`
|
|
|
|
|
)
|
|
|
|
|
|
2024-08-19 14:32:54 +02:00
|
|
|
// ExtractSignatureAndContent extracts the signature (if present) and returns the content without the signature
|
|
|
|
|
func ExtractSignatureAndContent(data []byte) (signature, content []byte) {
|
|
|
|
|
dataStr := string(data)
|
|
|
|
|
if idx := strings.LastIndex(dataStr, SignaturePattern); idx != -1 {
|
|
|
|
|
signature = []byte(strings.TrimSpace(dataStr[idx:]))
|
2024-12-02 14:31:46 +05:30
|
|
|
content = bytes.TrimSpace(data[:idx])
|
2024-08-19 14:32:54 +02:00
|
|
|
} else {
|
|
|
|
|
content = data
|
|
|
|
|
}
|
2024-12-02 14:31:46 +05:30
|
|
|
content = bytes.TrimSpace(content)
|
|
|
|
|
return signature, content
|
2023-10-13 13:17:27 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SignableTemplate is a template that can be signed
|
|
|
|
|
type SignableTemplate interface {
|
|
|
|
|
// GetFileImports returns a list of files that are imported by the template
|
|
|
|
|
GetFileImports() []string
|
|
|
|
|
// HasCodeProtocol returns true if the template has a code protocol section
|
|
|
|
|
HasCodeProtocol() bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TemplateSigner struct {
|
|
|
|
|
sync.Once
|
|
|
|
|
handler *KeyHandler
|
|
|
|
|
fragment string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Identifier returns the identifier for the template signer
|
|
|
|
|
func (t *TemplateSigner) Identifier() string {
|
|
|
|
|
return t.handler.cert.Subject.CommonName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fragment is optional part of signature that is used to identify the user
|
|
|
|
|
// who signed the template via md5 hash of public key
|
|
|
|
|
func (t *TemplateSigner) GetUserFragment() string {
|
|
|
|
|
// wrap with sync.Once to reduce unnecessary md5 hashing
|
|
|
|
|
t.Do(func() {
|
|
|
|
|
if t.handler.ecdsaPubKey != nil {
|
|
|
|
|
hashed := md5.Sum(t.handler.ecdsaPubKey.X.Bytes())
|
|
|
|
|
t.fragment = fmt.Sprintf("%x", hashed)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return t.fragment
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sign signs the given template with the template signer and returns the signature
|
|
|
|
|
func (t *TemplateSigner) Sign(data []byte, tmpl SignableTemplate) (string, error) {
|
2024-08-19 14:32:54 +02:00
|
|
|
existingSignature, content := ExtractSignatureAndContent(data)
|
|
|
|
|
|
2023-10-13 13:17:27 +05:30
|
|
|
// while re-signing template check if it has a code protocol
|
|
|
|
|
// if it does then verify that it is signed by current signer
|
|
|
|
|
// if not then return error
|
|
|
|
|
if tmpl.HasCodeProtocol() {
|
2024-08-19 14:32:54 +02:00
|
|
|
if len(existingSignature) > 0 {
|
|
|
|
|
arr := strings.SplitN(string(existingSignature), ":", 3)
|
|
|
|
|
if len(arr) == 2 {
|
|
|
|
|
// signature has no fragment
|
2023-10-13 13:17:27 +05:30
|
|
|
return "", errorutil.NewWithTag("signer", "re-signing code templates are not allowed for security reasons.")
|
|
|
|
|
}
|
2024-08-19 14:32:54 +02:00
|
|
|
if len(arr) == 3 {
|
|
|
|
|
// signature has fragment verify if it is equal to current fragment
|
|
|
|
|
fragment := t.GetUserFragment()
|
|
|
|
|
if fragment != arr[2] {
|
|
|
|
|
return "", errorutil.NewWithTag("signer", "re-signing code templates are not allowed for security reasons.")
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-13 13:17:27 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-19 14:32:54 +02:00
|
|
|
buff := bytes.NewBuffer(content)
|
2023-10-13 13:17:27 +05:30
|
|
|
// if file has any imports process them
|
|
|
|
|
for _, file := range tmpl.GetFileImports() {
|
|
|
|
|
bin, err := os.ReadFile(file)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
buff.WriteRune('\n')
|
|
|
|
|
buff.Write(bin)
|
|
|
|
|
}
|
|
|
|
|
signatureData, err := t.sign(buff.Bytes())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return signatureData, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Signs given data with the template signer
|
|
|
|
|
// Note: this should not be used for signing templates as file references
|
|
|
|
|
// in templates are not processed use template.SignTemplate() instead
|
|
|
|
|
func (t *TemplateSigner) sign(data []byte) (string, error) {
|
|
|
|
|
dataHash := sha256.Sum256(data)
|
|
|
|
|
ecdsaSignature, err := ecdsa.SignASN1(rand.Reader, t.handler.ecdsaKey, dataHash[:])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
var signatureData bytes.Buffer
|
|
|
|
|
if err := gob.NewEncoder(&signatureData).Encode(ecdsaSignature); err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return fmt.Sprintf(SignatureFmt, signatureData.Bytes(), t.GetUserFragment()), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify verifies the given template with the template signer
|
|
|
|
|
func (t *TemplateSigner) Verify(data []byte, tmpl SignableTemplate) (bool, error) {
|
2024-08-19 14:32:54 +02:00
|
|
|
signature, content := ExtractSignatureAndContent(data)
|
|
|
|
|
if len(signature) == 0 {
|
|
|
|
|
return false, errors.New("no signature found")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !bytes.HasPrefix(signature, []byte(SignaturePattern)) {
|
|
|
|
|
return false, errors.New("signature must be at the end of the template")
|
2023-10-13 13:17:27 +05:30
|
|
|
}
|
|
|
|
|
|
2024-08-19 14:32:54 +02:00
|
|
|
digestData := bytes.TrimSpace(bytes.TrimPrefix(signature, []byte(SignaturePattern)))
|
2023-10-13 13:17:27 +05:30
|
|
|
// remove fragment from digest as it is used for re-signing purposes only
|
|
|
|
|
digestString := strings.TrimSuffix(string(digestData), ":"+t.GetUserFragment())
|
|
|
|
|
digest, err := hex.DecodeString(digestString)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false, err
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 14:31:46 +05:30
|
|
|
// 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"))
|
|
|
|
|
|
2024-08-19 14:32:54 +02:00
|
|
|
buff := bytes.NewBuffer(content)
|
2023-10-13 13:17:27 +05:30
|
|
|
// if file has any imports process them
|
|
|
|
|
for _, file := range tmpl.GetFileImports() {
|
|
|
|
|
bin, err := os.ReadFile(file)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false, err
|
|
|
|
|
}
|
|
|
|
|
buff.WriteRune('\n')
|
|
|
|
|
buff.Write(bin)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return t.verify(buff.Bytes(), digest)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify verifies the given data with the template signer
|
|
|
|
|
// Note: this should not be used for verifying templates as file references
|
|
|
|
|
// in templates are not processed
|
|
|
|
|
func (t *TemplateSigner) verify(data, signatureData []byte) (bool, error) {
|
|
|
|
|
dataHash := sha256.Sum256(data)
|
|
|
|
|
|
|
|
|
|
var signature []byte
|
|
|
|
|
if err := gob.NewDecoder(bytes.NewReader(signatureData)).Decode(&signature); err != nil {
|
|
|
|
|
return false, err
|
|
|
|
|
}
|
|
|
|
|
return ecdsa.VerifyASN1(t.handler.ecdsaPubKey, dataHash[:], signature), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewTemplateSigner creates a new signer for signing templates
|
|
|
|
|
func NewTemplateSigner(cert, privateKey []byte) (*TemplateSigner, error) {
|
|
|
|
|
handler := &KeyHandler{}
|
|
|
|
|
var err error
|
|
|
|
|
if cert != nil || privateKey != nil {
|
|
|
|
|
handler.UserCert = cert
|
|
|
|
|
handler.PrivateKey = privateKey
|
|
|
|
|
} else {
|
|
|
|
|
err = handler.ReadCert(CertEnvVarName, config.DefaultConfig.GetKeysDir())
|
|
|
|
|
if err == nil {
|
|
|
|
|
err = handler.ReadPrivateKey(PrivateKeyEnvName, config.DefaultConfig.GetKeysDir())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err != nil && !SkipGeneratingKeys {
|
|
|
|
|
if err != ErrNoCertificate && err != ErrNoPrivateKey {
|
|
|
|
|
gologger.Info().Msgf("Invalid user cert found : %s\n", err)
|
|
|
|
|
}
|
|
|
|
|
// generating new keys
|
|
|
|
|
handler.GenerateKeyPair()
|
|
|
|
|
if err := handler.SaveToDisk(config.DefaultConfig.GetKeysDir()); err != nil {
|
|
|
|
|
gologger.Fatal().Msgf("could not save generated keys to disk: %s\n", err)
|
|
|
|
|
}
|
|
|
|
|
// do not continue further let user re-run the command
|
|
|
|
|
os.Exit(0)
|
|
|
|
|
} else if err != nil && SkipGeneratingKeys {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := handler.ParseUserCert(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if err := handler.ParsePrivateKey(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &TemplateSigner{
|
|
|
|
|
handler: handler,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewTemplateSignerFromFiles creates a new signer for signing templates
|
|
|
|
|
func NewTemplateSignerFromFiles(cert, privKey string) (*TemplateSigner, error) {
|
|
|
|
|
certData, err := os.ReadFile(cert)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
privKeyData, err := os.ReadFile(privKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return NewTemplateSigner(certData, privKeyData)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewTemplateSigVerifier creates a new signer for verifying templates
|
|
|
|
|
func NewTemplateSigVerifier(cert []byte) (*TemplateSigner, error) {
|
|
|
|
|
handler := &KeyHandler{}
|
|
|
|
|
if cert != nil {
|
|
|
|
|
handler.UserCert = cert
|
|
|
|
|
} else {
|
|
|
|
|
if err := handler.ReadCert(CertEnvVarName, config.DefaultConfig.GetKeysDir()); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err := handler.ParseUserCert(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &TemplateSigner{
|
|
|
|
|
handler: handler,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|