Closer file structure to desired

This commit is contained in:
WeebDataHoarder
2025-04-06 01:46:18 +02:00
parent c222508e5c
commit 617ec804bc
28 changed files with 31 additions and 29 deletions

74
lib/challenge/generic.go Normal file
View File

@@ -0,0 +1,74 @@
//go:build !tinygo
package challenge
import (
"context"
"encoding/json"
"errors"
"github.com/tetratelabs/wazero/api"
)
func MakeChallengeCall(ctx context.Context, mod api.Module, in MakeChallengeInput) (*MakeChallengeOutput, error) {
makeChallengeFunc := mod.ExportedFunction("MakeChallenge")
malloc := mod.ExportedFunction("malloc")
free := mod.ExportedFunction("free")
inData, err := json.Marshal(in)
mallocResult, err := malloc.Call(ctx, uint64(len(inData)))
if err != nil {
return nil, err
}
defer free.Call(ctx, mallocResult[0])
if !mod.Memory().Write(uint32(mallocResult[0]), inData) {
return nil, errors.New("could not write memory")
}
result, err := makeChallengeFunc.Call(ctx, uint64(NewAllocation(uint32(mallocResult[0]), uint32(len(inData)))))
if err != nil {
return nil, err
}
resultPtr := Allocation(result[0])
outData, ok := mod.Memory().Read(resultPtr.Pointer(), resultPtr.Size())
if !ok {
return nil, errors.New("could not read result")
}
defer free.Call(ctx, uint64(resultPtr.Pointer()))
var out MakeChallengeOutput
err = json.Unmarshal(outData, &out)
if err != nil {
return nil, err
}
return &out, nil
}
func VerifyChallengeCall(ctx context.Context, mod api.Module, in VerifyChallengeInput) (VerifyChallengeOutput, error) {
verifyChallengeFunc := mod.ExportedFunction("VerifyChallenge")
malloc := mod.ExportedFunction("malloc")
free := mod.ExportedFunction("free")
inData, err := json.Marshal(in)
mallocResult, err := malloc.Call(ctx, uint64(len(inData)))
if err != nil {
return VerifyChallengeOutputError, err
}
defer free.Call(ctx, mallocResult[0])
if !mod.Memory().Write(uint32(mallocResult[0]), inData) {
return VerifyChallengeOutputError, errors.New("could not write memory")
}
result, err := verifyChallengeFunc.Call(ctx, uint64(NewAllocation(uint32(mallocResult[0]), uint32(len(inData)))))
if err != nil {
return VerifyChallengeOutputError, err
}
return VerifyChallengeOutput(result[0]), nil
}
func PtrToBytes(ptr uint32, size uint32) []byte { panic("not implemented") }
func BytesToPtr(s []byte) (uint32, uint32) { panic("not implemented") }
func BytesToLeakedPtr(s []byte) (uint32, uint32) { panic("not implemented") }
func PtrToString(ptr uint32, size uint32) string { panic("not implemented") }
func StringToPtr(s string) (uint32, uint32) { panic("not implemented") }
func StringToLeakedPtr(s string) (uint32, uint32) { panic("not implemented") }

View File

@@ -0,0 +1,95 @@
package inline
import (
"errors"
)
const (
hextable = "0123456789abcdef"
reverseHexTable = "" +
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\xff\xff\xff\xff\xff\xff" +
"\xff\x0a\x0b\x0c\x0d\x0e\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\xff\x0a\x0b\x0c\x0d\x0e\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
)
// EncodedLen returns the length of an encoding of n source bytes.
// Specifically, it returns n * 2.
func EncodedLen(n int) int { return n * 2 }
// Encode encodes src into [EncodedLen](len(src))
// bytes of dst. As a convenience, it returns the number
// of bytes written to dst, but this value is always [EncodedLen](len(src)).
// Encode implements hexadecimal encoding.
func Encode(dst, src []byte) int {
j := 0
for _, v := range src {
dst[j] = hextable[v>>4]
dst[j+1] = hextable[v&0x0f]
j += 2
}
return len(src) * 2
}
// ErrLength reports an attempt to decode an odd-length input
// using [Decode] or [DecodeString].
// The stream-based Decoder returns [io.ErrUnexpectedEOF] instead of ErrLength.
var ErrLength = errors.New("encoding/hex: odd length hex string")
// InvalidByteError values describe errors resulting from an invalid byte in a hex string.
type InvalidByteError byte
func (e InvalidByteError) Error() string {
return "encoding/hex: invalid byte"
}
// DecodedLen returns the length of a decoding of x source bytes.
// Specifically, it returns x / 2.
func DecodedLen(x int) int { return x / 2 }
// Decode decodes src into [DecodedLen](len(src)) bytes,
// returning the actual number of bytes written to dst.
//
// Decode expects that src contains only hexadecimal
// characters and that src has even length.
// If the input is malformed, Decode returns the number
// of bytes decoded before the error.
func Decode(dst, src []byte) (int, error) {
i, j := 0, 1
for ; j < len(src); j += 2 {
p := src[j-1]
q := src[j]
a := reverseHexTable[p]
b := reverseHexTable[q]
if a > 0x0f {
return i, InvalidByteError(p)
}
if b > 0x0f {
return i, InvalidByteError(q)
}
dst[i] = (a << 4) | b
i++
}
if len(src)%2 == 1 {
// Check for invalid char before reporting bad length,
// since the invalid char (if present) is an earlier problem.
if reverseHexTable[src[j-1]] > 0x0f {
return i, InvalidByteError(src[j-1])
}
return i, ErrLength
}
return i, nil
}

