owpengram-server/internal/mtprotoedge/server_info_http.go

74 lines
2.8 KiB
Go

package mtprotoedge
import (
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"net/http"
)
// ServerInfoPath is the well-known same-port HTTP path a client can GET to
// self-configure an "Add Server" entry from just a host:port -- no more
// manual `openssl rsa -RSAPublicKey_out` + copy-paste of the PEM block.
//
// Served over the same samePortMux HTTP side that already carries WebSocket
// upgrades (see same_port_mux.go): a plain GET here never looks like an
// obfuscated2 init (isHTTPHeaderPrefix's "legal init headers never start
// with GET/POST/HEAD/OPTI" invariant), so this adds no new port and no new
// risk to the raw-TCP detection path. Only reachable when serveMixed is
// active, i.e. TELESRV_WEBSOCKET_ENABLE=true (the default).
const ServerInfoPath = "/owpengram/server-info"
// ServerInfoResponse is ServerInfoPath's JSON body. RSAPublicKeyPEM is the
// PKCS#1 "RSA PUBLIC KEY" PEM block -- the same format
// `openssl rsa -RSAPublicKey_out` produces, and what the client's "Add
// Server" RSA key field already expects verbatim.
type ServerInfoResponse struct {
DCID int `json:"dc_id"`
RSAPublicKeyPEM string `json:"rsa_public_key_pem"`
}
// rsaPublicKeyPEM renders key's public half as a PKCS#1 PEM block, matching
// `openssl rsa -in server_rsa.pem -RSAPublicKey_out` byte-for-byte (that
// flag selects PKCS#1 encoding, not the x509/PKIX default `-pubout` would
// produce -- a real format difference, not just a header/footer string).
func rsaPublicKeyPEM(key *rsa.PrivateKey) []byte {
if key == nil {
return nil
}
der := x509.MarshalPKCS1PublicKey(&key.PublicKey)
return pem.EncodeToMemory(&pem.Block{Type: "RSA PUBLIC KEY", Bytes: der})
}
// serverInfoHTTPHandler serves ServerInfoResponse at ServerInfoPath and
// delegates every other path to next (the existing WebSocket route
// handler). pubKeyPEM is nil when the server has no RSA key configured
// (shouldn't happen in production -- handshakes would already be broken --
// but a client asking anyway gets 503, not a panic or an empty key).
func serverInfoHTTPHandler(next http.Handler, dc int, pubKeyPEM []byte) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != ServerInfoPath {
next.ServeHTTP(w, r)
return
}
if r.Method != http.MethodGet && r.Method != http.MethodHead {
w.Header().Set("Allow", "GET, HEAD")
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if len(pubKeyPEM) == 0 {
http.Error(w, "server key not configured", http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("Cache-Control", "no-store")
if r.Method == http.MethodHead {
return
}
_ = json.NewEncoder(w).Encode(ServerInfoResponse{
DCID: dc,
RSAPublicKeyPEM: string(pubKeyPEM),
})
})
}