Internal
Public Access
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c649f727f | ||
|
|
18f234105d | ||
|
|
186480dcf5 | ||
|
|
dd974c684a | ||
|
|
8c56d489af | ||
|
|
d7910f1631 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
project(OrbitHub VERSION 2026.9.8 LANGUAGES CXX)
|
||||
project(OrbitHub VERSION 2026.9.8.2 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
@@ -16,18 +16,29 @@ OrbitHub is in active development.
|
||||
- Milestones completed: M0-M5, and M7-M9
|
||||
- Current milestone: Milestone 10 (v1.0 Stabilization)
|
||||
- Deferred milestone: Milestone 6 (VNC Fully Working)
|
||||
- Latest checkpoint tag: `v0-m9-done`
|
||||
- Latest checkpoint tag: `v2026.9.8.2`
|
||||
- VNC implementation milestone (M6) is currently deferred
|
||||
|
||||
Progress and milestone details:
|
||||
- [docs/PROGRESS.md](docs/PROGRESS.md)
|
||||
|
||||
Latest release (installers for Windows, Linux, and macOS):
|
||||
- [v2026.9.8.2](https://git.darksingularity.org/DarkSingularity/orbithub/releases/tag/v2026.9.8.2)
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
|
||||
Profiles organized into folders, with protocol, host, and tags shown at a glance. (Sample data shown; not real hosts.)
|
||||
|
||||

|
||||
|
||||
An interactive SSH terminal session in a tab, with the event log below.
|
||||
|
||||

|
||||
|
||||
An embedded RDP session in a tab.
|
||||
|
||||
## Implemented Features
|
||||
|
||||
### Profile Management
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Flathub Submission Readiness
|
||||
|
||||
Tracks OrbitHub's readiness for submission to Flathub. This is a separate
|
||||
checklist from `docs/PROGRESS.md`'s development milestones, since Flathub
|
||||
submission is an external process with its own requirements.
|
||||
|
||||
## Status
|
||||
|
||||
| Item | Status |
|
||||
|---|---|
|
||||
| Production manifest with pinned, reproducible git source | Done — `packaging/flatpak/flathub/org.darksingularity.OrbitHub.yml`; commit pin updated at each tagged release |
|
||||
| `flathub.json` for build settings (`only-arches`, etc.) | Done — `packaging/flatpak/flathub/flathub.json` |
|
||||
| Offline build (no network fetches during build) | Verified — no `FetchContent`/`ExternalProject`/`curl`/`wget` in CMake; all vendored deps committed in `third_party/`; confirmed with a real `flatpak-builder` build |
|
||||
| SSH client available inside the sandbox | Verified — provided by the `org.kde.Platform` runtime base, no packaging needed |
|
||||
| `--filesystem=home` removed | Done — narrowed to `--filesystem=~/.ssh` (read-write, needed for `known_hosts` and SSH config) |
|
||||
| SSH known-hosts trust persists with narrowed permissions | Verified in sandbox against real infrastructure |
|
||||
| RDP works with zero filesystem permission | Verified — FreeRDP's cert trust store lives outside the sandboxed home path concerns entirely (see below) |
|
||||
| Private-key/export file pickers use the desktop portal | **Verified interactively** — `QFileDialog`'s Browse button correctly opens the native GTK portal chooser ("Select Private Key"), which can browse the full filesystem via user consent regardless of the sandbox's static `~/.ssh`-only grant |
|
||||
| Current, supported KDE runtime | Verified — upgraded to `6.11` (linter's recommended latest); confirmed the app still builds and launches against it |
|
||||
| Desktop entry validates | Verified |
|
||||
| Application icon validates | Verified — PNGs at all standard hicolor sizes, matching the real app icon |
|
||||
| MetaInfo/AppStream validates | Verified via both `appstreamcli validate` and Flathub's own `flatpak-builder-lint appstream` (0 errors either way) |
|
||||
| Screenshots present | Done — profiles view, active SSH session, active RDP session, all captured against real (test) infrastructure |
|
||||
| Release information present | Done — `<releases>` block with `v2026.9.8` (add an entry per future tagged release) |
|
||||
| Developer/project URLs present | Done — homepage, bugtracker, vcs-browser, developer block |
|
||||
| Architecture support decided | `x86_64` only (no ARM hardware available to test FreeRDP/WinPR on aarch64), set via `flathub.json`'s `only-arches` |
|
||||
| Flathub manifest linter passes | One expected finding remains: `finish-args-ssh-filesystem-access` (see below) — everything else passes, including `only-arches` placement and runtime-version currency |
|
||||
| AppStream linter passes | Passing (both `appstreamcli validate` and `flatpak-builder-lint appstream`) |
|
||||
| Clean install works without host dependencies | Verified via local `.flatpak` bundle install and launch, on both KDE 6.10 and 6.11 runtimes |
|
||||
|
||||
### `finish-args-ssh-filesystem-access` — expected, needs a submission-time justification
|
||||
|
||||
Flathub's linter flags *any* `~/.ssh` filesystem grant by policy — it's not a
|
||||
bug in this manifest, it's a deliberate prompt for the submitter to justify
|
||||
the access during PR review. Checked the linter's own exceptions list:
|
||||
several existing SSH-client apps already have this exact permission approved
|
||||
with justifications like *"Read-only access to ~/.ssh is required to load
|
||||
SSH keys for connecting to devices over SSH"* and *"Needed to manage SSH keys
|
||||
and configurations for connections"* — OrbitHub's case is the same pattern
|
||||
(read-write, specifically for `known_hosts` persistence and default identity
|
||||
file discovery). Include a similar justification in the submission PR.
|
||||
|
||||
## Related finding (not a packaging blocker)
|
||||
|
||||
During permission-narrowing research, RDP certificate verification was found
|
||||
to be completely disabled (`IgnoreCertificate=TRUE`, all server certificates
|
||||
silently accepted including *changed* ones). This has been fixed separately
|
||||
in `src/rdp_session_backend.cpp` — FreeRDP's own trust-on-first-use
|
||||
certificate store is now used, matching SSH's known-hosts model. Not a
|
||||
Flathub-specific issue, but worth noting since it was found in the course of
|
||||
this work.
|
||||
|
||||
## Explicitly out of scope for this repo
|
||||
|
||||
- Opening the actual submission PR against `github.com/flathub/flathub` —
|
||||
requires the maintainer's GitHub identity, done outside this repo.
|
||||
- ARM64 build/testing — no hardware available.
|
||||
- Flathub's post-acceptance developer-verification step — done via
|
||||
Flathub's own website after acceptance, using DNS control of
|
||||
`darksingularity.org`.
|
||||
|
||||
## Files
|
||||
|
||||
- Dev manifest (local iteration, `type: dir`): `packaging/flatpak/org.darksingularity.OrbitHub.yml`
|
||||
- Flathub submission manifest (pinned `type: git`): `packaging/flatpak/flathub/org.darksingularity.OrbitHub.yml`
|
||||
- AppStream metainfo: `packaging/linux/org.darksingularity.OrbitHub.metainfo.xml`
|
||||
- Desktop entry: `packaging/linux/org.darksingularity.OrbitHub.desktop`
|
||||
@@ -197,6 +197,8 @@ Validation:
|
||||
|
||||
Git:
|
||||
- Tag: `v0-m9-done`
|
||||
- Release: [v2026.9.8](https://git.darksingularity.org/DarkSingularity/orbithub/releases/tag/v2026.9.8) (`v2026.9.8` tag, installers for Windows/Linux/macOS)
|
||||
- Release: [v2026.9.8.2](https://git.darksingularity.org/DarkSingularity/orbithub/releases/tag/v2026.9.8.2) — same-day patch fixing RDP TLS certificate verification (was fully disabled) and preparing Flatpak packaging for Flathub submission
|
||||
|
||||
## Milestone 10 - v1.0 Stabilization
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"only-arches": ["x86_64"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
app-id: org.darksingularity.OrbitHub
|
||||
runtime: org.kde.Platform
|
||||
runtime-version: "6.11"
|
||||
sdk: org.kde.Sdk
|
||||
command: orbithub
|
||||
finish-args:
|
||||
- --share=network
|
||||
- --share=ipc
|
||||
- --socket=fallback-x11
|
||||
- --socket=wayland
|
||||
- --device=dri
|
||||
- --filesystem=~/.ssh
|
||||
modules:
|
||||
- name: orbithub
|
||||
buildsystem: cmake-ninja
|
||||
builddir: true
|
||||
config-opts:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
sources:
|
||||
- type: git
|
||||
url: https://git.darksingularity.org/DarkSingularity/orbithub.git
|
||||
tag: v2026.9.8
|
||||
commit: 68214db744714f19e0dcf01c1e535afed2dab1e7
|
||||
@@ -1,6 +1,6 @@
|
||||
app-id: org.darksingularity.OrbitHub
|
||||
runtime: org.kde.Platform
|
||||
runtime-version: "6.10"
|
||||
runtime-version: "6.11"
|
||||
sdk: org.kde.Sdk
|
||||
command: orbithub
|
||||
finish-args:
|
||||
@@ -9,7 +9,7 @@ finish-args:
|
||||
- --socket=fallback-x11
|
||||
- --socket=wayland
|
||||
- --device=dri
|
||||
- --filesystem=home
|
||||
- --filesystem=~/.ssh
|
||||
modules:
|
||||
- name: orbithub
|
||||
buildsystem: cmake-ninja
|
||||
|
||||
@@ -10,7 +10,38 @@
|
||||
</description>
|
||||
<launchable type="desktop-id">org.darksingularity.OrbitHub.desktop</launchable>
|
||||
<url type="homepage">https://git.darksingularity.org/DarkSingularity/orbithub</url>
|
||||
<url type="bugtracker">https://git.darksingularity.org/DarkSingularity/orbithub/issues</url>
|
||||
<url type="vcs-browser">https://git.darksingularity.org/DarkSingularity/orbithub</url>
|
||||
<developer id="org.darksingularity">
|
||||
<name>DarkSingularity</name>
|
||||
</developer>
|
||||
<provides>
|
||||
<binary>orbithub</binary>
|
||||
</provides>
|
||||
<content_rating type="oars-1.1" />
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Profiles organized into folders</caption>
|
||||
<image>https://git.darksingularity.org/DarkSingularity/orbithub/raw/branch/main/docs/images/screenshot-profiles.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Active SSH terminal session</caption>
|
||||
<image>https://git.darksingularity.org/DarkSingularity/orbithub/raw/branch/main/docs/images/screenshot-ssh-session.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Active RDP session</caption>
|
||||
<image>https://git.darksingularity.org/DarkSingularity/orbithub/raw/branch/main/docs/images/screenshot-rdp-session.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<releases>
|
||||
<release version="2026.9.8.2" date="2026-09-08">
|
||||
<url>https://git.darksingularity.org/DarkSingularity/orbithub/releases/tag/v2026.9.8.2</url>
|
||||
<description>
|
||||
<p>Fixes RDP connections silently accepting any server TLS certificate, including changed ones. Certificates are now verified with trust-on-first-use, matching SSH's known-hosts behavior.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="2026.9.8" date="2026-09-08">
|
||||
<url>https://git.darksingularity.org/DarkSingularity/orbithub/releases/tag/v2026.9.8</url>
|
||||
</release>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
+53
-11
@@ -420,9 +420,19 @@ BOOL orbitPreConnect(freerdp* instance)
|
||||
}
|
||||
|
||||
rdpSettings* settings = instance->context->settings;
|
||||
freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, TRUE);
|
||||
// FALSE gives the verify callbacks a short SHA256 fingerprint string
|
||||
// (e.g. "ab:cd:12:...") instead of the full PEM certificate blob, which
|
||||
// is what actually belongs in a user-facing changed-certificate message.
|
||||
freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, FALSE);
|
||||
freerdp_settings_set_bool(settings, FreeRDP_DesktopResize, TRUE);
|
||||
freerdp_settings_set_bool(settings, FreeRDP_IgnoreCertificate, TRUE);
|
||||
// Trust servers on first connection (matching SSH's "accept-new" known-hosts
|
||||
// policy) and persist that trust via FreeRDP's own certificate store, but
|
||||
// actually verify it on every subsequent connection: IgnoreCertificate
|
||||
// previously bypassed verification entirely, so a changed certificate
|
||||
// (e.g. an active MITM) was silently accepted, indistinguishable from a
|
||||
// legitimate server. See orbitVerifyChangedCertificateEx below.
|
||||
freerdp_settings_set_bool(settings, FreeRDP_IgnoreCertificate, FALSE);
|
||||
freerdp_settings_set_bool(settings, FreeRDP_AutoAcceptCertificate, TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -708,10 +718,13 @@ DWORD orbitVerifyCertificateEx(freerdp* instance,
|
||||
const char*,
|
||||
DWORD)
|
||||
{
|
||||
// With FreeRDP_AutoAcceptCertificate set, FreeRDP accepts and stores a
|
||||
// first-seen certificate itself without calling this callback at all;
|
||||
// it is kept as a defensive fallback for any code path that reaches it.
|
||||
if (instance != nullptr && instance->context != nullptr) {
|
||||
if (RdpSessionBackend* backend = backendFromContext(instance->context)) {
|
||||
emit backend->eventLogged(
|
||||
QStringLiteral("Accepting server certificate for %1:%2.")
|
||||
QStringLiteral("Trusting new server certificate for %1:%2.")
|
||||
.arg(QString::fromUtf8(host == nullptr ? "" : host))
|
||||
.arg(port));
|
||||
}
|
||||
@@ -725,21 +738,42 @@ DWORD orbitVerifyChangedCertificateEx(freerdp* instance,
|
||||
const char*,
|
||||
const char*,
|
||||
const char*,
|
||||
const char* newFingerprint,
|
||||
const char*,
|
||||
const char*,
|
||||
const char*,
|
||||
const char*,
|
||||
const char* oldFingerprint,
|
||||
DWORD)
|
||||
{
|
||||
// The server's certificate no longer matches the one trusted on a prior
|
||||
// connection. This is exactly the scenario TLS verification exists to
|
||||
// catch: either the server legitimately rotated its certificate, or an
|
||||
// active man-in-the-middle is presenting a different one. Refuse the
|
||||
// connection by default rather than silently trusting it.
|
||||
const QString hostStr = QString::fromUtf8(host == nullptr ? "" : host);
|
||||
const QString message = QStringLiteral(
|
||||
"Server certificate for %1:%2 has changed since it was last trusted. "
|
||||
"Connection refused for safety — this could mean the server's "
|
||||
"certificate was legitimately renewed, or that a different host is "
|
||||
"impersonating it. Previously trusted fingerprint: %3 — now "
|
||||
"presented: %4. If this change is expected, remove the stored entry "
|
||||
"for this host from FreeRDP's certificate store "
|
||||
"(~/.config/freerdp/server) and reconnect.")
|
||||
.arg(hostStr)
|
||||
.arg(port)
|
||||
.arg(QString::fromUtf8(oldFingerprint == nullptr ? "unknown" : oldFingerprint))
|
||||
.arg(QString::fromUtf8(newFingerprint == nullptr ? "unknown" : newFingerprint));
|
||||
|
||||
// Not logged directly here: recordCertificateRejection() feeds this
|
||||
// message into the generic connect-failure handling in workerMain(),
|
||||
// which already reports it through eventLogged/connectionError/setState
|
||||
// (the same channels every other RDP connection failure uses) — an
|
||||
// extra direct log call here would just duplicate that.
|
||||
if (instance != nullptr && instance->context != nullptr) {
|
||||
if (RdpSessionBackend* backend = backendFromContext(instance->context)) {
|
||||
emit backend->eventLogged(
|
||||
QStringLiteral("Accepting changed server certificate for %1:%2.")
|
||||
.arg(QString::fromUtf8(host == nullptr ? "" : host))
|
||||
.arg(port));
|
||||
backend->recordCertificateRejection(message);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* authReasonName(rdp_auth_reason reason)
|
||||
@@ -1451,6 +1485,11 @@ void RdpSessionBackend::setState(SessionState state, const QString& message)
|
||||
emit eventLogged(message);
|
||||
}
|
||||
|
||||
void RdpSessionBackend::recordCertificateRejection(const QString& reason)
|
||||
{
|
||||
m_certificateRejectionReason = reason;
|
||||
}
|
||||
|
||||
bool RdpSessionBackend::validateProfile(QString& message) const
|
||||
{
|
||||
const Profile& p = profile();
|
||||
@@ -1510,6 +1549,7 @@ void RdpSessionBackend::workerMain()
|
||||
m_workerRunning.store(false);
|
||||
return;
|
||||
#else
|
||||
m_certificateRejectionReason.clear();
|
||||
ensureFreeRdpRuntimeInitialized();
|
||||
|
||||
freerdp* instance = freerdp_new();
|
||||
@@ -1671,7 +1711,9 @@ void RdpSessionBackend::workerMain()
|
||||
emit eventLogged(QStringLiteral("RDP connect aborted: %1").arg(raw));
|
||||
emitStateAsync(SessionState::Disconnected, disconnectMessageForCode(code));
|
||||
} else {
|
||||
const QString mapped = mapRdpError(code);
|
||||
const QString mapped = m_certificateRejectionReason.isEmpty()
|
||||
? mapRdpError(code)
|
||||
: m_certificateRejectionReason;
|
||||
emit eventLogged(QStringLiteral("RDP connect failure detail: %1").arg(raw));
|
||||
emitConnectionFailureAsync(mapped, raw);
|
||||
emitStateAsync(SessionState::Failed, mapped);
|
||||
|
||||
@@ -90,6 +90,14 @@ private:
|
||||
void* m_cliprdrContext;
|
||||
QString m_pendingLocalClipboardText;
|
||||
|
||||
// Set synchronously by orbitVerifyChangedCertificateEx (called from this
|
||||
// object's own worker thread during freerdp_connect) when a server's TLS
|
||||
// certificate has changed since a prior trusted connection. Read back by
|
||||
// workerMain() right after freerdp_connect() fails, to show the specific
|
||||
// reason instead of a generic "TLS negotiation failed" message. Cleared
|
||||
// at the start of every connect attempt.
|
||||
QString m_certificateRejectionReason;
|
||||
|
||||
void setState(SessionState state, const QString& message);
|
||||
bool validateProfile(QString& message) const;
|
||||
void startWorker();
|
||||
@@ -109,6 +117,7 @@ public:
|
||||
void onCliprdrServerFormatList(bool hasUnicodeText);
|
||||
void onCliprdrServerFormatDataRequest(uint32_t requestedFormatId);
|
||||
void onCliprdrServerFormatDataResponse(bool success, const uint8_t* data, uint32_t size);
|
||||
void recordCertificateRejection(const QString& reason);
|
||||
private:
|
||||
void emitStateAsync(SessionState state, const QString& message);
|
||||
void emitConnectionFailureAsync(const QString& displayMessage, const QString& rawMessage);
|
||||
|
||||
Reference in New Issue
Block a user