View File

@@ -0,0 +1,234 @@
package inline
// from textproto
// A MIMEHeader represents a MIME-style header mapping
// keys to sets of values.
type MIMEHeader map[string][]string
// Add adds the key, value pair to the header.
// It appends to any existing values associated with key.
func (h MIMEHeader) Add(key, value string) {
key = CanonicalMIMEHeaderKey(key)
h[key] = append(h[key], value)
}
// Set sets the header entries associated with key to
// the single element value. It replaces any existing
// values associated with key.
func (h MIMEHeader) Set(key, value string) {
h[CanonicalMIMEHeaderKey(key)] = []string{value}
}
// Get gets the first value associated with the given key.
// It is case insensitive; [CanonicalMIMEHeaderKey] is used
// to canonicalize the provided key.
// If there are no values associated with the key, Get returns "".
// To use non-canonical keys, access the map directly.
func (h MIMEHeader) Get(key string) string {
if h == nil {
return ""
}
v := h[CanonicalMIMEHeaderKey(key)]
if len(v) == 0 {
return ""
}
return v[0]
}
// Values returns all values associated with the given key.
// It is case insensitive; [CanonicalMIMEHeaderKey] is
// used to canonicalize the provided key. To use non-canonical
// keys, access the map directly.
// The returned slice is not a copy.
func (h MIMEHeader) Values(key string) []string {
if h == nil {
return nil
}
return h[CanonicalMIMEHeaderKey(key)]
}
// Del deletes the values associated with key.
func (h MIMEHeader) Del(key string) {
delete(h, CanonicalMIMEHeaderKey(key))
}
// CanonicalMIMEHeaderKey returns the canonical format of the
// MIME header key s. The canonicalization converts the first
// letter and any letter following a hyphen to upper case;
// the rest are converted to lowercase. For example, the
// canonical key for "accept-encoding" is "Accept-Encoding".
// MIME header keys are assumed to be ASCII only.
// If s contains a space or invalid header field bytes, it is
// returned without modifications.
func CanonicalMIMEHeaderKey(s string) string {
// Quick check for canonical encoding.
upper := true
for i := 0; i < len(s); i++ {
c := s[i]
if !validHeaderFieldByte(c) {
return s
}
if upper && 'a' <= c && c <= 'z' {
s, _ = canonicalMIMEHeaderKey([]byte(s))
return s
}
if !upper && 'A' <= c && c <= 'Z' {
s, _ = canonicalMIMEHeaderKey([]byte(s))
return s
}
upper = c == '-'
}
return s
}
const toLower = 'a' - 'A'
// validHeaderFieldByte reports whether c is a valid byte in a header
// field name. RFC 7230 says:
//
// header-field = field-name ":" OWS field-value OWS
// field-name = token
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
// token = 1*tchar
func validHeaderFieldByte(c byte) bool {
// mask is a 128-bit bitmap with 1s for allowed bytes,
// so that the byte c can be tested with a shift and an and.
// If c >= 128, then 1<<c and 1<<(c-64) will both be zero,
// and this function will return false.
const mask = 0 |
(1<<(10)-1)<<'0' |
(1<<(26)-1)<<'a' |
(1<<(26)-1)<<'A' |
1<<'!' |
1<<'#' |
1<<'$' |
1<<'%' |
1<<'&' |
1<<'\'' |
1<<'*' |
1<<'+' |
1<<'-' |
1<<'.' |
1<<'^' |
1<<'_' |
1<<'`' |
1<<'|' |
1<<'~'
return ((uint64(1)<<c)&(mask&(1<<64-1)) |
(uint64(1)<<(c-64))&(mask>>64)) != 0
}
// canonicalMIMEHeaderKey is like CanonicalMIMEHeaderKey but is
// allowed to mutate the provided byte slice before returning the
// string.
//
// For invalid inputs (if a contains spaces or non-token bytes), a
// is unchanged and a string copy is returned.
//
// ok is true if the header key contains only valid characters and spaces.
// ReadMIMEHeader accepts header keys containing spaces, but does not
// canonicalize them.
func canonicalMIMEHeaderKey(a []byte) (_ string, ok bool) {
if len(a) == 0 {
return "", false
}
// See if a looks like a header key. If not, return it unchanged.
noCanon := false
for _, c := range a {
if validHeaderFieldByte(c) {
continue
}
// Don't canonicalize.
if c == ' ' {
// We accept invalid headers with a space before the
// colon, but must not canonicalize them.
// See https://go.dev/issue/34540.
noCanon = true
continue
}
return string(a), false
}
if noCanon {
return string(a), true
}
upper := true
for i, c := range a {
// Canonicalize: first letter upper case
// and upper case after each dash.
// (Host, User-Agent, If-Modified-Since).
// MIME headers are ASCII only, so no Unicode issues.
if upper && 'a' <= c && c <= 'z' {
c -= toLower
} else if !upper && 'A' <= c && c <= 'Z' {
c += toLower
}
a[i] = c
upper = c == '-' // for next time
}
// The compiler recognizes m[string(byteSlice)] as a special
// case, so a copy of a's bytes into a new string does not
// happen in this map lookup:
if v := commonHeader[string(a)]; v != "" {
return v, true
}
return string(a), true
}
func init() {
initCommonHeader()
}
// commonHeader interns common header strings.
var commonHeader map[string]string
func initCommonHeader() {
commonHeader = make(map[string]string)
for _, v := range []string{
"Accept",
"Accept-Charset",
"Accept-Encoding",
"Accept-Language",
"Accept-Ranges",
"Cache-Control",
"Cc",
"Connection",
"Content-Id",
"Content-Language",
"Content-Length",
"Content-Transfer-Encoding",
"Content-Type",
"Cookie",
"Date",
"Dkim-Signature",
"Etag",
"Expires",
"From",
"Host",
"If-Modified-Since",
"If-None-Match",
"In-Reply-To",
"Last-Modified",
"Location",
"Message-Id",
"Mime-Version",
"Pragma",
"Received",
"Return-Path",
"Server",
"Set-Cookie",
"Subject",
"To",
"User-Agent",
"Via",
"X-Forwarded-For",
"X-Imforwards",
"X-Powered-By",
} {
commonHeader[v] = v
}
}

