mirror of
https://github.com/jumpserver/jumpserver.git
synced 2026-07-02 07:01:30 +00:00
perf: support rdp file sign
This commit is contained in:
@@ -2,6 +2,8 @@ import base64
|
||||
import json
|
||||
import os
|
||||
import urllib.parse
|
||||
import subprocess
|
||||
from struct import pack
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
@@ -162,7 +164,166 @@ class RDPFileClientProtocolURLMixin:
|
||||
for k, v in rdp_options.items():
|
||||
content += f'{k}:{v}\n'
|
||||
|
||||
if settings.RDP_SIGN_ENABLED:
|
||||
signed_content = self.signed_rdp_content(content)
|
||||
if signed_content:
|
||||
content = signed_content
|
||||
|
||||
return filename, content
|
||||
|
||||
@staticmethod
|
||||
def signed_rdp_content(rdp_file_content: str):
|
||||
cert_dir = os.path.join(settings.PROJECT_DIR, 'data', 'certs')
|
||||
if not os.path.exists(cert_dir):
|
||||
logger.error(f'rdp sign cert dir [{cert_dir}] not exists')
|
||||
return None
|
||||
|
||||
crt_path = os.path.join(cert_dir, settings.RDP_SIGN_CERT)
|
||||
if not os.path.exists(crt_path):
|
||||
logger.error(f'rdp sign cert file [{crt_path}] not exists')
|
||||
return None
|
||||
|
||||
key_path = os.path.join(cert_dir, settings.RDP_SIGN_CERT_KEY)
|
||||
if not os.path.exists(key_path):
|
||||
logger.warning(f'rdp sign cert file [{key_path}] not exists')
|
||||
key_path = None
|
||||
|
||||
securesettings = [
|
||||
["full address:s:", "Full Address"],
|
||||
["alternate full address:s:", "Alternate Full Address"],
|
||||
["pcb:s:", "PCB"],
|
||||
["use redirection server name:i:", "Use Redirection Server Name"],
|
||||
["server port:i:", "Server Port"],
|
||||
["negotiate security layer:i:", "Negotiate Security Layer"],
|
||||
["enablecredsspsupport:i:", "EnableCredSspSupport"],
|
||||
["disableconnectionsharing:i:", "DisableConnectionSharing"],
|
||||
["autoreconnection enabled:i:", "AutoReconnection Enabled"],
|
||||
["gatewayhostname:s:", "GatewayHostname"],
|
||||
["gatewayusagemethod:i:", "GatewayUsageMethod"],
|
||||
["gatewayprofileusagemethod:i:", "GatewayProfileUsageMethod"],
|
||||
["gatewaycredentialssource:i:", "GatewayCredentialsSource"],
|
||||
["support url:s:", "Support URL"],
|
||||
["promptcredentialonce:i:", "PromptCredentialOnce"],
|
||||
["require pre-authentication:i:", "Require pre-authentication"],
|
||||
["pre-authentication server address:s:", "Pre-authentication server address"],
|
||||
["alternate shell:s:", "Alternate Shell"],
|
||||
["shell working directory:s:", "Shell Working Directory"],
|
||||
["remoteapplicationprogram:s:", "RemoteApplicationProgram"],
|
||||
["remoteapplicationexpandworkingdir:s:", "RemoteApplicationExpandWorkingdir"],
|
||||
["remoteapplicationmode:i:", "RemoteApplicationMode"],
|
||||
["remoteapplicationguid:s:", "RemoteApplicationGuid"],
|
||||
["remoteapplicationname:s:", "RemoteApplicationName"],
|
||||
["remoteapplicationicon:s:", "RemoteApplicationIcon"],
|
||||
["remoteapplicationfile:s:", "RemoteApplicationFile"],
|
||||
["remoteapplicationfileextensions:s:", "RemoteApplicationFileExtensions"],
|
||||
["remoteapplicationcmdline:s:", "RemoteApplicationCmdLine"],
|
||||
["remoteapplicationexpandcmdline:s:", "RemoteApplicationExpandCmdLine"],
|
||||
["prompt for credentials:i:", "Prompt For Credentials"],
|
||||
["authentication level:i:", "Authentication Level"],
|
||||
["audiomode:i:", "AudioMode"],
|
||||
["redirectdrives:i:", "RedirectDrives"],
|
||||
["redirectprinters:i:", "RedirectPrinters"],
|
||||
["redirectcomports:i:", "RedirectCOMPorts"],
|
||||
["redirectsmartcards:i:", "RedirectSmartCards"],
|
||||
["redirectposdevices:i:", "RedirectPOSDevices"],
|
||||
["redirectclipboard:i:", "RedirectClipboard"],
|
||||
["devicestoredirect:s:", "DevicesToRedirect"],
|
||||
["drivestoredirect:s:", "DrivesToRedirect"],
|
||||
["loadbalanceinfo:s:", "LoadBalanceInfo"],
|
||||
["redirectdirectx:i:", "RedirectDirectX"],
|
||||
["rdgiskdcproxy:i:", "RDGIsKDCProxy"],
|
||||
["kdcproxyname:s:", "KDCProxyName"],
|
||||
["eventloguploadaddress:s:", "EventLogUploadAddress"],
|
||||
]
|
||||
|
||||
rdp_settings = list()
|
||||
signlines = list()
|
||||
signnames = list()
|
||||
|
||||
lines = [v.strip() for v in rdp_file_content.splitlines()]
|
||||
|
||||
fulladdress = None
|
||||
alternatefulladdress = None
|
||||
|
||||
for v in lines:
|
||||
if v.startswith("full address:s:"):
|
||||
fulladdress = v[15:]
|
||||
elif v.startswith("alternate full address:s:"):
|
||||
alternatefulladdress = v[25:]
|
||||
elif v.startswith("signature:s:"):
|
||||
continue
|
||||
elif v.startswith("signscope:s:"):
|
||||
continue
|
||||
rdp_settings.append(v)
|
||||
|
||||
# prevent hacks via alternate full address
|
||||
if fulladdress and not alternatefulladdress:
|
||||
rdp_settings.append("alternate full address:s:" + fulladdress)
|
||||
|
||||
for s in securesettings:
|
||||
for v in rdp_settings:
|
||||
if v.startswith(s[0]):
|
||||
signnames.append(s[1])
|
||||
signlines.append(v)
|
||||
|
||||
msgtext = (
|
||||
"\r\n".join(signlines)
|
||||
+ "\r\n"
|
||||
+ "signscope:s:"
|
||||
+ ",".join(signnames)
|
||||
+ "\r\n"
|
||||
+ "\x00"
|
||||
)
|
||||
|
||||
msgblob = msgtext.encode("UTF-16LE")
|
||||
|
||||
params = ["openssl", "smime", "-sign", "-binary"]
|
||||
params += ["-signer", crt_path]
|
||||
params += ["-outform", "DER"]
|
||||
params += ["-noattr", "-nosmimecap"]
|
||||
|
||||
if key_path is not None:
|
||||
params += ["-inkey", key_path]
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
params,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
|
||||
opensslout, opensslerr = proc.communicate(msgblob)
|
||||
except OSError as e:
|
||||
logger.error("Error calling openssl command: %s", e.strerror)
|
||||
return None
|
||||
|
||||
retcode = proc.poll()
|
||||
|
||||
if retcode != 0:
|
||||
emsg = "openssl command failed (return code #{0:d})".format(retcode)
|
||||
if opensslerr is not None:
|
||||
emsg += ":\n"
|
||||
emsg += opensslerr.decode("utf-8", errors="replace")
|
||||
logger.error(emsg)
|
||||
return None
|
||||
|
||||
# The Microsoft rdpsign.exe adds a 12 byte header to the signature
|
||||
# before it gets base64 encoded
|
||||
# The meaning of the first 8 bytes is still unknown
|
||||
msgsig = pack("<I", 0x00010001) # unknown DWORD value
|
||||
msgsig += pack("<I", 0x00000001) # unknown DWORD value
|
||||
msgsig += pack("<I", len(opensslout))
|
||||
msgsig += opensslout
|
||||
|
||||
sigval = base64.b64encode(msgsig).decode("ascii")
|
||||
|
||||
parts = []
|
||||
parts.append("\r\n".join(rdp_settings))
|
||||
parts.append("signscope:s:" + ",".join(signnames))
|
||||
parts.append("signature:s:" + sigval)
|
||||
signed_content = "\r\n".join(parts) + "\r\n"
|
||||
return signed_content
|
||||
|
||||
@staticmethod
|
||||
def escape_name(name):
|
||||
|
||||
@@ -750,6 +750,11 @@ class Config(dict):
|
||||
'TRUSTED_IP_SOURCE_HEADER': '',
|
||||
'TRUSTED_IP_VERIFY_SIGNATURE_HEADER': '',
|
||||
'TRUSTED_IP_VERIFY_KEY_PATH': '',
|
||||
|
||||
# rdp sign cert
|
||||
'RDP_SIGN_ENABLED': False,
|
||||
'RDP_SIGN_CERT': 'signer.crt',
|
||||
'RDP_SIGN_CERT_KEY': 'signer.key',
|
||||
}
|
||||
|
||||
old_config_map = {
|
||||
|
||||
@@ -282,3 +282,8 @@ TRUSTED_IP_VERIFY_ENABLED = CONFIG.TRUSTED_IP_VERIFY_ENABLED
|
||||
TRUSTED_IP_SOURCE_HEADER = CONFIG.TRUSTED_IP_SOURCE_HEADER
|
||||
TRUSTED_IP_VERIFY_SIGNATURE_HEADER = CONFIG.TRUSTED_IP_VERIFY_SIGNATURE_HEADER
|
||||
TRUSTED_IP_VERIFY_KEY_PATH = CONFIG.TRUSTED_IP_VERIFY_KEY_PATH
|
||||
|
||||
# RDP 签名相关
|
||||
RDP_SIGN_ENABLED = CONFIG.RDP_SIGN_ENABLED
|
||||
RDP_SIGN_CERT = CONFIG.RDP_SIGN_CERT
|
||||
RDP_SIGN_CERT_KEY = CONFIG.RDP_SIGN_CERT_KEY
|
||||
Reference in New Issue
Block a user