Skip to content

Instantly share code, notes, and snippets.

@pyllyukko
Last active February 27, 2026 15:51
Show Gist options
  • Select an option

  • Save pyllyukko/a1ed6baa6b638b805a30b16960399e94 to your computer and use it in GitHub Desktop.

Select an option

Save pyllyukko/a1ed6baa6b638b805a30b16960399e94 to your computer and use it in GitHub Desktop.
FINEID notes

FINEID

Card

pcsc_scan (from pcsc-tools) output:

3B 7F 96 00 00 80 31 B8 65 B0 85 04 02 1B 12 00 F6 82 90 00
	Finnish ID-card v5.0(?) (eID)
	https://dvv.fi/en/fineid-specifications

S/MIME with Mutt, gnupg-pkcs11, GPGME & mpollux

The communication flow is as follows: mutt -> gpgme -> (gpgsm) -> gpg-agent -> gnupg-pkcs11-scd -> libcryptoki -> smartcard

Prerequisites

Configuration

~/.gnupg/gpg-agent.conf:

scdaemon-program /usr/bin/gnupg-pkcs11-scd

~/.gnupg/gnupg-pkcs11-scd.conf:

providers p1
provider-p1-library /usr/lib64/libcryptoki.so

muttrc:

set crypt_use_gpgme
set smime_default_key="12345678.0" <- check your ID with the commands described below in "Usage"
set nohonor_disposition

Usage

gpgsm --learn-card

Run gpgsm --list-keys and check the ID of your cert with key usage: digitalSignature keyEncipherment dataEncipherment.

You can test whether your card is identified by running gpg-agent --server gpg-connect-agent and issuing the SCD LEARN command.

S/MIME with Mutt, OpenSSL & mpollux

Prerequisites

Determine your private key ID with pkcs11-tool --module /usr/lib64/libcryptoki.so --list-objects --type privkey. It should look like this:

Private Key Object; RSA 
  label:      todentamis- ja salausavain
  ID:         45
  Usage:      decrypt, sign

Determine the IDs of your "todentamis- ja salausvarmenne" certificate and the intermediate CA "VRK Gov. CA for Citizen Certificates - G3" certificate with the following command:

pkcs11-tool --module /usr/lib64/libcryptoki.so --list-objects --type cert

Extract the "todentamis- ja salausvarmenne" and "VRK Gov. CA for Citizen Certificates - G3" certificates from the card into separate files with (repeat and save into different files):

pkcs11-tool --module /usr/lib64/libcryptoki.so --read-object --id KEY_ID --type cert --output-file cert.der

Convert them from DER to PEM (repeat for both certificates):

openssl x509 -in cert.der -inform DER -out cert.pem -outform PEM

Configuration

OpenSSL

Create the following OpenSSL configuration (see Using the engine from the command line):

openssl_conf = openssl_init

[openssl_init]
engines = engine_section

[engine_section]
pkcs11 = pkcs11_section

[pkcs11_section]
engine_id = pkcs11
dynamic_path = /usr/lib64/engines-1.1/pkcs11.so
MODULE_PATH = /usr/lib64/libcryptoki.so
init = 0

Mutt

  • You need to disable GPGME in Mutt, so you can use the old school smime_*_commands
  • cert.pem is the extracted "todentamis- ja salausvarmenne" certificate
  • ca.pem is the "VRK Gov. CA for Citizen Certificates - G3" intermediate CA certificate
  • The key ID is hardcoded and determined in the "Determine your private key ID" part
  • smime_default_key and smime_sign_as are just dummy values so Mutt doesn't prompt you for the key ID

Add the following to your muttrc:

unset crypt_use_gpgme
set smime_default_key="12345678.0"
set nohonor_disposition
set smime_decrypt_command="OPENSSL_CONF=/path/to/openssl.conf openssl smime -decrypt -engine pkcs11 -keyform engine -inform DER -in %f -inkey 45"
set smime_sign_as="87654321.0"
set smime_sign_command="OPENSSL_CONF=/path/to/openssl.conf openssl smime -sign -engine pkcs11 -keyform engine -signer /path/to/cert.pem -certfile /path/to/ca.pem -inkey 45 -in %f -outform DER"

Usage

Mutt will ask you for a passphrase, but as this comes from mpollux you can enter anything to the prompt.

SSH

If you have #1608 patch, you can get your SSH public key with pkcs15-tool --read-ssh-key ID where ID is the ID of your "todentamis- ja salausvarmenne" certificate. You also need to have the following in /etc/opensc.conf or otherwise pkcs15-tool will not work:

app default {
	card_atr 3b:7f:96:00:00:80:31:b8:65:b0:85:04:02:1b:12:00:f6:82:90:00 {
		driver = fineid;
	}
}

Do note that the patch does not seem to work properly with FINEID v5, except for the pkcs15-tool part so leaving it to your opensc.conf is not recommended.

