Initial version for #15
This commit is contained in:
parent
84f23f93a1
commit
d6a5258640
|
@ -0,0 +1 @@
|
||||||
|
/examples/httpaltsvc/tor-data
|
|
@ -7,3 +7,4 @@ The following examples are in this directory:
|
||||||
* [embeddedversion](embeddedversion) - Example showing how to dump the version of Tor embedded in the binary
|
* [embeddedversion](embeddedversion) - Example showing how to dump the version of Tor embedded in the binary
|
||||||
* [embeddedfileserver](embeddedfileserver) - Example showing a file server using Tor embedded in the binary
|
* [embeddedfileserver](embeddedfileserver) - Example showing a file server using Tor embedded in the binary
|
||||||
* [grpc](grpc) - Example showing how to use gRPC over Tor
|
* [grpc](grpc) - Example showing how to use gRPC over Tor
|
||||||
|
* [httpaltsvc](httpaltsvc) - Example showing how to use .onion address as `Alt-Svc` of regular website (in development)
|
|
@ -0,0 +1,32 @@
|
||||||
|
## HTTP Alt-Svc Example
|
||||||
|
|
||||||
|
The Tor browser now supports `Alt-Svc` headers to be onion services as
|
||||||
|
[HTTP alternate services](https://tools.ietf.org/html/rfc7838) to traditional sites.
|
||||||
|
[This Cloudflare post](https://blog.cloudflare.com/cloudflare-onion-service/) explains how they use it. This example
|
||||||
|
shows how to do it yourself. Since
|
||||||
|
|
||||||
|
Specifically, this example listens on all IPs on 80 for insecure HTTP requests. It also listens on an onion service for
|
||||||
|
insecure HTTP requests. Both services return `Alt-Svc` addresses to an onion service that is run securely.
|
||||||
|
|
||||||
|
**NOTE: Only do the steps in this example if you are comfortable with and aware of the consequences.**
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
We're going to self-sign a certificate for use in the Tor browser. First, download `mkcert` and run:
|
||||||
|
|
||||||
|
mkcert -install
|
||||||
|
|
||||||
|
This will install a fake CA on your local machine that certs can be generated from. Remember the path it says the local
|
||||||
|
CA is at or run `mkcert -CAROOT` to get it back. We will use this "CA path" later.
|
||||||
|
|
||||||
|
Now the CA must be added to the Tor browser. By default the Tor browser doesn't use the cert database so it cannot store
|
||||||
|
the overrides. To change this, go to `about:config` click through warning and change `security.nocertdb` to `false`. Now
|
||||||
|
that CAs can be added, go to `Options` (i.e. `about:preferences`) > `Privacy & Security` > `Certificates` section at the
|
||||||
|
bottom > `View Certificates...` > `Authorities` tab > `Import...` > choose `rootCA.pem` from the "CA path" from
|
||||||
|
earlier > check "Trust this CA to identify websites" and click `OK`. Restart the Tor browser.
|
||||||
|
|
||||||
|
### Running
|
||||||
|
|
||||||
|
Point yor DNS to this machine's IP (or use start `ngrok http 80` via [ngrok.com](https://ngrok.com/))
|
||||||
|
|
||||||
|
TODO: the rest...
|
|
@ -0,0 +1,187 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cretz/bine/tor"
|
||||||
|
"github.com/cretz/bine/torutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var verbose bool
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.BoolVar(&verbose, "verbose", false, "Whether to have verbose logging")
|
||||||
|
flag.Parse()
|
||||||
|
if flag.NArg() != 1 {
|
||||||
|
log.Fatal("Expecting single domain arg")
|
||||||
|
} else if err := run(flag.Arg(0)); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(domain string) error {
|
||||||
|
ctx, cancelFn := context.WithCancel(context.Background())
|
||||||
|
defer cancelFn()
|
||||||
|
// Make sure mkcert is available
|
||||||
|
if _, err := exec.LookPath("mkcert"); err != nil {
|
||||||
|
return fmt.Errorf("Unable to find mkcert on PATH: %v", err)
|
||||||
|
}
|
||||||
|
// Listen until enter pressed
|
||||||
|
srv, err := start(ctx, domain, ":80")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srv.Close()
|
||||||
|
fmt.Printf("Listening on all IPs on port 80, so http://%v will use second onion as alt-svc\n", domain)
|
||||||
|
fmt.Printf("Listening on onion http://%v.onion that will use second onion as alt-svc\n", srv.onion1.ID)
|
||||||
|
fmt.Printf("Created secure second onion at https://%v.onion\n", srv.onion2.ID)
|
||||||
|
fmt.Println("Press enter to exit")
|
||||||
|
// Wait for key asynchronously
|
||||||
|
go func() {
|
||||||
|
fmt.Scanln()
|
||||||
|
cancelFn()
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case err := <-srv.Err():
|
||||||
|
return err
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type server struct {
|
||||||
|
exitAddrs map[string]bool
|
||||||
|
t *tor.Tor
|
||||||
|
onion1 *tor.OnionService
|
||||||
|
onion2 *tor.OnionService
|
||||||
|
httpSrv *http.Server
|
||||||
|
httpSrvErrCh chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
func start(ctx context.Context, domain string, httpAddr string) (srv *server, err error) {
|
||||||
|
srv = &server{}
|
||||||
|
// // Get all exit addrs
|
||||||
|
if srv.exitAddrs, err = getExitAddresses(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Start tor
|
||||||
|
startConf := &tor.StartConf{DataDir: "tor-data"}
|
||||||
|
if verbose {
|
||||||
|
startConf.DebugWriter = os.Stdout
|
||||||
|
} else {
|
||||||
|
startConf.ExtraArgs = []string{"--quiet"}
|
||||||
|
}
|
||||||
|
if srv.t, err = tor.Start(ctx, startConf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Henceforth, any err needs to call close
|
||||||
|
// Start Onion 1
|
||||||
|
if srv.onion1, err = srv.t.Listen(ctx, &tor.ListenConf{RemotePorts: []int{80}, Version3: true}); err != nil {
|
||||||
|
srv.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Start Onion 2
|
||||||
|
if srv.onion2, err = srv.t.Listen(ctx, &tor.ListenConf{RemotePorts: []int{443}, Version3: true}); err != nil {
|
||||||
|
srv.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Call mkcert for both onions
|
||||||
|
cmd := exec.CommandContext(ctx, "mkcert", domain, srv.onion1.ID+".onion", srv.onion2.ID+".onion")
|
||||||
|
cmd.Dir = "tor-data"
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("Output from mkcert:\n%v\n", string(output))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
srv.Close()
|
||||||
|
return nil, fmt.Errorf("Failed running mkcert: %v", err)
|
||||||
|
}
|
||||||
|
cert := filepath.Join("tor-data", domain+"+2.pem")
|
||||||
|
key := filepath.Join("tor-data", domain+"+2-key.pem")
|
||||||
|
// Listen on the onions
|
||||||
|
srv.httpSrvErrCh = make(chan error, 3)
|
||||||
|
go func(errCh chan error) {
|
||||||
|
errCh <- http.Serve(srv.onion1, srv.NewHandler(srv.onion1.ID+".onion", srv.onion2.ID+".onion:443"))
|
||||||
|
}(srv.httpSrvErrCh)
|
||||||
|
go func(errCh chan error) {
|
||||||
|
errCh <- http.ServeTLS(srv.onion2, srv.NewHandler(srv.onion2.ID+".onion", ""), cert, key)
|
||||||
|
}(srv.httpSrvErrCh)
|
||||||
|
// Start HTTP server
|
||||||
|
srv.httpSrv = &http.Server{Addr: httpAddr, Handler: srv.NewHandler(httpAddr, srv.onion2.ID+".onion:443")}
|
||||||
|
go func(httpSrv *http.Server, errCh chan error) { errCh <- httpSrv.ListenAndServe() }(srv.httpSrv, srv.httpSrvErrCh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) Err() <-chan error { return s.httpSrvErrCh }
|
||||||
|
|
||||||
|
func (s *server) NewHandler(siteAddr string, altSvc string) http.Handler {
|
||||||
|
hitCount := 0
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
hitCount++
|
||||||
|
fmt.Printf("Accessed %v (%v)\n", siteAddr, hitCount)
|
||||||
|
// Set an alt-svc header
|
||||||
|
if altSvc != "" {
|
||||||
|
w.Header().Add("Alt-Svc", "h2=\""+altSvc+"\"; ma=600")
|
||||||
|
}
|
||||||
|
// Respond
|
||||||
|
remoteAddr, _, _ := torutil.PartitionString(r.RemoteAddr, ':')
|
||||||
|
exit := ""
|
||||||
|
if !s.exitAddrs[remoteAddr] {
|
||||||
|
exit = " NOT"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "Server-side site addr: %v\n", siteAddr)
|
||||||
|
fmt.Fprintf(w, "You accessed %v on %v from %v which is%v an exit node\n",
|
||||||
|
r.URL.Path, r.Host, r.RemoteAddr, exit)
|
||||||
|
fmt.Fprintf(w, "--------------- Headers ---------------\n")
|
||||||
|
for h, vals := range r.Header {
|
||||||
|
for _, val := range vals {
|
||||||
|
fmt.Fprintf(w, "%v: %v\n", h, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) Close() {
|
||||||
|
if s.httpSrv != nil {
|
||||||
|
s.httpSrv.Close()
|
||||||
|
}
|
||||||
|
if s.onion1 != nil {
|
||||||
|
s.onion1.Close()
|
||||||
|
}
|
||||||
|
if s.onion2 != nil {
|
||||||
|
s.onion2.Close()
|
||||||
|
}
|
||||||
|
if s.t != nil {
|
||||||
|
s.t.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExitAddresses() (map[string]bool, error) {
|
||||||
|
resp, err := http.Get("https://check.torproject.org/exit-addresses")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret := map[string]bool{}
|
||||||
|
for _, line := range strings.Split(string(body), "\n") {
|
||||||
|
pieces := strings.Split(strings.TrimSpace(line), " ")
|
||||||
|
if len(pieces) >= 2 && pieces[0] == "ExitAddress" {
|
||||||
|
fmt.Printf("Found exit: '%v'\n", pieces[1])
|
||||||
|
ret[pieces[1]] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
Loading…
Reference in New Issue