Skip to content

modules/tools/browsers: register the OpenSC PKCS#11 module in both browsers - #240

Open
RISK-alt wants to merge 10 commits into
cloud-gouv:mainfrom
RISK-alt:browsers-smartcard
Open

modules/tools/browsers: register the OpenSC PKCS#11 module in both browsers#240
RISK-alt wants to merge 10 commits into
cloud-gouv:mainfrom
RISK-alt:browsers-smartcard

Conversation

@RISK-alt

Copy link
Copy Markdown
Contributor

Closes #208. Stacked on #239, review that one first — this branch starts from
it, so the diff only makes sense once #239 is in.

Smartcards were not picked up by the browsers Sécurix ships, even though opensc
is already in the system profile and pcscd is already enabled by
modules/security-keys.nix. This is only about telling the browsers where the
module lives.

Firefox gets it declaratively, through the SecurityDevices policy. Worth
knowing: nixpkgs' Firefox wrapper already offers
wrapperConfig.smartcardSupport, which sets that same policy — but it writes it
to $libDir/distribution/policies.json. Firefox returns the first policy file
it finds and does not merge, and programs.firefox writes
/etc/firefox/policies/policies.json, which wins. The wrapper's copy would
never be read, so the policy is set explicitly here, with an absolute store
path.

Chromium has no equivalent policy on Linux. It reads the NSS database of the
user, in ~/.pki/nssdb. nixpkgs' NSS does not load p11-kit modules either
(useP11kit only swaps libnssckbi for p11-kit's trust module), so there is no
system-wide shortcut: a small user unit creates the database when missing and
registers each module with modutil. Each module is deleted before being added,
so the entry follows the store path across upgrades instead of pointing at a
library the garbage collector has reclaimed.

I did consider OpenSC's own pkcs11-register, and dropped it: it also rewrites
Firefox profiles, which the policy already covers, and it registers a different
provider for Chrome than for Firefox (OpenSC#1818).

The option is generic — securix.browser.securityDevices takes any PKCS#11
module — with securix.browser.enableSmartcards filling it with OpenSC by
default, which is what the issue asks for.

Tested in the VM test, both halves separately: the policy file carries the
module path, and modutil -list shows the security device in the database of a
lingering test user.

One thing you will see in that test's log, so that nobody has to go looking for
it: pcscd logs Rejected unauthorized PC/SC client there. That is the
modutil -list call loading the module and probing for a reader as a user the
test reaches through su, with no active session. pcsc-lite's polkit action
grants access_pcsc to active local sessions only (allow_active = yes,
allow_inactive = no), which is what a real Sécurix user has. Registering the
module does not talk to pcscd at all.

rlahfa-dinum and others added 10 commits August 11, 2026 23:21
This rework the abstraction module for Firefox into a generic
abstraction for both major browsers: Chromium and Firefox.

This enables developers to let user choose between Firefox and Chromium
and configure them uniformly.

Obviously, abstracting all options is impossible, so a developer should
always consider using the fine-grained API of a browser if needed.

Signed-off-by: Ryan Lahfa <ryan.lahfa.ext@numerique.gouv.fr>
The module as introduced did not evaluate:

- homepage-dashboard.nix read securix.homepage-dashboard.{enable,bookmarks}
  without declaring either option;
- lockFlags was typed as an enum although it holds a list of flags;
- nullOr was applied to two arguments instead of to (submodule proxyConfig);
- firefox.nix and chromium.nix consumed a homepage option neither declared;
- chromium.nix dereferenced cfg.proxy unconditionally although it defaults to
  null, and read autoConfigUrl where option-types.nix declares autoConfigURL;
- the per-browser extensions were typed loosely enough that each browser could
  receive the other one's shape.

Type the extensions submodule so that each browser gets what it expects.

The Chromium proxy policy also used mkIf inside programs.chromium.extraOpts.
That option is a plain attribute set, so the module system never discharges the
property, and {_type = "if"; ...} would be serialised as-is into the policy
JSON. Build the conditional parts with optionalAttrs instead.

Signed-off-by: risk-alt <aldu6974@gmail.com>
securix.browser.enable defaulted to false and modules/tools/default.nix only
imported the module, so the browser abstraction removed Firefox from the system
instead of configuring it. Enable it by default, and default the browser list to
Firefox, which is what Sécurix shipped so far. The default lives on the option
rather than in modules/tools/default.nix on purpose: list options concatenate on
merge, so a definition there would force users to mkForce their way out of
Firefox.

Restore the two things the refactor dropped along the way: the Tridactyl native
messaging host, and the Bitwarden extension.

programs.chromium only writes policy files, so install the package explicitly,
otherwise picking Chromium configures a browser that is not there.

Drop the bare firefox entry from environment.systemPackages: programs.firefox
already installs the wrapped package, and shipping both puts two bin/firefox in
collision inside the system profile.

Enabling the module for real also surfaced one last type error: the browser-wide
lockFlags were typed as listOf lockFlagEnum, where lockFlagEnum is the list of
allowed flags rather than a type.

Signed-off-by: risk-alt <aldu6974@gmail.com>
programs.chromium.initialPrefs feeds /etc/chromium/initial_preferences, whose
schema is the initial preferences one, not the Chrome Enterprise policy one.
Every policy written there was silently ignored. Move them to extraOpts, which
lands in /etc/chromium/policies/managed/extra.json.

Three keys do not exist as written, checked against the policy list in
components/policy/resources/templates/policies.yaml:

- IsolateOrigins is typed as a string holding a comma separated list of
  origins; the intent here, isolating every origin, is SitePerProcess;
- GenAiSettings is GenAiDefaultSettings, an int-enum where 2 means "do not
  allow GenAI features";
- BuiltInAIAPIIsEnabled is BuiltInAIAPIsEnabled.

Wire the home page through programs.chromium.homepageLocation, which was the
remaining TODO on the Chromium side, and honour the extension lock flag the way
the Firefox module already does.

Pass null rather than an empty list when no extension is configured, so that
ExtensionInstallForcelist stays out of the generated policy file.

Signed-off-by: risk-alt <aldu6974@gmail.com>
The browser modules only produce configuration, so assert on what they emit:
the Firefox policy file, both Chromium managed policy files, the presence of
each browser in the system profile, and the local homepage dashboard answering
on its port.

This is what would have caught the policies landing in initial_preferences.

Signed-off-by: risk-alt <aldu6974@gmail.com>
securix.firefox is now one browser behind the securix.browser abstraction.

Signed-off-by: risk-alt <aldu6974@gmail.com>
Browsers on Sécurix have to be told about PKCS#11 modules; smartcards are not
picked up on their own. Add a generic option holding the modules to register,
and consume it on the Firefox side through the SecurityDevices policy.

nixpkgs' Firefox wrapper offers wrapperConfig.smartcardSupport, but it writes
the policy to $libDir/distribution/policies.json. Firefox returns the first
policy file it finds without merging, and programs.firefox writes
/etc/firefox/policies/policies.json, so the wrapper's copy would never be read.
Set the policy here instead, with an absolute store path, which also makes the
wrapper's library path irrelevant.

Signed-off-by: risk-alt <aldu6974@gmail.com>
…r NSS database

Chromium has no enterprise policy to install a PKCS#11 module on Linux: it reads
the NSS database of the user, in ~/.pki/nssdb. nixpkgs' NSS does not load
p11-kit modules either -- useP11kit only swaps libnssckbi for p11-kit's trust
module, it does not carry the patch that would load /etc/pkcs11/modules -- so
there is no system-wide place to declare them and the database has to be
populated per user.

Add a user unit that creates the database when it is missing and registers each
module with modutil. Each module is deleted before being added, so that the
entry follows the store path across upgrades rather than pointing at a library
the garbage collector has reclaimed.

OpenSC ships pkcs11-register for this, but it also rewrites Firefox profiles,
which the SecurityDevices policy already covers, and it registers a different
provider for Chrome than for Firefox (OpenSC#1818).

Signed-off-by: risk-alt <aldu6974@gmail.com>
Smartcards were not picked up by the browsers Sécurix ships, even though opensc
is already in the system profile and pcscd is already enabled. Register the
OpenSC PKCS#11 module in both browsers by default, and keep pcscd enabled from
here as well, so the browser module does not depend on security-keys.nix being
imported.

Closes cloud-gouv#208

Signed-off-by: risk-alt <aldu6974@gmail.com>
Assert on both halves of the feature: the SecurityDevices policy Firefox reads,
and the NSS database entry Chromium reads, checked through modutil for a
lingering test user.

Signed-off-by: risk-alt <aldu6974@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Install opensc smartcard driver automatically in the installed browsers (Firefox / Chromium)

2 participants