diff --git a/OrbitalBeat.go b/OrbitalBeat.go index b2dffe5..b6db311 100644 --- a/OrbitalBeat.go +++ b/OrbitalBeat.go @@ -2,31 +2,20 @@ package main import ( "bytes" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" "database/sql" - "encoding/base32" "encoding/base64" - "encoding/binary" "encoding/hex" - "encoding/pem" "flag" "fmt" + "git.gammaspectra.live/S.O.N.G/MakyuuIchaival" + "git.gammaspectra.live/S.O.N.G/MakyuuIchaival/contentmessage" + "git.gammaspectra.live/S.O.N.G/MakyuuIchaival/httputils" + "git.gammaspectra.live/S.O.N.G/MakyuuIchaival/tlsutils" "github.com/cloudflare/circl/sign/ed25519" - "github.com/dgrr/http2" "github.com/ipfs/go-cid" _ "github.com/lib/pq" - "github.com/lucas-clemente/quic-go/http3" "github.com/multiformats/go-multihash" - "github.com/valyala/fasthttp" "log" - "math" - "math/big" - "net" "net/http" "net/url" "os" @@ -41,33 +30,18 @@ type ContentCacheEntry struct { AccessTime time.Time } -var base32Encoding = base32.NewEncoding("qpzry9x8gf2tvdw0s3jn54khce6mua7l").WithPadding(base32.NoPadding) - var dbHandle *sql.DB -var sniAddress string var sha256Statement *sql.Stmt var md5Statement *sql.Stmt var fdlimit int -var fsHandler fasthttp.RequestHandler var objectCacheMutex sync.RWMutex var objectCache = make(map[string]*ContentCacheEntry) -var messageCacheLimit int -var messageCacheMutex sync.RWMutex -var messageCache = make(map[string]*ContentMessage) - -var extraHeadersMutex sync.RWMutex -var extraHeaders = make(map[string]string) - var trustedPublicKeys []ed25519.PublicKey var debugOutput = false -func isSNIAllowed(sni string) bool { - return len(sniAddress) == 0 || sni == sniAddress -} - func getFirstValidContentEntry(entries *[]ContentEntry) *ContentEntry { for _, entry := range *entries { stat, err := os.Stat(entry.Path) @@ -109,7 +83,7 @@ func guessMimeType(p string) string { return "" } -func handleQueryRequest(ctx *RequestContext, identifier cid.Cid, extraArguments []string) { +func handleQueryRequest(ctx *httputils.RequestContext, identifier cid.Cid, extraArguments []string) { cTime := time.Now() var cacheEntry = tryGetCacheEntryForIdentifier(identifier) @@ -121,21 +95,21 @@ func handleQueryRequest(ctx *RequestContext, identifier cid.Cid, extraArguments if entry != nil { cacheEntry = getCacheEntryForContentEntry(entry, identifier) } - addServerTimingMetricInformational(ctx, "ec", "Content Cache MISS") + ctx.AddTimingInformational("ec", "Content Cache MISS") } else { - addServerTimingMetricInformational(ctx, "ec", "Content Cache HIT") + ctx.AddTimingInformational("ec", "Content Cache HIT") } pTime := cTime cTime = time.Now() - addServerTimingMetric(ctx, "e", "Content Entry", cTime.Sub(pTime)) + ctx.AddTiming("e", "Content Entry", cTime.Sub(pTime)) if cacheEntry == nil { var origin string - if len(ctx.getRequestHeader("Referer")) > 0 { - origin = ctx.getRequestHeader("Referer") - } else if len(ctx.getRequestHeader("Origin")) > 0 { - origin = ctx.getRequestHeader("Origin") + if len(ctx.GetRequestHeader("Referer")) > 0 { + origin = ctx.GetRequestHeader("Referer") + } else if len(ctx.GetRequestHeader("Origin")) > 0 { + origin = ctx.GetRequestHeader("Origin") } //Try to redirect back to origin @@ -148,64 +122,62 @@ func handleQueryRequest(ctx *RequestContext, identifier cid.Cid, extraArguments kind = "md5" } if kind != "" { - ctx.doRedirect(fmt.Sprintf("%s/%s/%s/%s", origin, kind, hex.EncodeToString(mh.Digest), strings.Join(extraArguments, "/")), http.StatusFound) + ctx.DoRedirect(fmt.Sprintf("%s/%s/%s/%s", origin, kind, hex.EncodeToString(mh.Digest), strings.Join(extraArguments, "/")), http.StatusFound) return } } - ctx.setResponseCode(http.StatusNotFound) + ctx.SetResponseCode(http.StatusNotFound) return } mh, _ := multihash.Decode(cacheEntry.Entry.Identifier.Hash()) - ctx.setResponseHeader("Accept-Ranges", "bytes") - ctx.setResponseHeader("X-Request-CID", identifier.String()) - ctx.setResponseHeader("ETag", fmt.Sprintf("\"%s\"", cacheEntry.Entry.Identifier.String())) + ctx.SetResponseHeader("Accept-Ranges", "bytes") + ctx.SetResponseHeader("X-Request-CID", identifier.String()) + ctx.SetResponseHeader("ETag", fmt.Sprintf("\"%s\"", cacheEntry.Entry.Identifier.String())) if mh.Code == multihash.SHA2_256 { - ctx.setResponseHeader("Digest", fmt.Sprintf("sha-256=%s", base64.StdEncoding.EncodeToString(mh.Digest))) + ctx.SetResponseHeader("Digest", fmt.Sprintf("sha-256=%s", base64.StdEncoding.EncodeToString(mh.Digest))) } - ctx.setResponseHeader("Cache-Control", "public, max-age=2592000, immutable") + ctx.SetResponseHeader("Cache-Control", "public, max-age=2592000, immutable") filename := path.Base(cacheEntry.Entry.Path) //TODO: setting to hide filename - ctx.setResponseHeader("Content-Disposition", fmt.Sprintf("inline; filename*=utf-8''%s", url.PathEscape(filename))) + ctx.SetResponseHeader("Content-Disposition", fmt.Sprintf("inline; filename*=utf-8''%s", url.PathEscape(filename))) pTime = cTime cTime = time.Now() - addServerTimingMetric(ctx, "s", "Content Serve", cTime.Sub(pTime)) + ctx.AddTiming("s", "Content Serve", cTime.Sub(pTime)) mime := guessMimeType(cacheEntry.Entry.Path) if len(mime) > 0 { - ctx.setResponseHeader("Content-Type", mime) + ctx.SetResponseHeader("Content-Type", mime) } - ctx.serveFile(cacheEntry.Entry.Path) + ctx.ServeFile(cacheEntry.Entry.Path) } -func setOtherHeaders(ctx *RequestContext) { - ctx.setResponseHeader("Server", "OrbitalBeat") - ctx.setResponseHeader("Vary", "Content-Encoding") - ctx.setResponseHeader("X-Content-Type-Options", "nosniff") +func setOtherHeaders(ctx *httputils.RequestContext) { + ctx.SetResponseHeader("Server", "OrbitalBeat") + ctx.SetResponseHeader("Vary", "Content-Encoding") + ctx.SetResponseHeader("X-Content-Type-Options", "nosniff") - extraHeadersMutex.RLock() - defer extraHeadersMutex.RUnlock() - for k, v := range extraHeaders { - ctx.setResponseHeader(k, v) + for k, v := range ctx.GetExtraHeaders() { + ctx.SetResponseHeader(k, v) } } -func setCORSHeaders(ctx *RequestContext) { - ctx.setResponseHeader("Access-Control-Allow-Credentials", "true") - ctx.setResponseHeader("Access-Control-Max-Age", "7200") //Firefox caps this to 86400, Chrome to 7200. Default is 5 seconds (!!!) - ctx.setResponseHeader("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS") - ctx.setResponseHeader("Access-Control-Allow-Headers", "DNT,ETag,Origin,Accept,Accept-Language,X-Requested-With,Range") - ctx.setResponseHeader("Access-Control-Allow-Origin", "*") - ctx.setResponseHeader("Access-Control-Expose-Headers", "*") +func setCORSHeaders(ctx *httputils.RequestContext) { + ctx.SetResponseHeader("Access-Control-Allow-Credentials", "true") + ctx.SetResponseHeader("Access-Control-Max-Age", "7200") //Firefox caps this to 86400, Chrome to 7200. Default is 5 seconds (!!!) + ctx.SetResponseHeader("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS") + ctx.SetResponseHeader("Access-Control-Allow-Headers", "DNT,ETag,Origin,Accept,Accept-Language,X-Requested-With,Range") + ctx.SetResponseHeader("Access-Control-Allow-Origin", "*") + ctx.SetResponseHeader("Access-Control-Expose-Headers", "*") //CORP, COEP, COOP - ctx.setResponseHeader("Cross-Origin-Embedder-Policy", "require-corp") - ctx.setResponseHeader("Cross-Origin-Resource-Policy", "cross-origin") - ctx.setResponseHeader("Cross-Origin-Opener-Policy", "unsafe-none") + ctx.SetResponseHeader("Cross-Origin-Embedder-Policy", "require-corp") + ctx.SetResponseHeader("Cross-Origin-Resource-Policy", "cross-origin") + ctx.SetResponseHeader("Cross-Origin-Opener-Policy", "unsafe-none") } func getCacheEntryForContentEntry(entry *ContentEntry, originalIdentifier cid.Cid) *ContentCacheEntry { @@ -269,228 +241,64 @@ func IsTrustedPublicKey(key ed25519.PublicKey) bool { return false } -func addServerTimingMetric(ctx *RequestContext, name string, desc string, d time.Duration) { - if d < 0 { - d = 0 - } - - ctx.addResponseHeader("Server-Timing", fmt.Sprintf("%d_%s;desc=\"%s\";dur=%.6F", ctx.TimingEvents, name, desc, float64(d.Nanoseconds())/1e6)) - ctx.TimingEvents++ -} - -func addServerTimingMetricInformational(ctx *RequestContext, name string, desc string) { - ctx.addResponseHeader("Server-Timing", fmt.Sprintf("%d_%s;desc=\"%s\"", ctx.TimingEvents, name, desc)) - ctx.TimingEvents++ -} - -type RequestContext struct { - fasthttp *fasthttp.RequestCtx - httpWriter *http.ResponseWriter - httpRequest *http.Request - connTime time.Time - requestTime time.Time - TimingEvents uint -} - -func NewRequestContextFromFastHttp(ctx *fasthttp.RequestCtx) RequestContext { - return RequestContext{ - fasthttp: ctx, - connTime: ctx.ConnTime(), - requestTime: ctx.Time(), - } -} - -func NewRequestContextFromHttp(w http.ResponseWriter, r *http.Request) RequestContext { - return RequestContext{ - httpWriter: &w, - httpRequest: r, - connTime: time.Now(), - requestTime: time.Now(), - } -} - -func (c *RequestContext) getPath() string { - if c.fasthttp != nil { - return string(c.fasthttp.Path()) - } else if c.httpRequest != nil { - return c.httpRequest.URL.Path - } - - return "" -} - -func (c *RequestContext) getConnectionTime() time.Time { - return c.connTime -} - -func (c *RequestContext) getRequestTime() time.Time { - return c.requestTime -} - -func (c *RequestContext) isFastHttp() bool { - return c.fasthttp != nil -} - -func (c *RequestContext) getTLSServerName() string { - if c.fasthttp != nil { - return c.fasthttp.TLSConnectionState().ServerName - } else if c.httpRequest != nil { - return c.httpRequest.TLS.ServerName - } - - return "" -} - -func (c *RequestContext) getRequestHeader(name string) string { - if c.fasthttp != nil { - return string(c.fasthttp.Request.Header.Peek(name)) - } else if c.httpRequest != nil { - return c.httpRequest.Header.Get(name) - } - - return "" -} - -func (c *RequestContext) getResponseHeader(name string) string { - if c.fasthttp != nil { - return string(c.fasthttp.Response.Header.Peek(name)) - } else if c.httpWriter != nil { - return (*c.httpWriter).Header().Get(name) - } - - return "" -} - -func (c *RequestContext) addResponseHeader(name string, value string) { - if c.fasthttp != nil { - c.fasthttp.Response.Header.Add(name, value) - } else if c.httpWriter != nil { - (*c.httpWriter).Header().Add(name, value) - } -} - -func (c *RequestContext) setResponseHeader(name string, value string) { - if c.fasthttp != nil { - c.fasthttp.Response.Header.Set(name, value) - } else if c.httpWriter != nil { - (*c.httpWriter).Header().Set(name, value) - } -} - -func (c *RequestContext) serveFile(path string) { - if c.fasthttp != nil { - c.fasthttp.Request.URI().Reset() - c.fasthttp.Request.URI().SetPath(path) - fsHandler(c.fasthttp) - } else if c.httpWriter != nil { - http.ServeFile(*c.httpWriter, c.httpRequest, path) - } -} - -func (c *RequestContext) setResponseCode(code int) { - if c.fasthttp != nil { - c.fasthttp.Response.SetStatusCode(code) - } else if c.httpWriter != nil { - (*c.httpWriter).WriteHeader(code) - } -} - -func (c *RequestContext) doRedirect(location string, code int) { - if c.fasthttp != nil { - c.fasthttp.Redirect(location, code) - } else if c.httpWriter != nil { - http.Redirect(*c.httpWriter, c.httpRequest, location, code) - } -} - -func (c *RequestContext) isGet() bool { - if c.fasthttp != nil { - return c.fasthttp.IsGet() - } else if c.httpRequest != nil { - return c.httpRequest.Method == "GET" - } - - return false -} - -func (c *RequestContext) isOptions() bool { - if c.fasthttp != nil { - return c.fasthttp.IsOptions() - } else if c.httpRequest != nil { - return c.httpRequest.Method == "OPTIONS" - } - - return false -} - -func (c *RequestContext) isHead() bool { - if c.fasthttp != nil { - return c.fasthttp.IsHead() - } else if c.httpRequest != nil { - return c.httpRequest.Method == "HEAD" - } - - return false -} - -func handle(ctx RequestContext) { - if len(ctx.getRequestHeader("Host")) > 0 && ctx.getRequestHeader("Host") == ctx.getTLSServerName() { //Prevents rebinding / DNS stuff - ctx.setResponseCode(http.StatusNotFound) - ctx.setResponseCode(http.StatusNotFound) +func handle(ctx *httputils.RequestContext) { + if len(ctx.GetRequestHeader("Host")) > 0 && ctx.GetRequestHeader("Host") == ctx.GetTLSServerName() { //Prevents rebinding / DNS stuff + ctx.SetResponseCode(http.StatusNotFound) + ctx.SetResponseCode(http.StatusNotFound) return } cTime := time.Now() - if ctx.isFastHttp() { - addServerTimingMetric(&ctx, "c", "Connection", ctx.getRequestTime().Sub(ctx.getConnectionTime())) + if ctx.IsFastHttp() { + ctx.AddTiming("c", "Connection", ctx.GetRequestTime().Sub(ctx.GetConnectionTime())) } - addServerTimingMetric(&ctx, "r", "Request Handler", cTime.Sub(ctx.getRequestTime())) + ctx.AddTiming("r", "Request Handler", cTime.Sub(ctx.GetRequestTime())) - if ctx.isGet() || ctx.isHead() { + if ctx.IsGet() || ctx.IsHead() { if debugOutput { - log.Printf("Serve %s", ctx.getPath()) + log.Printf("Serve %s", ctx.GetPath()) } - setOtherHeaders(&ctx) - setCORSHeaders(&ctx) - pathElements := strings.Split(ctx.getPath(), "/") + setOtherHeaders(ctx) + setCORSHeaders(ctx) + pathElements := strings.Split(ctx.GetPath(), "/") if len(pathElements) < 2 { - ctx.setResponseCode(http.StatusBadRequest) + ctx.SetResponseCode(http.StatusBadRequest) return } - messageBytes, err := base32Encoding.DecodeString(pathElements[1]) + messageBytes, err := MakyuuIchaival.Bech32Encoding.DecodeString(pathElements[1]) if err != nil { - ctx.setResponseCode(http.StatusBadRequest) + ctx.SetResponseCode(http.StatusBadRequest) return } - message := DecodeContentMessage(messageBytes) + message := contentmessage.DecodeContentMessage(messageBytes) if message == nil { - ctx.setResponseCode(http.StatusBadRequest) + ctx.SetResponseCode(http.StatusBadRequest) return } if !IsTrustedPublicKey(message.PublicKey) { - ctx.setResponseCode(http.StatusForbidden) + ctx.SetResponseCode(http.StatusForbidden) return } pTime := cTime cTime := time.Now() - addServerTimingMetric(&ctx, "d", "Decode", cTime.Sub(pTime)) + ctx.AddTiming("d", "Decode", cTime.Sub(pTime)) - result, cacheHit := message.verify() + result, cacheHit := message.Verify() pTime = cTime cTime = time.Now() if cacheHit { - addServerTimingMetricInformational(&ctx, "vc", "Ed25519 Cache HIT") + ctx.AddTimingInformational("vc", "Ed25519 Cache HIT") } else { - addServerTimingMetricInformational(&ctx, "vc", "Ed25519 Cache MISS") + ctx.AddTimingInformational("vc", "Ed25519 Cache MISS") } - addServerTimingMetric(&ctx, "v", "Ed25519 Verify", cTime.Sub(pTime)) + ctx.AddTiming("v", "Ed25519 Verify", cTime.Sub(pTime)) if !result { - ctx.setResponseCode(http.StatusForbidden) + ctx.SetResponseCode(http.StatusForbidden) return } @@ -498,176 +306,16 @@ func handle(ctx RequestContext) { log.Printf("Serving CID %s", message.Identifier.String()) } - handleQueryRequest(&ctx, message.Identifier, pathElements[2:]) - } else if ctx.isOptions() { - setOtherHeaders(&ctx) - setCORSHeaders(&ctx) - ctx.setResponseCode(http.StatusNoContent) + handleQueryRequest(ctx, message.Identifier, pathElements[2:]) + } else if ctx.IsOptions() { + setOtherHeaders(ctx) + setCORSHeaders(ctx) + ctx.SetResponseCode(http.StatusNoContent) } else { - ctx.setResponseCode(http.StatusNotImplemented) + ctx.SetResponseCode(http.StatusNotImplemented) } } -// ContentMessage TODO: move this to a library -type ContentMessage struct { - Version uint64 - PublicKey ed25519.PublicKey - IssueTime int64 - Identifier cid.Cid - VerificationResult *bool - Signature []byte -} - -func (s *ContentMessage) sign(privateKey ed25519.PrivateKey) { - s.PublicKey = make([]byte, ed25519.PublicKeySize) - copy(s.PublicKey, privateKey[32:]) - s.Signature = ed25519.Sign(privateKey, s.encodeMessage()) - s.VerificationResult = nil -} - -func (s *ContentMessage) verify() (bool, bool) { - currentTime := time.Now() - - issueTime := time.Unix(s.IssueTime, 0) - validityStart := issueTime.Add(-time.Hour) //Only one hour before time - validityEnd := issueTime.Add(time.Hour * 24) //Only 24 hours after time - - if validityStart.After(currentTime) { - return false, false - } - - if validityEnd.Before(currentTime) { - return false, false - } - - messageCacheMutex.RLock() - k := string(s.encode()) - cachedMessage, ok := messageCache[k] - messageCacheMutex.RUnlock() - if ok { - return *cachedMessage.VerificationResult, true - } - - messageCacheMutex.Lock() - defer messageCacheMutex.Unlock() - - if s.VerificationResult == nil { - makeBool := func(v bool) *bool { return &v } - s.VerificationResult = makeBool(ed25519.Verify(s.PublicKey, s.encodeMessage(), s.Signature)) - } - - if len(messageCache) >= messageCacheLimit { - //Find oldest value, remove it - var item *ContentMessage - for _, e := range messageCache { - if item == nil || e.IssueTime < item.IssueTime { - item = e - } - } - - if item != nil { - delete(messageCache, string(item.encode())) - } - } - - messageCache[k] = s - - return *s.VerificationResult, false -} - -func (s *ContentMessage) encodeMessage() []byte { - message := &bytes.Buffer{} - - buf := make([]byte, binary.MaxVarintLen64) - - n := binary.PutUvarint(buf, s.Version) //signature version - _, _ = message.Write(buf[:n]) - - if s.Version == 0 || s.Version == 1 { - _, _ = message.Write(s.PublicKey) - n = binary.PutVarint(buf, s.IssueTime) //time - _, _ = message.Write(buf[:n]) - - if s.Version == 1 { - _, _ = message.Write(s.Identifier.Hash()) - } else { - _, _ = s.Identifier.WriteBytes(message) - } - - return message.Bytes() - } - - return nil -} - -func (s *ContentMessage) encode() []byte { - message := s.encodeMessage() - - if message == nil || len(s.Signature) != ed25519.SignatureSize { - return nil - } - - return append(message, s.Signature...) -} - -func (s ContentMessage) String() string { - return fmt.Sprintf("%d %x %d %s %x", s.Version, s.PublicKey, s.IssueTime, s.Identifier.String(), s.Signature) -} - -func DecodeContentMessage(signatureBytes []byte) *ContentMessage { - message := ContentMessage{} - - buffer := bytes.NewBuffer(signatureBytes) - - var err error - - message.Version, err = binary.ReadUvarint(buffer) - if err != nil { - return nil - } - - if message.Version == 0 || message.Version == 1 { - message.PublicKey = make([]byte, ed25519.PublicKeySize) - _, err := buffer.Read(message.PublicKey) - if err != nil { - return nil - } - message.IssueTime, err = binary.ReadVarint(buffer) - if err != nil { - return nil - } - - if message.Version == 1 { - read, mh, err := multihash.MHFromBytes(buffer.Bytes()) - if err != nil { - return nil - } - buffer.Next(read) - message.Identifier = cid.NewCidV1(cid.Raw, mh) - } else { - _, message.Identifier, err = cid.CidFromReader(buffer) - if err != nil { - return nil - } - } - - message.Signature = make([]byte, ed25519.SignatureSize) - _, err = buffer.Read(message.Signature) - if err != nil { - return nil - } - - if buffer.Len() != 0 { //Unknown extra data - return nil - } - - return &message - - } - - return nil -} - type ContentEntry struct { Identifier cid.Cid Path string @@ -716,53 +364,6 @@ func getEntriesForCID(identifier cid.Cid) []ContentEntry { return []ContentEntry{} } -func createSelfSignedCertificate() ([]byte, []byte) { - serial, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) - x509Template := x509.Certificate{ - SerialNumber: serial, - Subject: pkix.Name{}, - NotBefore: time.Unix(0, 0).UTC(), - NotAfter: time.Date(time.Now().UTC().Year()+10, 0, 0, 0, 0, 0, 0, time.UTC), - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } - - privateBogusKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - log.Fatal(err) - } - - certBytes, err := x509.CreateCertificate(rand.Reader, &x509Template, &x509Template, privateBogusKey.Public(), privateBogusKey) - if err != nil { - log.Fatal(err) - } - - keyBytes, err := x509.MarshalPKCS8PrivateKey(privateBogusKey) - if err != nil { - log.Fatal(err) - } - - return pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: certBytes, - }), - pem.EncodeToMemory(&pem.Block{ - Type: "PRIVATE KEY", - Bytes: keyBytes, - }) -} - -func processCertificate(c *tls.Certificate) { - if c.Leaf == nil { - leaf, err := x509.ParseCertificate(c.Certificate[0]) - if err != nil { - return - } - c.Leaf = leaf - } -} - func main() { //TODO: OCSP certificatePath := flag.String("certificate", "", "Path to SSL certificate file.") @@ -789,7 +390,7 @@ func main() { flag.Parse() fdlimit = *fdLimitOption - messageCacheLimit = *signatureCacheLimitOption + contentmessage.SetMessageCacheLimit(*signatureCacheLimitOption) debugOutput = *debugOption @@ -797,7 +398,7 @@ func main() { for _, k := range strings.Split(*trustedKeys, ",") { var publicKey ed25519.PublicKey - publicKey, err = base32Encoding.DecodeString(strings.Trim(k, " ")) + publicKey, err = MakyuuIchaival.Bech32Encoding.DecodeString(strings.Trim(k, " ")) if err != nil { log.Fatal(err) } @@ -830,159 +431,20 @@ func main() { //TODO: create postgres tables - sniAddress = strings.ToLower(*sniAddressOption) - - bogusCertificatePEM, bogusKeyPairPEM := createSelfSignedCertificate() - - bogusCertificate, err := tls.X509KeyPair(bogusCertificatePEM, bogusKeyPairPEM) + tlsConfiguration, err := tlsutils.NewTLSConfiguration(*certificatePath, *keypairPath, strings.ToLower(*sniAddressOption)) if err != nil { log.Fatal(err) } - var serverCertificate tls.Certificate - - if *certificatePath != "" && *keypairPath != "" { - serverCertificate, err = tls.LoadX509KeyPair(*certificatePath, *keypairPath) - if err != nil { - log.Fatal(err) - } - } else { - serverCertificate = bogusCertificate - } - processCertificate(&bogusCertificate) - processCertificate(&serverCertificate) - - fs := fasthttp.FS{ - Root: "/", - AcceptByteRange: true, - Compress: false, - CompressBrotli: false, - CacheDuration: time.Minute * 15, - PathRewrite: func(ctx *fasthttp.RequestCtx) []byte { - return ctx.Request.URI().PathOriginal() - }, + server := &httputils.Server{ + ListenAddress: *listenAddress, + TLSConfig: tlsConfiguration, + EnableHTTP2: *http2Option, + EnableHTTP3: *http3Option, + Handler: handle, + Debug: debugOutput, } - fsHandler = fs.NewRequestHandler() + server.Serve() - tlsConfig := &tls.Config{ - MinVersion: tls.VersionTLS12, - MaxVersion: 0, //max supported, currently TLS 1.3 - CurvePreferences: []tls.CurveID{ - tls.X25519, - tls.CurveP256, - tls.CurveP384, - }, - CipherSuites: []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - }, - SessionTicketsDisabled: false, - NextProtos: []string{ - "http/1.1", - }, - GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { - if isSNIAllowed(info.ServerName) { - return &serverCertificate, nil - } - return &bogusCertificate, nil - }, - } - - if serverCertificate.Leaf.PublicKeyAlgorithm == x509.RSA || bogusCertificate.Leaf.PublicKeyAlgorithm == x509.RSA { - tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, []uint16{ - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - }...) - } - - if *http2Option { - tlsConfig.NextProtos = []string{ - "h2", - "http/1.1", - } - } else { - extraHeaders["Connection"] = "close" - } - - var wg sync.WaitGroup - - wg.Add(1) - go func(wg *sync.WaitGroup) { - defer wg.Done() - - server := &fasthttp.Server{ - ReadTimeout: 5 * time.Second, - IdleTimeout: 15 * time.Second, - Handler: func(ctx *fasthttp.RequestCtx) { - handle(NewRequestContextFromFastHttp(ctx)) - }, - NoDefaultServerHeader: true, - NoDefaultDate: true, - DisableKeepalive: !*http2Option, - TCPKeepalive: *http2Option, - TLSConfig: tlsConfig, - } - - if *http2Option { - http2.ConfigureServer(server, http2.ServerConfig{ - Debug: false, - }) - } - - log.Printf("Serving TCP on %s", *listenAddress) - ln, err := net.Listen("tcp", *listenAddress) - if err != nil { - log.Panic(err) - } - defer ln.Close() - err = server.Serve(tls.NewListener(ln, server.TLSConfig.Clone())) - if err != nil { - log.Panic(err) - } - }(&wg) - - if *http3Option { - - wg.Add(1) - go func(wg *sync.WaitGroup) { - defer wg.Done() - - server := &http3.Server{ - Server: &http.Server{ - Addr: *listenAddress, - TLSConfig: &tls.Config{ - GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { - if isSNIAllowed(info.ServerName) { - return &serverCertificate, nil - } - return &bogusCertificate, nil - }, - }, - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if debugOutput { - log.Print("Received HTTP/3 request") - } - handle(NewRequestContextFromHttp(w, r)) - }), - }, - } - - h := http.Header{} - server.SetQuicHeaders(h) - extraHeadersMutex.Lock() - extraHeaders["Alt-Svc"] = h.Get("Alt-Svc") - extraHeadersMutex.Unlock() - - log.Printf("Serving UDP on %s", *listenAddress) - err = server.ListenAndServe() - if err != nil { - log.Panic(err) - } - }(&wg) - - } - wg.Wait() } diff --git a/go.mod b/go.mod index 643e7bc..43112e8 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,22 @@ module git.gammaspectra.live/S.O.N.G/OrbitalBeat go 1.17 require ( + git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220118183219-7bfcd667a183 github.com/cloudflare/circl v1.1.0 - github.com/dgrr/http2 v0.3.3 github.com/ipfs/go-cid v0.1.0 github.com/lib/pq v1.10.4 - github.com/lucas-clemente/quic-go v0.25.0 github.com/multiformats/go-multihash v0.1.0 - github.com/valyala/fasthttp v1.32.0 ) require ( github.com/andybalholm/brotli v1.0.2 // indirect github.com/cheekybits/genny v1.0.0 // indirect + github.com/dgrr/http2 v0.3.3 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/klauspost/compress v1.13.4 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/lucas-clemente/quic-go v0.25.0 // indirect github.com/marten-seemann/qpack v0.2.1 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect @@ -34,6 +34,7 @@ require ( github.com/onsi/ginkgo v1.16.4 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.32.0 // indirect github.com/valyala/fastrand v1.0.0 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect golang.org/x/mod v0.4.2 // indirect diff --git a/go.sum b/go.sum index 909f5a0..ab42518 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220118183219-7bfcd667a183 h1:wF+oxs88n5p2O5ixy/vJN4KYefYOgwVmx+8a0xB7TPc= +git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220118183219-7bfcd667a183/go.mod h1:z6KcP5RPhMxDJaVU48sBhiYRCJ6ZJBbx1iIhkUrrhfY= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E= github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=