Build a Voter Registration System with SSH Keys (the lazy way)

Did you know you can digitally sign and validate messages with your ssh keys?

Let’s face it, ssh keys can be a dry topic. Let’s spruce up the conversation with a fun scenario!

You are a Cyber Security Pro for the United States of Utopia. You are tasked with ensuring the integrity of their up comming National Election.

After scratching your chin for a full minute on how you might emabark on this quest you remember a security talk that happened on the Lazy Dev School blog titled “How to Generate an ed25519 SSH Key Pair”.

Sitting next to you, a collegue is slurping up a nitro cold brew through that sippy cup lid.

You blert out loud “Could voter fraud be prevented with ssh keys?”

Your collegue turns their head towards you and give you a look that says “Please don’t wear your tin foil hat to work”.

Unwaivering in your idea, you head back to your cave, throw on some lazy pants and start flying away on your keyboard.

Prerequisites

If for some reason you landed here without knowing what an SSH key is, check out the Official Lazy Dev School article on “How to Generate an ed25519 SSH Key Pair”.

Overview

In order to create a voter registration system we need to talk about architecture

Establish a Central Voting Authority

The voting authority is the most trusted entity in the system. Their job is to certify voter registration offices. If we don’t do this step, some yahoo could open a fake voter registration office without your approval.

For our purposes we will call the Central Voting Authortity the CVA.

In this step we will generate the cva key pair. The private key is the agenciey’s secret stamp. The public key is the agency’s official seal that everyone can recognize.

The CVA private key is the most important thing in this whole system. If this key is compromised, the whole system is breaks.

# Generate a new Voting Authority key
ssh-keygen -f cva_key -C "Central Voting Authority" -N ""
  1. -f cva_key: specifies the filename for the key. In this case, it will create files named cva_key (private key) and cva_key.pub (public key).
  2. -C "Central Voting Authority": This flag adds a comment to the key. The comment “Central Voting Authority” is being attached to this key for identification purposes.
  3. -N "": This flag sets an empty passphrase for the key. The double quotes indicate no passphrase will be used.ey

Create a Voter Registration Offices

Now we can create official voter registration offices authorized by the CVA. These offices will be responsible for registering voters and issuing digital signatures that can be used to verify their vote.

# Create a key for a local registration office
ssh-keygen -f reg_office_01 -C "Registration Office 1" -N ""
ssh-keygen -f reg_office_02 -C "Registration Office 2" -N ""
  1. -f reg_office_01: specifies the filename for the key. In this case, it will create files named reg_office_01 (private key) and reg_office_01.pub (public key).
  2. -C "Registration Office 1": This flag adds a comment to the key. The comment “Central Voting Authority” is being attached to this key for identification purposes.
  3. -N "": This flag sets an empty passphrase for the key. The double quotes indicate no passphrase will be used.ey
# The CVA certifies the registration office's public key
ssh-keygen -s cva_key -I reg_office_01@digitopia -n voter_registration -V +52w reg_office_01.pub
ssh-keygen -s cva_key -I reg_office_02@digitopia -n voter_registration -V +52w reg_office_02.pub

When a voter registers, the registration is signed by the office’s private key along with it’s certificate. The certificate is only used durring the signing or verification process. We will see those steps at the end of this tutorial.

  1. -s cva_key: This flag specifies the private key to be used for signing. It’s using the cva_key we generated in the previous command.
  2. -I reg_office_01@digitopia: The -I false is for identification and helps track which certificate belongs to which office.
  3. -n voter_registration: Specifies the principals authorized for this certificate. In this case it ensures it can only be used for voter registration purposes. You will not be able to see this text anywhere, it becomes part of the certificate.
  4. -V +52w: This sets the validity period for the certificate. In this case, it’s valid for 52 weeks (approximately one year) from the current date.
  5. reg_office_01.pub: This is the public key file being certified.