Other option to convert your certificate into SSH authorized_key is to run openssl x509 -in cert.pem -noout -pubkey | ssh-keygen -i -m PKCS8 -f /dev/stdin.

You need to have the following in your SSH client configuration (ssh_config):

Host ...
	PKCS11Provider /usr/lib64/libcryptoki.so

Then just add the public key into your authorized_key and you can authenticate with your FINEID card.

mPollux notes

mPollux stores "DigiSign WebSigner ROOT Certificate" into your ~/.pki/nssdb database. It uses a password, which you can find from ~/.digisign/Seed.txt.

See DigiSignApplication.bin:

nth   paddr      vaddr      len  size section type    string
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
2856  0x0043b520 0x0043b520 19   20   .rodata ascii   /.digisign/Seed.txt
52823 0x006d4041 0x006d4041 2288 2289 .rodata ascii   #!/bin/bash\n\npwdfile=$1\nnsdCreate=$2\n\nif [ -f /usr/lib/libcryptoki.so ]; then\n   cryptokiPath=/usr/lib/libcryptoki.so;\nelse\n   cryptokiPath=/usr/lib64/libcryptoki.so;\nfi\n\nif [ "$nsdCreate" = "true" ]; then\n   certutil -d sql:$HOME/.pki/nssdb -N -f $pwdfile\nfi\n\nif [ $? -gt 0 ]; then\n   echo nssdb creation failed\n   exit 1\nfi\n\nhostname=$(hostname)\nif [ -z "$hostname" ]; then\n   echo hostname not defined\n   exit 1\nfi\n\nrootname="DigiSign WebSigner ROOT Certificate for $hostname"\n\n#Wrong password exit status: \n#   local nssd: 2\n#   firefox and thunderbird: 3\n\n#add root certificate to nssd db sent by parameters\naddRootCertificate()\n{\n   dbPath=$1\n   isPwdFile=$2\n   exitStatus=$3\n\n   certCheck=$(certutil -d $dbPath -L | grep "$rootname")\n   if [ -n "$certCheck" ]; then\n      #remove old certificate, ignore if it doesn't exist\n      certutil -d $dbPath -D -n "$rootname"\n   fi\n\n   if [ "$isPwdFile" = "true" ]; then\n      certutil -d  $dbPath -A -t "TC,Tw,Tw" -n "$rootname" -i /etc/xdg/Fujitsu/SSLCA.cer -f $pwdfile\n   else\n      certutil -d $dbPath -A -t "TC,Tw,Tw" -n "$rootname" -i /etc/xdg/Fujitsu/SSLCA.cer\n   fi\n   if [ $? -gt 0 ]; then\n       #if certificate inset failed for some reason, certutil added incorrect data to db. Clean incorrect data from db.\n       certutil -d $dbPath -D -n "$rootname"\n       exit $exitStatus\n   fi\n   #update pkcs11.txt\n   isInFile=$(cat $dbPath/pkcs11.txt | grep -c "DigiSign PKCS#11 Module")\n\n   if [ $isInFile -eq 0 ]; then\n      echo "library=$cryptokiPath" >> $dbPath/pkcs11.txt\n\t  echo "name=DigiSign PKCS#11 Module" >> $dbPath/pkcs11.txt\n   fi\n}\n\naddRootCertificate $HOME/.pki/nssdb true 2\n\nif [ -d ~/snap/firefox ]; then\necho snap version\n   for certDB in $(find ~/snap/firefox/common/ -name "cert9.db")\n   do\n      certDir=$(dirname ${certDB});\n\t  addRootCertificate $certDir false 3\n   done\nelse\n   for certDB in $(find  ~/.mozilla* ~/.thunderbird -name "cert9.db")\n   do\n      certDir=$(dirname ${certDB});\n\t  addRootCertificate $certDir false 3\n   done\n   #we need also check cet8.db for old version of Firefox or thunderbird. More related to Ubuntu 32 version\n   for certDB in $(find  ~/.mozilla* ~/.thunderbird -name "cert8.db")\n   do\n      certDir=$(dirname ${certDB});\n\t  addRootCertificate $certDir false 3\n   done\nfi\n\nexit 0\n

Atostek ID notes

Atostek ID is the new software that is going to replace mPollux. All these notes are about the Debian package.

Version 4.4.1.0

https://files.fineid.fi/download/atostek/4.4.1.0/linux/AtostekID_DEB_4.4.1.0.deb

6fec3d89bf2ff95a2f12acd2d28563b9a4ce416d82de52e882401c4bce4ca647  AtostekID_DEB_4.4.1.0.deb

Mysterious private key

A mysterious private key is embedded within the binary:

nth   paddr      vaddr      len  size section type    string
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
2197  0x005735a1 0x005735a1 3500 3501 .rodata ascii   -----BEGIN ENCRYPTED PRIVATE KEY-----\r\n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\r\n-----END ENCRYPTED PRIVATE KEY-----\r\n