119
lib/challenge/interface.go Normal file
View File

@@ -0,0 +1,119 @@
package challenge
import (
"encoding/json"
"git.gammaspectra.live/git/go-away/lib/challenge/inline"
)
type MakeChallenge func(in Allocation) (out Allocation)
type Allocation uint64
func NewAllocation(ptr, size uint32) Allocation {
return Allocation((uint64(ptr) << uint64(32)) | uint64(size))
}
func (p Allocation) Pointer() uint32 {
return uint32(p >> 32)
}
func (p Allocation) Size() uint32 {
return uint32(p)
}
func MakeChallengeDecode(callback func(in MakeChallengeInput, out *MakeChallengeOutput), in Allocation) (out Allocation) {
outStruct := &MakeChallengeOutput{}
var inStruct MakeChallengeInput
inData := PtrToBytes(in.Pointer(), in.Size())
err := json.Unmarshal(inData, &inStruct)
if err != nil {
outStruct.Code = 500
outStruct.Error = err.Error()
} else {
outStruct.Code = 200
outStruct.Headers = make(inline.MIMEHeader)
func() {
// encapsulate err
defer func() {
if recovered := recover(); recovered != nil {
if outStruct.Code == 200 {
outStruct.Code = 500
}
if err, ok := recovered.(error); ok {
outStruct.Error = err.Error()
} else {
outStruct.Error = "error"
}
}
}()
callback(inStruct, outStruct)
}()
}
if len(outStruct.Headers) == 0 {
outStruct.Headers = nil
}
outData, err := json.Marshal(outStruct)
if err != nil {
panic(err)
}
return NewAllocation(BytesToLeakedPtr(outData))
}
func VerifyChallengeDecode(callback func(in VerifyChallengeInput) VerifyChallengeOutput, in Allocation) (out VerifyChallengeOutput) {
var inStruct VerifyChallengeInput
inData := PtrToBytes(in.Pointer(), in.Size())
err := json.Unmarshal(inData, &inStruct)
if err != nil {
return VerifyChallengeOutputError
} else {
func() {
// encapsulate err
defer func() {
if recovered := recover(); recovered != nil {
out = VerifyChallengeOutputError
}
}()
out = callback(inStruct)
}()
}
return out
}
type MakeChallengeInput struct {
Key []byte
Parameters map[string]string
Headers inline.MIMEHeader
Data []byte
}
type MakeChallengeOutput struct {
Data []byte
Code int
Headers inline.MIMEHeader
Error string
}
type VerifyChallengeInput struct {
Key []byte
Parameters map[string]string
Result []byte
}
type VerifyChallengeOutput uint64
const (
VerifyChallengeOutputOK = VerifyChallengeOutput(iota)
VerifyChallengeOutputFailed
VerifyChallengeOutputError
)