The -V +52w flag is used to specify the validity period for the key. This implies there is some kind of key rotation process in place. It’s alwas a good move to have a defined key rotation process for high stakes systems.

Publish the official Polling Locations (Authorized Signers File)

The allowed signers file lists all the official voter registration offices. If you skip this step voters would not know which voter registration locaions are legitament.

The allowed signers file is similar to the authorizied_keys file on a server.

The authorizied_keys file lists out all the public keys authorized to login to a given server.

In the context of this example, the Authorized Signers file lists out all the voter registration offices authorized to register voters.

AND

This file establishes the CVA as the “root of trust”

# add CVA as root of trust
echo "cva@digitopia cert-authority $(cat cva_key.pub)" > allowed_signers
# add registration offices  as an authorized signer
echo "reg_office_01@digitopia $(cat reg_office_01.pub)" >> allowed_signers
echo "reg_office_02@digitopia $(cat reg_office_02.pub)" >> allowed_signers
  1. echo is a command that outputs the string to the terminal
  2. $(cat cva_key.pub) is a command that outputs the contents of cva_key.pub to the terminal
  3. the text of the echo and the output from the $(cat cva_key.pub) are concatenated together and written to a file called allowed_signers

Voter Registration

Congratulations! You have successfully created the infrastructure required to register voters.

Each voter will go through a vetting process. This could be as simple as verify their criminal record, valid id, prior year taxes, etc.

If they pass the vetting process their registration will be completed. The way we can prove their registration is complete is by signing their registration data with the office’s private key and associated namespace.

The action of signing results in an artifact called a signature file.

The signature file will not be given to the voter. Instead, the signature file will be stored on CVA servers and a confirmation will be given to the voter in the form of a hashed version of the signature file.

# Create Alice's voter registration form
echo "Name: Alice Citizen
ID: AC12345
Address: 123 Democracy St
DOB: 1990-01-01" > alice_registration.txt

# Create Shawn's voter registration form
echo "Name: Shawn Citizen
ID: AC45456
Address: 123 Lazy Dev St
DOB: 1998-05-01" > shawn_registration.txt
  1. echo is a command that outputs the string to the terminal
  2. > is the redirection operator. It takes the output of the echo command and redirects it to a file path
# The registration offices sign the voter registration forms for Alice and Shawn
cat alice_registration.txt | ssh-keygen -Y sign -n voter_registration -f reg_office_02 > alice_registration.sig
cat shawn_registration.txt | ssh-keygen -Y sign -n voter_registration -f reg_office_01 > shawn_registration.sig
  1. cat: passes the contents of *.txt to terminal
  2. | is the pipe operator. It takes the output of the cat command and passes it to the ssh-keygen command
  3. -Y sign tells ssh-keygen to sign the data passed to it from the pipe command
  4. -n voter_registration tells ssh-keygen to sign the data using the voter_registration namespace. When it comes time to validate the signature the same namespace baked into certificate needs to match the namespace used durring signing.
  5. -f reg_office_0* tells ssh-keygen to sign the data with the private key in reg_office_01 or reg_office_02

The signature generated is sensative and is going nowhere but to the CVA servers. But how can Alice prove she has completed registration? The registration offce can give Alice a hash of the signature file.

# reset the voter database
echo "" > voter_database.txt

# Provide Alice with a confirmation (not the actual signature)
hashed_signature=$(shasum -a 256 alice_registration.sig | cut -d' ' -f1)
principal="reg_office_02@digitopia"

echo "Voter Registration Confirmation
Name: Alice Citizen
ID: AC12345
Registration Office: Office 02
Registration ID: $hashed_signature" > alice_confirmation.txt

echo "Registration ID: $hashed_signature, Principal: $principal, Timestamp: $(date)" >> voter_database.txt

echo "Alice has been provided with their voter registration confirmation."

#####
# Provide Shawn with a confirmation (not the actual signature)
hashed_signature=$(shasum -a 256 shawn_registration.sig | cut -d' ' -f1)
principal="reg_office_01@digitopia"

