Skip to content

Keyring file corruption: raw newlines in PEM certs break entire GLib KeyFile on cert renewal #8

Description

@IgnisNatura

Summary

proton-vpn-gtk-app (via python-proton-keyring-linux) writes VPN session credentials into the GNOME keyring as a single JSON secret value whose ClientKey, Certificate, and ServerPublicKey fields contain PEM blocks with literal, unescaped newlines. When the target keyring is stored in GNOME's unencrypted keyfile format (no keyring password set — a common and supported configuration, e.g. any system relying on the login keyring auto-unlocking, or one deliberately left passwordless), those raw newlines break GLib.KeyFile's parser for the entire keyring file, not just the one entry. Every other secret stored in that keyring (browser saved passwords, other apps' Safe Storage keys, etc.) becomes unreadable until the file is manually repaired, and GNOME Keyring responds by offering to silently create a brand-new empty keyring — which, if the user accepts and then relaunches apps like a browser, causes them to mint new encryption keys and permanently orphan previously-saved secrets.

This has recurred 5 times over ~3 weeks on the same machine, each time on a ProtonVPN session/cert renewal.

Environment

  • proton-vpn-gtk-app: 4.18.1-1 (Arch/AUR native package; confirmed identical version 4.18.1 on the Flathub flatpak build, so this is not packaging-specific)
  • python-proton-vpn-api-core: 5.6.10-1
  • python-proton-keyring-linux: 0.2.3-1
  • Distro: Arch Linux (Omarchy)
  • Keyring backend: gnome-keyring-daemon, Default.keyring with no keyring password set (unencrypted keyfile-format storage — this is the config that exposes the bug)

Exact error

g-key-file-error-quark: Key file contains line "-----END PUBLIC KEY-----", "Certificate": "-----BEGIN CERTIFICATE-----" which is not a key-value pair, group, or comment (1)

Raised by GLib.KeyFile.load_from_file() when trying to parse ~/.local/share/keyrings/Default.keyring.

Root cause

Inspecting the corrupted file directly, the offending entry looks like this (secrets redacted, structure preserved):

secret={"UID": "...", "AccessToken": "...", ..., "vpn": {"vpninfo": {...}, "certificate": {"SerialNumber": "...", "ClientKeyFingerprint": "...", "ClientKey": "-----BEGIN PUBLIC KEY-----
<base64 lines with REAL newlines>
-----END PUBLIC KEY-----", "Certificate": "-----BEGIN CERTIFICATE-----
<base64 lines with REAL newlines>
-----END CERTIFICATE-----", ...}, ..., "ServerPublicKey": "-----BEGIN PUBLIC KEY-----
<base64 lines with REAL newlines>
-----END PUBLIC KEY-----"}}}

A GLib.KeyFile-format file requires every value to be a single line; multi-line string values must have their newlines escaped as \n before being written. This JSON blob was written with the PEM certificates' actual newline characters intact, so the single secret= line becomes dozens of malformed lines, and the parser gives up on the whole file.

This has occurred identically 5 times (2026-08-26, 2026-09-02 ×2, 2026-09-13, 2026-09-15), always immediately following a ProtonVPN session/cert renewal, always with the same three fields affected (ClientKey, Certificate, ServerPublicKey).

Steps to reproduce

  1. Use proton-vpn-gtk-app on a system where the GNOME login/default keyring has no password set (unencrypted keyfile format).
  2. Sign in / let a VPN session cert renew.
  3. Inspect ~/.local/share/keyrings/Default.keyring — the secret= line for the Proton SSO account entry spans multiple raw lines instead of one \n-escaped line.
  4. Attempt to load it: GLib.KeyFile.new().load_from_file(path, GLib.KeyFileFlags.NONE) → raises the error above.
  5. In practice this surfaces to the user as a "keyring file corrupted" or "an application wants to create a new keyring called Default" popup.

Suggested fix

Whatever serializes the VPN session JSON before handing it to the Secret Service API (python-proton-keyring-linux) should either:

  • Ensure the underlying secret-storage call properly escapes the value for the target backend (this looks like it may be bypassing normal libsecret/Secret Service escaping and writing closer to the raw file), or
  • At minimum, avoid embedding raw multi-line PEM text in a value that's going to be stored in a line-oriented format — e.g. base64-encode or otherwise flatten ClientKey/Certificate/ServerPublicKey before nesting them in the JSON secret.

Related existing reports

This looks like the same underlying issue behind:

  • #16 "Finally found the keyring problem" (still open)
  • #100 "Keyring bug still still still alive..." (still open, reporter using flatpak — confirms this isn't packaging-specific)

Neither of those pins down the actual GLib KeyFile parse failure or the raw-PEM-newline mechanism, so filing this separately with the concrete root cause in case it helps prioritize a real fix rather than another manual keyring wipe.

Workaround (for anyone hitting this)

The corruption is confined to one malformed multi-line secret= value; re-collapsing it back into a single \n-escaped line (matching every other line in the file) restores the file immediately with zero data loss to other keyring entries. No need to delete/recreate the whole keyring.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions