├── .travis.yml ├── .gitignore ├── .circleci └── config.yml ├── tools ├── genrelease.sh └── gencerts.sh ├── LICENSE ├── README.md └── httpexec.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.3.x 5 | - 1.4.x 6 | - 1.5.x 7 | - 1.6.x 8 | - 1.7.x 9 | - 1.8.x 10 | - 1.9.x 11 | - tip 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Golang CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-go/ for more details 4 | version: 2 5 | jobs: 6 | build: 7 | docker: 8 | # specify the version 9 | - image: circleci/golang:1.8 10 | 11 | # Specify service dependencies here if necessary 12 | # CircleCI maintains a library of pre-built images 13 | # documented at https://circleci.com/docs/2.0/circleci-images/ 14 | # - image: circleci/postgres:9.4 15 | 16 | #### TEMPLATE_NOTE: go expects specific checkout path representing url 17 | #### expecting it in the form of 18 | #### /go/src/github.com/circleci/go-tool 19 | #### /go/src/bitbucket.org/circleci/go-tool 20 | working_directory: /go/src/github.com/kost/httpexec 21 | steps: 22 | - checkout 23 | 24 | # specify any bash command here prefixed with `run: ` 25 | - run: go get -v -t -d ./... 26 | - run: go test -v ./... 27 | 28 | -------------------------------------------------------------------------------- /tools/genrelease.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # apk add go-cross 3 | 4 | CGO_ENABLED=0 go build -ldflags "-extldflags -static -s -w" -o httpexec-linux-x64 httpexec.go 5 | GOOS=linux GOARCH=386 CGO_ENABLED=0 go build -ldflags "-extldflags -static -s -w" -o httpexec-linux-i386 httpexec.go 6 | GOOS=windows GOARCH=386 go build -ldflags="-s -w" -o httpexec-win32.exe httpexec.go 7 | GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o httpexec-win64.exe httpexec.go 8 | GOOS=freebsd GOARCH=386 go build -ldflags="-s -w" -o httpexec-freebsd-i386.exe httpexec.go 9 | GOOS=freebsd GOARCH=amd64 go build -ldflags="-s -w" -o httpexec-freebsd-x64 httpexec.go 10 | GOOS=openbsd GOARCH=386 go build -ldflags="-s -w" -o httpexec-openbsd-i386 httpexec.go 11 | GOOS=openbsd GOARCH=amd64 go build -ldflags="-s -w" -o httpexec-openbsd-x64 httpexec.go 12 | GOOS=darwin GOARCH=386 go build -ldflags="-s -w" -o httpexec-darwin-i386 httpexec.go 13 | GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o httpexec-darwin-x64 httpexec.go 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 kost 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tools/gencerts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # set -x 4 | 5 | if [ "$CERTSERVERNAME" = "" ]; then 6 | CERTSERVERNAME="127.0.0.1" 7 | echo "-" 8 | echo "[i] Using $CERTSERVERNAME as server common name, specify it with CERTSERVERNAME env variable" 9 | echo "[i] e.g. CERTSERVERNAME=mydomain.com $0" 10 | echo "-" 11 | fi 12 | 13 | if [ "$CERTCLIENTNAME" = "" ]; then 14 | CERTCLIENTNAME="client.local" 15 | fi 16 | 17 | CERTVALID=3650 18 | # -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" 19 | CERTCOMMON="/C=US/ST=Denial/L=Springfield/O=Dis" 20 | 21 | openssl genrsa -out ca.key 4096 22 | openssl req -new -x509 -days $CERTVALID -subj "$CERTCOMMON/CN=myCA" -key ca.key -out ca.pem 23 | echo "00" > ca.srl 24 | 25 | openssl genrsa -out server.key 4096 26 | openssl req -new -key server.key -nodes -subj "$CERTCOMMON/CN=$CERTSERVERNAME" -out server.csr 27 | openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAserial ca.srl -out server.crt 28 | 29 | 30 | openssl genrsa -out client.key 4096 31 | openssl req -new -key client.key -nodes -subj "$CERTCOMMON/CN=$CERTCLIENTNAME" -out client.csr 32 | openssl x509 -req -in client.csr -CA ca.pem -CAkey ca.key -CAserial ca.srl -out client.crt 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/kost/httpexec.png)](https://travis-ci.org/kost/httpexec) 2 | [![Circle Status](https://circleci.com/gh/kost/httpexec.svg?style=shield&circle-token=:circle-token)](https://circleci.com/gh/kost/httpexec) 3 | 4 | # httpexec 5 | RESTful interface to your operating system shell. 6 | Swiss knife for your OS shells over the Web. 7 | 8 | (Yes, it's dangerous if you don't know what you're doing.) 9 | 10 | Features 11 | ======== 12 | 13 | - Single executable (thanks to Go!) 14 | - Linux/Windows/Mac/BSD support 15 | - Standalone HTTP Server or CGI mode 16 | - (optional) JSON support (in requests and responses!) 17 | 18 | Modes of operation 19 | ================== 20 | 21 | - Standalone HTTP(S) server (just run the binary) 22 | - CGI mode (just put it somewhere where your CGI-BIN is served) 23 | - Reverse client mode (aka reverse shell - in development) 24 | 25 | # Examples 26 | 27 | Here is quick example, just to get idea what you can do with it. 28 | 29 | ## Quick Examples 30 | 31 | Start server: 32 | ``` 33 | $ ./httpexec -listen 127.0.0.1:8080 -verbose 5 34 | 2016/10/22 21:20:18 Starting to listen at 127.0.0.1:8080 with URI / with auth 35 | ``` 36 | 37 | Run whoami command on server: 38 | ``` 39 | $ curl http://127.0.0.1:8080/ -d 'whoami' 40 | user 41 | ``` 42 | 43 | # Usage 44 | 45 | httpexec can be useful to different types of people. Just few ideas how it can 46 | be useful(and dangerous) for you. 47 | 48 | ## Sysadmin/Devops 49 | 50 | You need easy way to send commands to your virtualization guests. 51 | You need to run many commands on multiple machines behind many firewalls. 52 | You can use it as rudimentary configuration management tool as well. 53 | 54 | ## Hacker/Pentester 55 | 56 | You can use it to keep access to machine. 57 | You need to run many commands on multiple machines you owned behind many firewalls. 58 | 59 | ## Note 60 | 61 | httpexec can be quite useful, but it also can be quite dangerous if you don't know what you're doing. 62 | You should run httpexec inside safe network/environment. By default it listens on 63 | all interfaces as user who executed it. 64 | 65 | In short, it is quite dangerous to run on the internet exposed server as any user (especially root). You have been warned! 66 | 67 | # Download 68 | 69 | You can find binary and source releases on Github under "Releases". Here's the [link to the latest release](https://github.com/kost/httpexec/releases/latest) 70 | 71 | # HTTP method mapping 72 | 73 | - HEAD request = launch command specified as query (everything behind ?) and don't care about output (blind) 74 | - GET request = launch command specified as query (everything behind ?) and display output 75 | - POST request = launch command specified as POST data and display output 76 | - POST request with query = launch command specified as query (everything behind ?), treat POST data as stdin and display output 77 | 78 | # Large set of examples 79 | 80 | Here is large set of examples 81 | 82 | ## Server Examples 83 | 84 | ### Start Server (Linux/Mac) 85 | 86 | $ ./httpexec -verbose 5 87 | 88 | ### Start Server (Windows) 89 | 90 | httpexec.exe -verbose 5 91 | 92 | ### Start Server on 127.0.0.1 with SSL cert and key 93 | 94 | $ ./httpexec -listen 127.0.0.1 -tls -cert server.cert -key server.key -verbose 5 95 | 96 | # Client Examples 97 | 98 | ### Simple Example: run whoami 99 | 100 | $ curl 'http://127.0.0.1:8080/' -d 'whoami' 101 | user 102 | 103 | ### Simple Example: run id 104 | 105 | $ curl 'http://127.0.0.1:8080/' -d 'id' 106 | uid=1000(user) gid=1000(user) groups=1000(user) 107 | 108 | ### Simple Example - GET request: run id 109 | 110 | $ curl 'http://127.0.0.1:8080/?id' 111 | uid=1000(user) gid=1000(user) groups=1000(user) 112 | 113 | ### Simple Example - GET request: run ifconfig -a 114 | 115 | $ curl 'http://127.0.0.1:8080/?ifconfig+-a' 116 | [ifconfig output] 117 | 118 | ### Simple Example - POST request: run ifconfig -a 119 | 120 | $ curl 'http://127.0.0.1:8080/' -d 'ifconfig -a' 121 | [ifconfig output] 122 | 123 | ### Simple Example - POST request: run tr [a-z] [A-Z] on POST body as stdin 124 | 125 | $ curl 'http://127.0.0.1:8080/?tr+a-z+A-Z' -d 'data' 126 | DATA 127 | 128 | ### Simple Example - POST JSON request: run tr [a-z] [A-Z] on Stdin as JSON field 129 | 130 | $ curl http://127.0.0.1:8080/ -d '{"cmd":"tr [a-z] [A-Z]","Stdin":"data"}' -H 'Content-Type: application/json' 131 | {"Cmd":"tr [a-z] [A-Z]","Stdout":"DATA","Stderr":"","Err":""} 132 | 133 | ### Simple Example - POST JSON request: run tr [a-z] [A-Z] on Stdin as JSON field and do not return JSON: 134 | $ curl "http://127.0.0.1:8080/test" -d '{"cmd":"tr a-z A-Z","NoJSON":true,"Stdin":"data"}' -H "Content-Type: application/json" 135 | DATA 136 | 137 | # Security options 138 | 139 | These are security options you have: 140 | 141 | - custom URI/URL (security through obscurity, attacker have to guess URI) - low security 142 | - basic authentication (username/password authentication) - low security if http (not https) 143 | - SSL/TLS tunneling (attacker cannot MiTM) - moderate security 144 | - SSL/TLS client/server verification (depends on security of the keys) - moderate security 145 | 146 | You can combine options above for higher security. It is encouraged to combine SSL/TLS with different options above. 147 | For highest security - combine all with SSL/TLS (custom URI, basic authentication and client certs). 148 | 149 | ## Example with SSL client certs 150 | 151 | You need to generate CA/server/client certificates and specify verification in command line: 152 | ``` 153 | tools/gencerts.sh 154 | ./httpexec -tls -verify ca.pem 155 | ``` 156 | 157 | You can then issue curl request as following: 158 | ``` 159 | curl --cert client.crt --key client.key --cacert ca.pem https://127.0.0.1:8080/ -d 'whoami' 160 | 161 | ``` 162 | 163 | 164 | Options explained 165 | ================= 166 | ``` 167 | $ ./httpexec -h 168 | Usage of ./httpexec: 169 | -auth string 170 | basic auth to require - in form user:pass 171 | -cert string 172 | SSL/TLS certificate file (default "server.crt") 173 | -cgi 174 | CGI mode 175 | -key string 176 | SSL/TLS certificate key file (default "server.key") 177 | -listen string 178 | listen address and port (default ":8080") 179 | -realm string 180 | Basic authentication realm (default "httpexec") 181 | -silentout 182 | Silent Output (do not display errors) 183 | -ssl 184 | use TLS/SSL 185 | -tls 186 | use TLS/SSL 187 | -uri string 188 | URI to serve (default "/") 189 | -verbose int 190 | verbose level 191 | -verify string 192 | Client cert verification using SSL/TLS (CA) certificate file 193 | ``` 194 | 195 | 196 | Building 197 | ======== 198 | 199 | ### Linux/Mac/POSIX builds 200 | 201 | Just type: 202 | 203 | go build httpexec.go 204 | 205 | Static compiling: 206 | 207 | CGO_ENABLED=0 go build -ldflags "-extldflags -static" 208 | 209 | ### Windows builds: 210 | 211 | Just type: 212 | 213 | go build httpexec.go 214 | 215 | ## Design goals 216 | 217 | - prefer builtin go packages over 3rd party packages 218 | - small as possible 219 | - self-contained static executable 220 | 221 | ### ToDo 222 | - [ ] Implement http client 223 | - [ ] Implement logging of failed basic auth 224 | - [ ] Switch to CGI mode automatically if REQUEST_METHOD env is found 225 | 226 | ### Done 227 | - [X] Implement TLS client auth 228 | 229 | Credits 230 | ======= 231 | 232 | Vlatko Kosturjak 233 | 234 | 235 | -------------------------------------------------------------------------------- /httpexec.go: -------------------------------------------------------------------------------- 1 | //usr/bin/go run $0 $@ ; exit 2 | // httpexec in Go. Copyright (C) Kost. Distributed under MIT. 3 | // RESTful interface to your operating system shell 4 | 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "crypto/tls" 10 | "crypto/x509" 11 | "encoding/base64" 12 | "encoding/json" 13 | "flag" 14 | "io/ioutil" 15 | "log" 16 | "net/http" 17 | "net/http/cgi" 18 | "net/url" 19 | "os/exec" 20 | "strings" 21 | ) 22 | 23 | // CmdReq holds JSON input request. 24 | type CmdReq struct { 25 | Cmd string 26 | Nojson bool 27 | Stdin string 28 | } 29 | 30 | // CmdResp holds JSON output request. 31 | type CmdResp struct { 32 | Cmd string 33 | Stdout string 34 | Stderr string 35 | Err string 36 | } 37 | 38 | var auth string // basic authentication combo 39 | var realm string // basic authentication realm 40 | 41 | // VerboseLevel holds global verbosity level. 42 | var VerboseLevel int 43 | 44 | // SilentOutput is silent output. 45 | var SilentOutput bool 46 | 47 | // check basic authentication if set 48 | func checkAuth(w http.ResponseWriter, r *http.Request) bool { 49 | s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) 50 | if len(s) != 2 { 51 | return false 52 | } 53 | 54 | b, err := base64.StdEncoding.DecodeString(s[1]) 55 | if err != nil { 56 | return false 57 | } 58 | 59 | return bytes.Equal(b, []byte(auth)) 60 | } 61 | 62 | // real content Handler 63 | func contHandler(w http.ResponseWriter, r *http.Request) { 64 | var jsonout bool 65 | var inputjson CmdReq 66 | var outputjson CmdResp 67 | var body []byte 68 | if r.Header.Get("Content-Type") == "application/json" { 69 | w.Header().Set("Content-Type", "application/json") 70 | jsonout = true 71 | } else { 72 | w.Header().Set("Content-Type", "text/plain") 73 | } 74 | cmdstr := "" 75 | urlq, urlErr := url.QueryUnescape(r.URL.RawQuery) 76 | if urlErr != nil { 77 | log.Printf("url query unescape: %v", urlErr) 78 | } 79 | if r.Method == "GET" || r.Method == "HEAD" { 80 | cmdstr = urlq 81 | } 82 | if r.Method == "POST" { 83 | var rerr error 84 | body, rerr = ioutil.ReadAll(r.Body) 85 | if rerr != nil { 86 | log.Printf("read Body: %v", rerr) 87 | } 88 | if closeErr := r.Body.Close(); closeErr != nil { 89 | log.Printf("body close: %v", closeErr) 90 | 91 | } 92 | if VerboseLevel > 2 { 93 | log.Printf("Body: %s", body) 94 | } 95 | 96 | if len(urlq) > 0 { 97 | cmdstr = urlq 98 | } else { 99 | if jsonout { 100 | jerr := json.Unmarshal(body, &inputjson) 101 | if jerr != nil { 102 | // http.Error(w, jerr.Error(), 400) 103 | return 104 | } 105 | cmdstr = inputjson.Cmd 106 | jsonout = !inputjson.Nojson 107 | } else { 108 | cmdstr = string(body) 109 | } 110 | } 111 | } 112 | if VerboseLevel > 0 { 113 | log.Printf("Command to execute: %s", cmdstr) 114 | } 115 | 116 | if len(cmdstr) < 1 { 117 | return 118 | } 119 | 120 | parts := strings.Fields(cmdstr) 121 | head := parts[0] 122 | parts = parts[1:] 123 | 124 | cmd := exec.Command(head, parts...) 125 | 126 | // Handle stdin if have any 127 | if len(urlq) > 0 && r.Method == "POST" { 128 | if VerboseLevel > 2 { 129 | log.Printf("Stdin: %s", body) 130 | } 131 | cmd.Stdin = bytes.NewReader(body) 132 | } 133 | if len(inputjson.Stdin) > 0 { 134 | if VerboseLevel > 2 { 135 | log.Printf("JSON Stdin: %s", inputjson.Stdin) 136 | } 137 | cmd.Stdin = strings.NewReader(inputjson.Stdin) 138 | } 139 | 140 | var err error 141 | var jStdout bytes.Buffer 142 | var jStderr bytes.Buffer 143 | if r.Method == "HEAD" { 144 | err = cmd.Start() 145 | } else { 146 | if jsonout { 147 | cmd.Stdout = &jStdout 148 | cmd.Stderr = &jStderr 149 | } else { 150 | cmd.Stdout = w 151 | cmd.Stderr = w 152 | } 153 | err = cmd.Run() 154 | } 155 | if err != nil { 156 | if VerboseLevel > 0 { 157 | log.Printf("Error executing: %s", err) 158 | } 159 | if jsonout { 160 | outputjson.Err = err.Error() 161 | } else { 162 | if !SilentOutput { 163 | _, writeErr := w.Write([]byte(err.Error())) 164 | if writeErr != nil { 165 | log.Printf("write: %v", writeErr) 166 | } 167 | } 168 | } 169 | } 170 | 171 | if jsonout { 172 | outputjson.Stdout = jStdout.String() 173 | outputjson.Stderr = jStderr.String() 174 | outputjson.Cmd = cmdstr 175 | if encodeErr := json.NewEncoder(w).Encode(outputjson); encodeErr != nil { 176 | log.Printf("encode: %v", err) 177 | } 178 | } 179 | } 180 | 181 | func retlogstr(entry string) string { 182 | if len(entry) == 0 { 183 | return "-" 184 | } 185 | return entry 186 | } 187 | 188 | // main handler which basically checks (basic) authentication first 189 | func handler(w http.ResponseWriter, r *http.Request) { 190 | if VerboseLevel > 0 { 191 | log.Printf("%s %s %s %s %s", retlogstr(r.RemoteAddr), retlogstr(r.Header.Get("X-Forwarded-For")), r.Method, r.RequestURI, retlogstr(r.URL.RawQuery)) 192 | } 193 | if auth == "" { 194 | contHandler(w, r) 195 | } else { 196 | if checkAuth(w, r) { 197 | contHandler(w, r) 198 | return 199 | } 200 | w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`) 201 | w.WriteHeader(401) 202 | _, err := w.Write([]byte("401 Unauthorized\n")) 203 | if err != nil { 204 | log.Printf("401 write: %v", err) 205 | } 206 | } 207 | } 208 | 209 | // main function with main http loop and command line parsing 210 | func main() { 211 | flag.StringVar(&auth, "auth", "", "basic auth to require - in form user:pass") 212 | optcgi := flag.Bool("cgi", false, "CGI mode") 213 | cert := flag.String("cert", "server.crt", "SSL/TLS certificate file") 214 | key := flag.String("key", "server.key", "SSL/TLS certificate key file") 215 | uri := flag.String("uri", "/", "URI to serve") 216 | listen := flag.String("listen", ":8080", "listen address and port") 217 | flag.StringVar(&realm, "realm", "httpexec", "Basic authentication realm") 218 | opttls := flag.Bool("tls", false, "use TLS/SSL") 219 | optssl := flag.Bool("ssl", false, "use TLS/SSL") 220 | optverify := flag.String("verify", "", "Client cert verification using SSL/TLS (CA) certificate file") 221 | flag.BoolVar(&SilentOutput, "silentout", false, "Silent Output (do not display errors)") 222 | flag.IntVar(&VerboseLevel, "verbose", 0, "verbose level") 223 | 224 | flag.Parse() 225 | 226 | // turn on tls if client verification is specified 227 | if len(*optverify) > 0 { 228 | *opttls = true 229 | } 230 | 231 | httpProto := "http" 232 | if *opttls || *optssl { 233 | httpProto = "https" 234 | } 235 | 236 | if VerboseLevel > 0 { 237 | log.Printf("Starting to listen at %s with URI %s as %s", *listen, *uri, httpProto) 238 | } 239 | if VerboseLevel > 5 && len(auth) > 0 { 240 | log.Printf("Using basic authentication: %s", auth) 241 | } 242 | if VerboseLevel > 1 && len(*optverify) > 0 { 243 | log.Printf("Using TLS/SSL client verification with: %s", *optverify) 244 | } 245 | 246 | if *optcgi { 247 | cgiErr := cgi.Serve(http.HandlerFunc(handler)) 248 | if cgiErr != nil { 249 | log.Printf("cgiErr: %v", cgiErr) 250 | } 251 | } else { 252 | http.HandleFunc(*uri, handler) 253 | var err error 254 | if *opttls || *optssl { 255 | // secure default TLS configuration 256 | tlsCfg := &tls.Config{ 257 | MinVersion: tls.VersionTLS12, 258 | CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, 259 | PreferServerCipherSuites: true, 260 | CipherSuites: []uint16{ 261 | // turn on for beter security if you have supported 262 | // tls.TLS_RSA_WITH_AES_256_GCM_SHA384, 263 | // tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 264 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 265 | tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 266 | tls.TLS_RSA_WITH_AES_256_CBC_SHA, 267 | }, 268 | } 269 | // if verify is specified add only specific (CA) certificate to cert pool 270 | if len(*optverify) > 0 { 271 | caCertPool := x509.NewCertPool() 272 | caCert, readErr := ioutil.ReadFile(*optverify) 273 | if readErr != nil { 274 | log.Fatal("Error reading client verification cert: ", err) 275 | } 276 | caCertPool.AppendCertsFromPEM(caCert) 277 | 278 | tlsCfg.ClientCAs = caCertPool 279 | tlsCfg.ClientAuth = tls.RequireAndVerifyClientCert 280 | tlsCfg.BuildNameToCertificate() 281 | } 282 | 283 | srv := &http.Server{ 284 | Addr: *listen, 285 | TLSConfig: tlsCfg, 286 | } 287 | err = srv.ListenAndServeTLS(*cert, *key) 288 | } else { 289 | err = http.ListenAndServe(*listen, nil) 290 | } 291 | if err != nil { 292 | log.Fatal("ListenAndServe: ", err) 293 | } 294 | } 295 | } 296 | --------------------------------------------------------------------------------