Couldn't find any code referencing it and apparently the installCA function does generate it's own keys.

installCA function

You can find a reference string:

nth   paddr      vaddr      len  size section type    string
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
471   0x0056521c 0x0056521c 27   28   .rodata ascii   Entering installCA function

From there you can find the function:

address            noret size  nbbs edges    cc cost          min bound range max bound          calls locals args xref frame name
================== ===== ===== ===== ===== ===== ==== ================== ===== ================== ===== ====== ==== ==== ===== ====
0x000000000013c180     0 7025   256   418   164 2394 0x000000000013c180  7279 0x000000000013ddef   288   31      1    1   520 fcn.0013c180

PKCS #12 password

I dislike the idea of having random private keys stored on my computer and not knowing how to unlock them.

When you run the binary with the -installCA parameter, it creates this AID-ERASMARTCARD-EHOITO-FI-CA CA certificate for you into ~/.local/share/Atostek Oy/Atostek ID/ directory. The password for the PKCS #12 file is DyCpu4fB4y4WdsnCG6k3. The password is the same between different machines (e.g. not using any instance specific attributes to generate a unique one). You can breakpoint PKCS12_create to catch it.

You can see the password loaded into the r12 register before calling the function at 0x0013e430:

            0x0013ca7a      4c8b6590       mov r12, qword [rbp - 0x70]

The -installSCSCA parameter installs another cert (AID_SCSCA) and the password for that is Le3svzKWE2OD4cgWzXan.

The deobfuscation function (for installCA) is as follows:

╭ 82: fcn.0013e1f0 (int64_t arg1);
│ `- args(rdi)
│           0x0013e1f0      53             push rbx
│           0x0013e1f1      31d2           xor edx, edx
│           0x0013e1f3      4889fb         mov rbx, rdi                ; arg1
│           0x0013e1f6      4c8d05a372..   lea r8, str.xYue6hgg5ImeRot68oYdAdding_certificate ; 0x5654a0 ; "xYue6hgg5ImeRot68oYdAdding certificate"
│           0x0013e1fd      488d357c72..   lea rsi, [0x00565480]
│           0x0013e204      4883ec20       sub rsp, 0x20
│           0x0013e208      4889e1         mov rcx, rsp
│           ; CODE XREF from fcn.0013e1f0 @ 0x13e227(x)
│       ╭─> 0x0013e20b      66410f6e0410   movd xmm0, dword [r8 + rdx]
│       ╎   0x0013e211      660f6e0c16     movd xmm1, dword [rsi + rdx]
│       ╎   0x0013e216      660fefc1       pxor xmm0, xmm1
│       ╎   0x0013e21a      660f7e0411     movd dword [rcx + rdx], xmm0
│       ╎   0x0013e21f      4883c204       add rdx, 4
│       ╎   0x0013e223      4883fa14       cmp rdx, 0x14
│       ╰─< 0x0013e227      75e2           jne 0x13e20b
│           0x0013e229      4889df         mov rdi, rbx
│           0x0013e22c      be14000000     mov esi, 0x14
│           0x0013e231      4889ca         mov rdx, rcx
│           0x0013e234      e8976ef9ff     call sym.imp.QString::fromLatin1_QByteArrayView_ ; QString::fromLatin1(QByteArrayView)
│           0x0013e239      4883c420       add rsp, 0x20
│           0x0013e23d      4889d8         mov rax, rbx
│           0x0013e240      5b             pop rbx
╰           0x0013e241      c3             ret

The XOR key is this:

nth   paddr      vaddr      len  size section type    string
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
497   0x005654a0 0x005654a0 39   40   .rodata ascii   xYue6hgg5ImeRot68oYdAdding certificate

The obfuscated password is here:

- offset -  4041 4243 4445 4647 4849 4A4B 4C4D 4E4F  0123456789ABCDEF
0x0013c240  4889 8568 feff ffe8 0491 f9ff 488b 75a0  H..h........H.u.
0x0013c250  488b 5598                                H.U.

You can deobfuscate the "secret" with r2 -n -qc 'e io.cache=true; b 20; s 0x565480; wox `p8 20 @ 0x5654a0`; ps' atostekid.

For the installSCSCA the deobfuscation function is here:

address            noret size  nbbs edges    cc cost          min bound range max bound          calls locals args xref frame name
================== ===== ===== ===== ===== ===== ==== ================== ===== ================== ===== ====== ==== ==== ===== ====
0x0000000000219220     0   82     3     3     2   27 0x0000000000219220    82 0x0000000000219272     1    0      1    4    40 fcn.00219220

Deobfuscate with: r2 -n -qc 'e io.cache=true; b 20; s 0x56fac0; wox `p8 20 @ 0x56fae0`; ps' atostekid.

References

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