TOTP MFA for teams: Shamir's Secret Sharing and zero trust OTP generation
Secure, distributed Multi-Factor Authentication system based on TOTP and Shamir’s Secret Sharing.
WIP! farMFA is still in development and may not be suitable for production use.
The user experience is terrible, the code is not tested, and the documentation is incomplete.
It’s here for Umarells as myself, who like to take a look at half-baked projects.
I believe you can trust the decryption to work in the future, as it’s based on the age encryption tool.
So it’s unlikely that your secrets will be lost or leaked.
You may also trust the Shamir’s Secret Sharing implementation, as it’s coming from the HashiCorp Vault project.
However, if you do feel like using this tool right now, please keep a copy of the code and the encrypted secrets in a safe place.
Multi-Factor Authentication (MFA) is often implemented using the TOTP standard (RFC6238) from OATH.
The generated One-Time Password may only be used once, or within a certain timeframe, depending on the server implementation.
farMFA is designed for shared environments where access to certain accounts should be restricted to very special occasions, such as accessing the root user of an AWS account. The goal is to restrict access so that multiple individuals are needed to grant authorization.
First, we apply the Shamir’s Secret Sharing scheme (Shamir 1979) to the original TOTP secret key. For instance, at least 3 out of 5 holders must combine their shares to reconstruct the secret.
Additionally, farMFA implements a workflow to reassemble the TOTP secret on a server, allowing users to access only the generated TOTP code without risking accidental leaks of the secret.
The two main workflows are:
During this phase, farMFA encrypts the Tocs based on the intended recipient (player). The current encryption strategy uses age.
Each player generates their age keypair via age-keygen:
age-keygen -o player_1.txt
# And if you're now testing by yourself, generate a few more keys
age-keygen -o player_2.txt
age-keygen -o player_3.txt
Players then share their public key with the dealer.
The dealer starts the process, usually providing their own age public key and keeping one Toc for themselves:
farmfa dealer \
--totp-secret HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ \
-p player_1=age1ct885ya9458q63ea6fdl42ajl3rpkasj45c5hkkp5ccpf7c80cssk688fr \
-p player_2=age1emv0n3j90658ke68ktrl7ne9w5gqwrcuetww5ac5rhdkeg638ykqg7uuhd \
-p player_3=age1rl2ranstxv2dd8pmamvwukz35zrcv9p3knthp0kal3q47gyyea5qk0cl4q
This generates encrypted Tocs, one per player. Each player then decrypts and inspects their Toc using the age CLI:
age -i player_1.txt --decrypt
[paste the encrypted Toc - Ctrl+D]
{
"group_id": "7GCUCI2Y",
"group_size": 3,
"group_threshold": 3,
"note":"",
"share":"C2iCgb3pRfxPJw2a7od8p4ShkhrDWAm/Dt6ioQNAVFPZ",
"toc_id":"5oaAUX9b6aBE"
}
Players must store their Toc securely.
When a user wants to log in, they start a session. The user initiating the login is called the applicant.
# For testing purposes, we'll now start the server locally - in production, this should be a remote server
farmfa server &
http --body POST localhost:8080/sessions toc_zero:='{"group_id":"J7UHQPZK","group_size":5,"group_threshold":2,"share":"5Ovpu-PKEeYXx5ebiQhzU_AT0Z79POf8GGkskDp3its=urkBkVXr-pYjIvTt1ch2YJILCScAoRquLoX_VBxxps4=","toc_id":"TFW52GAK"}'
{
"complete": false,
"created_at": "2021-02-24T18:05:53.507396809+01:00",
"id": "V5K6QD4XUFLRGCZH",
"kek": "MIotBtYOWrXnQCj6o9rSNIkNeRfIPhNLjEdQtJDDemPRJcKUbme+iq5K2Hc6Ypil6Loi/K9rnN/YrJiKDT/tPi8kFq2WuAY8zl8=",
"tek": "age1cl5ndmdsq09vs09awlpt8nd4cdu6fpl33lpyyuv75syknqalkpdszwnwyc",
"toc_group_id": "J7UHQPZK",
"tocs_in_group": 5,
"tocs_provided": 1,
"tocs_threshold": 2
}
The oracle returns:
The applicant shares the TEK and session ID with team members holding the other Tocs. These team members, who can authorize the applicant, are called constituents.
Constituents encrypt and armor their Toc with TEK using age:
export ENCTOC=$(echo '{"group_id":"J7UHQPZK","group_size":5,"group_threshold":2,"share":"zxRrozuUaCMgn_u6ajZStlV7RKwhp0keT9aQoXAEruI=nfx2CPJfKiFM32zLmtxHjV94OlZOgBevV1Whrx-lslU=","toc_id":"K5FSSJSV"}' | age -r age1cl5ndmdsq09vs09awlpt8nd4cdu6fpl33lpyyuv75syknqalkpdszwnwyc -a)
Constituents upload the encrypted Toc to the oracle, associating it with the existing session:
http POST localhost:8080/sessions/V5K6QD4XUFLRGCZH/tocs encrypted_toc="$ENCTOC"
HTTP/1.1 200 OK
Once the oracle has enough Tocs, the applicant may query the oracle. The applicant provides the KEK to let the oracle decrypt the Tocs and generate the TOTP:
http --body POST localhost:8080/sessions/V5K6QD4XUFLRGCZH/totp kek="MIotBtYOWrXnQCj6o9rSNIkNeRfIPhNLjEdQtJDDemPRJcKUbme+iq5K2Hc6Ypil6Loi/K9rnN/YrJiKDT/tPi8kFq2WuAY8zl8="
{
"totp": "824588"
}