added endpoint for clients auto fetch for server dc and key
This commit is contained in:
parent
2cfc7a0539
commit
902f3606c2
2 changed files with 86 additions and 1 deletions
|
|
@ -517,6 +517,12 @@ type Server struct {
|
||||||
rpcResults *rpcExecutionLedger
|
rpcResults *rpcExecutionLedger
|
||||||
rpcRewrap *rpcRewrapRegistry
|
rpcRewrap *rpcRewrapRegistry
|
||||||
|
|
||||||
|
// pubKeyPEM is the server's RSA public key, precomputed once at
|
||||||
|
// construction and served over the same-port HTTP side (see
|
||||||
|
// server_info_http.go) so a client's "Add Server" flow can self-configure
|
||||||
|
// from a host:port instead of a manual openssl + copy-paste.
|
||||||
|
pubKeyPEM []byte
|
||||||
|
|
||||||
// onFrame 是测试钩子:收到一帧时回调其字节数;生产为 nil。
|
// onFrame 是测试钩子:收到一帧时回调其字节数;生产为 nil。
|
||||||
onFrame func(n int)
|
onFrame func(n int)
|
||||||
}
|
}
|
||||||
|
|
@ -554,6 +560,7 @@ func New(opts Options) *Server {
|
||||||
dc: opts.DC,
|
dc: opts.DC,
|
||||||
strictDC: opts.StrictDC,
|
strictDC: opts.StrictDC,
|
||||||
key: exchange.PrivateKey{RSA: opts.RSAKey},
|
key: exchange.PrivateKey{RSA: opts.RSAKey},
|
||||||
|
pubKeyPEM: rsaPublicKeyPEM(opts.RSAKey),
|
||||||
authKeys: opts.AuthKeys,
|
authKeys: opts.AuthKeys,
|
||||||
conns: conns,
|
conns: conns,
|
||||||
rpc: opts.legacyRPC,
|
rpc: opts.legacyRPC,
|
||||||
|
|
@ -707,7 +714,11 @@ func (s *Server) serveMixed(ctx context.Context, ln net.Listener) error {
|
||||||
wsLn := newTransportPacketMessageListener(wsRawLn)
|
wsLn := newTransportPacketMessageListener(wsRawLn)
|
||||||
|
|
||||||
httpServer := &http.Server{
|
httpServer := &http.Server{
|
||||||
Handler: websocketRouteHandler(wsHandler, s.websocketOrigins),
|
Handler: serverInfoHTTPHandler(
|
||||||
|
websocketRouteHandler(wsHandler, s.websocketOrigins),
|
||||||
|
s.dc,
|
||||||
|
s.pubKeyPEM,
|
||||||
|
),
|
||||||
ReadHeaderTimeout: minDuration(10*time.Second, s.handshakeTimeout),
|
ReadHeaderTimeout: minDuration(10*time.Second, s.handshakeTimeout),
|
||||||
BaseContext: func(net.Listener) context.Context {
|
BaseContext: func(net.Listener) context.Context {
|
||||||
return ctx
|
return ctx
|
||||||
|
|
|
||||||
74
internal/mtprotoedge/server_info_http.go
Normal file
74
internal/mtprotoedge/server_info_http.go
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue