Skip to content

Commit

Permalink
refactor download support into "additional header" support
Browse files Browse the repository at this point in the history
  • Loading branch information
dropwhile committed Aug 21, 2023
1 parent 329b6b8 commit f026bbe
Show file tree
Hide file tree
Showing 10 changed files with 379 additions and 135 deletions.
9 changes: 0 additions & 9 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,5 @@ jobs:
with:
languages: go

- name: Setup Go ${{ matrix.goVer }}
uses: actions/setup-go@v1
with:
go-version: '1.17.x'
id: go

- name: build
run: make build

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
4 changes: 2 additions & 2 deletions cmd/go-camo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func main() {
AllowContentVideo bool `long:"allow-content-video" description:"Additionally allow 'video/*' content"`
AllowContentAudio bool `long:"allow-content-audio" description:"Additionally allow 'audio/*' content"`
AllowCredentialURLs bool `long:"allow-credential-urls" description:"Allow urls to contain user/pass credentials"`
EnableDownloadParam bool `long:"enable-download-param" description:"Enable support for forced content disposition attachment with ?download query param"`
EnableAddHeaders bool `long:"enable-additional-headers" description:"Enable support for additional headers as part of hmac signed url data"`
FilterRuleset string `long:"filter-ruleset" description:"Text file containing filtering rules (one per line)"`
ServerName string `long:"server-name" default:"go-camo" description:"Value to use for the HTTP server field"`
ExposeServerVersion bool `long:"expose-server-version" description:"Include the server version in the HTTP server response header"`
Expand Down Expand Up @@ -242,7 +242,7 @@ func main() {
// other options
config.EnableXFwdFor = opts.EnableXFwdFor
config.AllowCredentialURLs = opts.AllowCredentialURLs
config.EnableDownloadParam = opts.EnableDownloadParam
config.EnableAddHeaders = opts.EnableAddHeaders

// additional content types to allow
config.AllowContentVideo = opts.AllowContentVideo
Expand Down
34 changes: 29 additions & 5 deletions cmd/url-tool/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,26 @@ func (c *EncodeCommand) Execute(args []string) error {

hmacKeyBytes := []byte(opts.HmacKey)
var outURL string
var err error
switch c.Base {
case "base64":
outURL = encoding.B64EncodeURL(hmacKeyBytes, c.Positional.Url)
outURL, err = encoding.B64EncodeURL(hmacKeyBytes, c.Positional.Url, nil)
case "hex":
outURL = encoding.HexEncodeURL(hmacKeyBytes, c.Positional.Url)
outURL, err = encoding.HexEncodeURL(hmacKeyBytes, c.Positional.Url, nil)
default:
return errors.New("invalid base provided")
}
if err != nil {
return err
}
fmt.Println(strings.TrimRight(c.Prefix, "/") + outURL)
return nil
}

// DecodeCommand holds command options for the decode command
type DecodeCommand struct {
Positional struct {
PrintHeaders bool `short:"x" long:"print-headers" description:"Print any encoded addition headers, if present"`
Positional struct {
Url string `positional-arg-name:"URL"`
} `positional-args:"yes" required:"true"`
}
Expand All @@ -73,12 +78,31 @@ func (c *DecodeCommand) Execute(args []string) error {
if err != nil {
return err
}
comp := strings.SplitN(u.Path, "/", 3)
decURL, valid := encoding.DecodeURL(hmacKeyBytes, comp[1], comp[2])
comp := strings.Split(u.Path, "/")
if len(comp) < 3 || len(comp) > 4 {
return errors.New("bad url provided")
}

encAddHdr := ""
if len(comp) == 4 {
encAddHdr = comp[3]
}
decURL, addHdr, valid := encoding.DecodeURL(hmacKeyBytes, comp[1], comp[2], encAddHdr)
if !valid {
return errors.New("hmac is invalid")
}
fmt.Println(decURL)
if c.PrintHeaders {
if len(addHdr) > 0 {
fmt.Println("Additional Headers: ")
for k, values := range addHdr {
fmt.Printf(" %s:\n", k)
for _, v := range values {
fmt.Printf(" %s\n", v)
}
}
}
}
return nil
}

Expand Down
177 changes: 143 additions & 34 deletions pkg/camo/encoding/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,34 @@ import (
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strings"

"github.com/cactus/mlog"
)

// DecoderFunc is a function type that defines a url decoder.
type DecoderFunc func([]byte, string, string) (string, error)
type DecoderFunc func([]byte, string, string, string) (string, http.Header, error)

// EncoderFunc is a function type that defines a url encoder.
type EncoderFunc func([]byte, string) string
type EncoderFunc func([]byte, string, http.Header) (string, error)

func validateURL(hmackey *[]byte, macbytes *[]byte, urlbytes *[]byte) error {
func validateURL(hmackey *[]byte, macBytes *[]byte, urlBytes *[]byte, addHdrBytes *[]byte) error {
mac := hmac.New(sha1.New, *hmackey)
mac.Write(*urlbytes) // #nosec G104 -- doesn't apply to hmac
mac.Write(*urlBytes) // #nosec G104 -- doesn't apply to hmac
if addHdrBytes != nil && len(*addHdrBytes) != 0 {
mac.Write(*addHdrBytes) // #nosec G104 -- doesn't apply to hmac
}
macSum := mac.Sum(nil)

// ensure lengths are equal. if not, return false
if len(macSum) != len(*macbytes) {
if len(macSum) != len(*macBytes) {
return fmt.Errorf("mismatched length")
}

if subtle.ConstantTimeCompare(macSum, *macbytes) != 1 {
if subtle.ConstantTimeCompare(macSum, *macBytes) != 1 {
return fmt.Errorf("invalid mac")
}
return nil
Expand All @@ -51,86 +56,190 @@ func b64decode(str string) ([]byte, error) {
return decBytes, ok
}

func addHdrUnmarshal(addHdrBytes []byte) (http.Header, error) {
var headers http.Header
err := json.Unmarshal(addHdrBytes, &headers)
if err != nil {
return nil, err
}
return headers, nil
}

func addHdrMarshal(headers http.Header) ([]byte, error) {
headerBytes, err := json.Marshal(headers)
if err != nil {
return nil, err
}
return headerBytes, nil
}

// HexDecodeURL ensures the url is properly verified via HMAC, and then
// unencodes the url, returning the url (if valid) and whether the
// HMAC was verified.
func HexDecodeURL(hmackey []byte, hexdig string, hexURL string) (string, error) {
urlBytes, err := hex.DecodeString(hexURL)
func HexDecodeURL(hmackey []byte, encDig, encURL, encAddHdr string) (string, http.Header, error) {
macBytes, err := hex.DecodeString(encDig)
if err != nil {
return "", fmt.Errorf("bad url decode")
return "", nil, fmt.Errorf("bad mac decode")
}
macBytes, err := hex.DecodeString(hexdig)

urlBytes, err := hex.DecodeString(encURL)
if err != nil {
return "", fmt.Errorf("bad mac decode")
return "", nil, fmt.Errorf("bad url decode")
}

if err = validateURL(&hmackey, &macBytes, &urlBytes); err != nil {
return "", fmt.Errorf("invalid signature: %s", err)
var addHdrBytes []byte
hasAddHdr := false
if len(encAddHdr) > 0 {
var err error
addHdrBytes, err = hex.DecodeString(encAddHdr)
if err != nil {
return "", nil, fmt.Errorf("bad additional data decode")
}
hasAddHdr = true
}
return string(urlBytes), nil

if err = validateURL(&hmackey, &macBytes, &urlBytes, &addHdrBytes); err != nil {
return "", nil, fmt.Errorf("invalid signature: %s", err)
}

var addHdr http.Header
if hasAddHdr {
var err error
// !!only unmarshal _after_ hmac validation!!
addHdr, err = addHdrUnmarshal(addHdrBytes)
if err != nil {
return "", nil, fmt.Errorf("bad additional data unmarshal")
}
}
return string(urlBytes), addHdr, nil
}

// HexEncodeURL takes an HMAC key and a url, and returns url
// path partial consisitent of signature and encoded url.
func HexEncodeURL(hmacKey []byte, oURL string) string {
oBytes := []byte(oURL)
func HexEncodeURL(hmacKey []byte, oURL string, oAddHdr http.Header) (string, error) {
oURLBytes := []byte(oURL)

var oAddHdrBytes []byte
hasAddHdr := false
if len(oAddHdr) > 0 {
var err error
oAddHdrBytes, err = addHdrMarshal(oAddHdr)
if err != nil {
return "", err
}
if len(oAddHdrBytes) > 0 {
hasAddHdr = true
}
}

mac := hmac.New(sha1.New, hmacKey)
mac.Write(oBytes) // #nosec G104 -- doesn't apply to hmac
mac.Write(oURLBytes) // #nosec G104 -- doesn't apply to hmac
if hasAddHdr {
mac.Write(oAddHdrBytes) // #nosec G104 -- doesn't apply to hmac
}
macSum := hex.EncodeToString(mac.Sum(nil))
encodedURL := hex.EncodeToString(oBytes)
hexURL := "/" + macSum + "/" + encodedURL
return hexURL
encodedURL := hex.EncodeToString(oURLBytes)
encURL := "/" + macSum + "/" + encodedURL
if hasAddHdr {
encAddHdr := hex.EncodeToString(oAddHdrBytes)
encURL = encURL + "/" + encAddHdr
}
return encURL, nil
}

// B64DecodeURL ensures the url is properly verified via HMAC, and then
// unencodes the url, returning the url (if valid) and whether the
// HMAC was verified.
func B64DecodeURL(hmackey []byte, encdig string, encURL string) (string, error) {
urlBytes, err := b64decode(encURL)
func B64DecodeURL(hmackey []byte, encDig, encURL, encAddHdr string) (string, http.Header, error) {
macBytes, err := b64decode(encDig)
if err != nil {
return "", fmt.Errorf("bad url decode")
return "", nil, fmt.Errorf("bad mac decode")
}
macBytes, err := b64decode(encdig)

urlBytes, err := b64decode(encURL)
if err != nil {
return "", fmt.Errorf("bad mac decode")
return "", nil, fmt.Errorf("bad url decode")
}

var addHdrBytes []byte
hasAddHdr := false
if len(encAddHdr) > 0 {
var err error
addHdrBytes, err = hex.DecodeString(encAddHdr)
if err != nil {
return "", nil, fmt.Errorf("bad additional data decode")
}
hasAddHdr = true
}

if err := validateURL(&hmackey, &macBytes, &urlBytes); err != nil {
return "", fmt.Errorf("invalid signature: %s", err)
if err := validateURL(&hmackey, &macBytes, &urlBytes, &addHdrBytes); err != nil {
return "", nil, fmt.Errorf("invalid signature: %s", err)
}
return string(urlBytes), nil

// !!only unmarshal _after_ hmac validation!!
var addHdr http.Header
if hasAddHdr {
var err error
// !!only unmarshal _after_ hmac validation!!
addHdr, err = addHdrUnmarshal(addHdrBytes)
if err != nil {
return "", nil, fmt.Errorf("bad additional data unmarshal")
}
}
return string(urlBytes), addHdr, nil
}

// B64EncodeURL takes an HMAC key and a url, and returns url
// path partial consisitent of signature and encoded url.
func B64EncodeURL(hmacKey []byte, oURL string) string {
func B64EncodeURL(hmacKey []byte, oURL string, oAddHdr http.Header) (string, error) {
oBytes := []byte(oURL)

var oAddHdrBytes []byte
hasAddHdr := false
if len(oAddHdr) > 0 {
var err error
oAddHdrBytes, err = addHdrMarshal(oAddHdr)
if err != nil {
return "", err
}
if len(oAddHdrBytes) > 0 {
hasAddHdr = true
}
}

mac := hmac.New(sha1.New, hmacKey)
mac.Write(oBytes) // #nosec G104 -- doesn't apply to hmac
if hasAddHdr {
mac.Write(oAddHdrBytes) // #nosec G104 -- doesn't apply to hmac
}
macSum := b64encode(mac.Sum(nil))
encodedURL := b64encode(oBytes)
encURL := "/" + macSum + "/" + encodedURL
return encURL
if hasAddHdr {
encAddHdr := hex.EncodeToString(oAddHdrBytes)
encURL = encURL + "/" + encAddHdr
}
return encURL, nil
}

// DecodeURL ensures the url is properly verified via HMAC, and then
// unencodes the url, returning the url (if valid) and whether the
// HMAC was verified. Tries either HexDecode or B64Decode, depending on the
// length of the encoded hmac.
func DecodeURL(hmackey []byte, encdig string, encURL string) (string, bool) {
func DecodeURL(hmackey []byte, encdig, encURL, encAddHdr string) (string, http.Header, bool) {
var decoder DecoderFunc
if len(encdig) == 40 {
decoder = HexDecodeURL
} else {
decoder = B64DecodeURL
}

urlBytes, err := decoder(hmackey, encdig, encURL)
urlBytes, addHdr, err := decoder(hmackey, encdig, encURL, encAddHdr)
if err != nil {
if mlog.HasDebug() {
mlog.Debugf("Bad Decode of URL: %s", err)
}
return "", false
return "", nil, false
}
return urlBytes, true
return urlBytes, addHdr, true
}
Loading

0 comments on commit f026bbe

Please sign in to comment.