59
lib/challenge/tinygo.go Normal file
View File

@@ -0,0 +1,59 @@
//go:build tinygo
package challenge
// #include <stdlib.h>
import "C"
import (
"unsafe"
)
// PtrToBytes returns a byte slice from WebAssembly compatible numeric types
// representing its pointer and length.
func PtrToBytes(ptr uint32, size uint32) []byte {
return unsafe.Slice((*byte)(unsafe.Pointer(uintptr(ptr))), size)
}
// BytesToPtr returns a pointer and size pair for the given byte slice in a way
// compatible with WebAssembly numeric types.
// The returned pointer aliases the slice hence the slice must be kept alive
// until ptr is no longer needed.
func BytesToPtr(s []byte) (uint32, uint32) {
ptr := unsafe.Pointer(unsafe.SliceData(s))
return uint32(uintptr(ptr)), uint32(len(s))
}
// BytesToLeakedPtr returns a pointer and size pair for the given byte slice in a way
// compatible with WebAssembly numeric types.
// The pointer is not automatically managed by TinyGo hence it must be freed by the host.
func BytesToLeakedPtr(s []byte) (uint32, uint32) {
size := C.ulong(len(s))
ptr := unsafe.Pointer(C.malloc(size))
copy(unsafe.Slice((*byte)(ptr), size), s)
return uint32(uintptr(ptr)), uint32(size)
}
// PtrToString returns a string from WebAssembly compatible numeric types
// representing its pointer and length.
func PtrToString(ptr uint32, size uint32) string {
return unsafe.String((*byte)(unsafe.Pointer(uintptr(ptr))), size)
}
// StringToPtr returns a pointer and size pair for the given string in a way
// compatible with WebAssembly numeric types.
// The returned pointer aliases the string hence the string must be kept alive
// until ptr is no longer needed.
func StringToPtr(s string) (uint32, uint32) {
ptr := unsafe.Pointer(unsafe.StringData(s))
return uint32(uintptr(ptr)), uint32(len(s))
}
// StringToLeakedPtr returns a pointer and size pair for the given string in a way
// compatible with WebAssembly numeric types.
// The pointer is not automatically managed by TinyGo hence it must be freed by the host.
func StringToLeakedPtr(s string) (uint32, uint32) {
size := C.ulong(len(s))
ptr := unsafe.Pointer(C.malloc(size))
copy(unsafe.Slice((*byte)(ptr), size), s)
return uint32(uintptr(ptr)), uint32(size)
}

View File

