webhookd/tooling/httpsig/main.go
2023-10-03 18:02:57 +00:00

119 lines
2.5 KiB
Go

package main
import (
"bufio"
"bytes"
"crypto/x509"
"encoding/pem"
"flag"
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
"time"
"github.com/go-fed/httpsig"
configflag "github.com/ncarlier/webhookd/pkg/config/flag"
)
type config struct {
KeyID string `flag:"key-id" desc:"Signature key ID"`
KeyFile string `flag:"key-file" desc:"Private key file (PEM format)" default:"./key.pem"`
JSON string `flag:"json" desc:"JSON payload"`
}
func main() {
conf := &config{}
configflag.Bind(conf, "HTTP_SIG")
flag.Parse()
if conf.KeyID == "" {
log.Fatal("missing key ID")
}
args := flag.Args()
if len(args) <= 0 {
log.Fatal("missing target URL")
}
targetURL := args[0]
if _, err := url.Parse(targetURL); err != nil {
log.Fatal("invalid target URL")
}
keyBytes, err := os.ReadFile(conf.KeyFile)
if err != nil {
log.Fatal(err.Error())
}
pemBlock, _ := pem.Decode(keyBytes)
if pemBlock == nil {
log.Fatal("invalid PEM format")
}
privateKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
if err != nil {
log.Fatal(err.Error())
}
var payload io.Reader
var jsonBytes []byte
if conf.JSON != "" {
var err error
jsonBytes, err = os.ReadFile(conf.JSON)
if err != nil {
log.Fatal(err.Error())
}
payload = bytes.NewReader(jsonBytes)
}
prefs := []httpsig.Algorithm{httpsig.RSA_SHA256}
digestAlgorithm := httpsig.DigestSha256
headers := []string{httpsig.RequestTarget, "date"}
signer, _, err := httpsig.NewSigner(prefs, digestAlgorithm, headers, httpsig.Signature, 0)
if err != nil {
log.Fatal(err.Error())
}
req, err := http.NewRequest("POST", targetURL, payload)
if err != nil {
log.Fatal(err.Error())
}
if payload != nil {
req.Header.Add("content-type", "application/json")
}
req.Header.Add("date", time.Now().UTC().Format(http.TimeFormat))
if err = signer.SignRequest(privateKey, conf.KeyID, req, jsonBytes); err != nil {
log.Fatal(err.Error())
}
dump, err := httputil.DumpRequest(req, true)
if err != nil {
log.Fatal(err.Error())
}
scanner := bufio.NewScanner(strings.NewReader(string(dump)))
for scanner.Scan() {
fmt.Println(">", scanner.Text())
}
client := &http.Client{Timeout: 10 * time.Second}
res, err := client.Do(req)
if err != nil {
log.Fatal(err.Error())
}
dump, err = httputil.DumpResponse(res, true)
if err != nil {
log.Fatal(err.Error())
}
scanner = bufio.NewScanner(strings.NewReader(string(dump)))
for scanner.Scan() {
fmt.Println("<", scanner.Text())
}
}