echo "Voter Registration Confirmation
Name: Sean Citizen
ID: AC45456
Registration Office: Office 01
Registration ID: $hashed_signature" > shawn_confirmation.txt

echo "Registration ID: $hashed_signature, Principal: $principal, Timestamp: $(date)" >> voter_database.txt

echo "Shawn has been provided with their voter registration confirmation."
  • shasum -a 256 command that gernerates a hash using the sha256 algorithm
  • cut -d' ' -f1 command that extracts the first field from the output of the sha256sum command; fields are delimited by spaces -d' '

Lets dig a little deeper into the hash.

The confirmation for registration is provided to Alice in the form of a hash of the signature file. This is the only secure way to prove she completed the process.

It is secure because there is no way to recover the signature file from the hash. This is a one way cryptographic function.

What purpose is the hash serving?

Proof of Registration: The hash is a unique ID for Alice’s registration that can easily be found in the CVA database.

check-reg.sh
# Function to check if a registration exists
check_registration() {
    reg_id=$1
    if grep -q "$reg_id" voter_database.txt; then
        echo "Registration found for ID: $reg_id"
        return 0
    else
        echo "No registration found for ID: $reg_id"
        return 1
    fi
}

# Usage
registration_id=$(grep "Registration ID:" alice_confirmation.txt | cut -d' ' -f3)
check_registration "$registration_id"

registration_id=$(grep "Registration ID:" shawn_confirmation.txt | cut -d' ' -f3)
check_registration "$registration_id"

  • grep -q: Quietly searches for the pattern. In this case the hashed signature (Alice’s confirmation) registration confirmation
  • cut -d' ' -f3: Extracts the registration ID from Alice’s confirmation
  • The function returns 0 if found, 1 if not found

Verification: Officials can quickly verify a citizens voter registration status.

verify-reg.sh
#!/bin/bash

verify_registration() {
    reg_file=$1
    sig_file=$2
    allowed_signers_file="allowed_signers"
    voter_database="voter_database.txt"

    echo "Verifying registration file: $reg_file"
    echo "Using signature file: $sig_file"
    echo "Using allowed_signers file: $allowed_signers_file"

    # Calculate the hash of the signature file
    sig_hash=$(shasum -a 256 "$sig_file" | cut -d' ' -f1)

    # Look up the principal in the voter database
    db_entry=$(grep "$sig_hash" "$voter_database")
    if [ -z "$db_entry" ]; then
        echo "No matching registration found in the database"
        return 1
    fi

    # Extract the principal from the database entry
    principal=$(echo "$db_entry" | awk -F', ' '{print $2}' | awk -F': ' '{print $2}')

    # Verify the signature using ssh-keygen
    cat "$reg_file" | ssh-keygen -Y verify -n voter_registration -f "$allowed_signers_file" -I "$principal" -s "$sig_file"
    if [ $? -eq 0 ]; then
        echo "Registration verification succeeded"
        return 0
    else
        echo "Registration verification failed"
        return 1
    fi
}

reg_file="alice_registration.txt"
sig_file="alice_registration.sig"

verify_registration "$reg_file" "$sig_file"

reg_file="shawn_registration.txt"
sig_file="shawn_registration.sig"

verify_registration "$reg_file" "$sig_file"
  • Checks if registration and signature files exist
  • Uses ssh-keygen -Y verify to verify the signature
  • Returns the result of the verification (0 for success, non-zero for failure)

Conclusion

In this tutorial, we’ve explored how SSH key signing can be applied to create a secure, efficient (lazy), and transparent voter registration system. By leveraging the power of public-key cryptography, we’ve demonstrated how to:

  1. Brag to our friends about our new bash scripting skills
  2. Establish a trusted Central Voting Authority
  3. Create and certify legitimate voter registration offices
  4. Securely register voters and protect their information
  5. Verify voter registrations completed
  6. Detect and prevent schenanigans with voter registration

Attribution