@@ -9,7 +9,7 @@ import (
"encoding/hex"
"errors"
"fmt"
go_away "git.gammaspectra.live/git/go-away"
"git.gammaspectra.live/git/go-away/embed"
"git.gammaspectra.live/git/go-away/lib/network"
"git.gammaspectra.live/git/go-away/lib/policy"
"github.com/google/cel-go/common/types"
@@ -43,7 +43,7 @@ func init() {
templates = make(map[string]*template.Template)
dir, err := go_away.TemplatesFs.ReadDir("templates")
dir, err := embed.TemplatesFs.ReadDir("templates")
if err != nil {
panic(err)
}
@@ -51,7 +51,7 @@ func init() {
if e.IsDir() {
continue
}
data, err := go_away.TemplatesFs.ReadFile(filepath.Join("templates", e.Name()))
data, err := embed.TemplatesFs.ReadFile(filepath.Join("templates", e.Name()))
if err != nil {
panic(err)
}
@@ -373,7 +373,7 @@ func (state *State) setupRoutes() error {
state.Mux.HandleFunc("/", state.handleRequest)
state.Mux.Handle("GET "+state.UrlPath+"/assets/", http.StripPrefix(state.UrlPath, gzipped.FileServer(gzipped.FS(go_away.AssetsFs))))
state.Mux.Handle("GET "+state.UrlPath+"/assets/", http.StripPrefix(state.UrlPath, gzipped.FileServer(gzipped.FS(embed.AssetsFs))))
for challengeName, c := range state.Challenges {
if c.Static != nil {

View File

@@ -1,7 +1,7 @@
package lib
import (
go_away "git.gammaspectra.live/git/go-away"
"git.gammaspectra.live/git/go-away/embed"
"io"
"path"
"slices"
@@ -17,7 +17,7 @@ func (state *State) getPoison(mime string, encodings []string) (r io.ReadCloser,
}
p := path.Join("poison", strings.ReplaceAll(mime, "/", "_")+"."+encoding+".poison")
f, err := go_away.PoisonFs.Open(p)
f, err := embed.PoisonFs.Open(p)
if err == nil {
return f, encoding
}

View File

@@ -11,9 +11,9 @@ import (
"encoding/json"
"errors"
"fmt"
go_away "git.gammaspectra.live/git/go-away"
"git.gammaspectra.live/git/go-away/challenge"
"git.gammaspectra.live/git/go-away/challenge/inline"
"git.gammaspectra.live/git/go-away/embed"
challenge2 "git.gammaspectra.live/git/go-away/lib/challenge"
"git.gammaspectra.live/git/go-away/lib/challenge/inline"
"git.gammaspectra.live/git/go-away/lib/condition"
"git.gammaspectra.live/git/go-away/lib/policy"
"github.com/google/cel-go/cel"
@@ -101,7 +101,7 @@ type ChallengeState struct {
type StateSettings struct {
Debug bool
PackagePath string
PackageName string
ChallengeTemplate string
ChallengeTemplateTheme string
}
@@ -114,7 +114,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
return http.ErrUseLastResponse
},
}
state.UrlPath = "/.well-known/." + state.Settings.PackagePath
state.UrlPath = "/.well-known/." + state.Settings.PackageName
state.Backends = make(map[string]http.Handler)
@@ -198,7 +198,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
}
assetPath := c.Path + "/static/"
subFs, err := fs.Sub(go_away.ChallengeFs, fmt.Sprintf("challenge/%s/static", challengeName))
subFs, err := fs.Sub(embed.ChallengeFs, fmt.Sprintf("challenge/%s/static", challengeName))
if err == nil {
c.Static = http.StripPrefix(
assetPath,
@@ -458,7 +458,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
})
case "wasm":
wasmData, err := go_away.ChallengeFs.ReadFile(fmt.Sprintf("challenge/%s/runtime/%s", challengeName, p.Runtime.Asset))
wasmData, err := embed.ChallengeFs.ReadFile(fmt.Sprintf("challenge/%s/runtime/%s", challengeName, p.Runtime.Asset))
if err != nil {
return nil, fmt.Errorf("c %s: could not load runtime: %w", challengeName, err)
}
@@ -470,7 +470,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
c.MakeChallenge = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := state.ChallengeMod(challengeName, func(ctx context.Context, mod api.Module) (err error) {
in := challenge.MakeChallengeInput{
in := challenge2.MakeChallengeInput{
Key: state.GetChallengeKeyForRequest(challengeName, time.Now().UTC().Add(DefaultValidity).Round(DefaultValidity), r),
Parameters: p.Parameters,
Headers: inline.MIMEHeader(r.Header),
@@ -480,7 +480,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
return err
}
out, err := challenge.MakeChallengeCall(state.WasmContext, mod, in)
out, err := challenge2.MakeChallengeCall(state.WasmContext, mod, in)
if err != nil {
return err
}
@@ -502,21 +502,21 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
c.Verify = func(key []byte, result string) (ok bool, err error) {
err = state.ChallengeMod(challengeName, func(ctx context.Context, mod api.Module) (err error) {
in := challenge.VerifyChallengeInput{
in := challenge2.VerifyChallengeInput{
Key: key,
Parameters: p.Parameters,
Result: []byte(result),
}
out, err := challenge.VerifyChallengeCall(state.WasmContext, mod, in)
out, err := challenge2.VerifyChallengeCall(state.WasmContext, mod, in)
if err != nil {
return err
}
if out == challenge.VerifyChallengeOutputError {
if out == challenge2.VerifyChallengeOutputError {
return errors.New("error checking challenge")
}
ok = out == challenge.VerifyChallengeOutputOK
ok = out == challenge2.VerifyChallengeOutputOK
return nil
})
if err != nil {