Build a GPG File Portal Through SSH

Deliver encrypted Ubuntu files to your Mac through an SSH tunnel and GPG.

Introduction

30 Second Summary

A protected connection shields a file while it travels. The same file may still sit fully readable on the machine that stores it.

In this project, you will build a private file-delivery lab using an SSH tunnel plus GnuPG public-key encryption. You will prove how transport encryption differs from encryption at rest.

What You'll Build

You retrieve ciphertext from a loopback-only Ubuntu endpoint through the tunnel before restoring the original file only on your Mac.

By the end of this project, you'll have:

  • A demonstration where a direct request fails before the same file loads through a local SSH forward.
  • Visible proof that transport protection secures the file in motion while a readable server copy remains exposed.
  • A ciphertext-only workflow that retrieves an encrypted file from Ubuntu before local decryption restores the original two lines on your Mac.
  • Secret Mission: Harden the tunnel with fail-fast forwarding plus encrypted-channel keepalives.

What do I need before starting?

You'll need a Mac running macOS Sonoma 14 or newer plus an Ubuntu 24.04 VM. Keep the Ubuntu username plus password ready.

Your Mac must be able to reach the VM over SSH on port 22.

Before We Start

Before any terminal work, commit to building a local lab where SSH tunneling protects the network path. GnuPG protects the file stored on Ubuntu.

Prepare the Mac and Ubuntu Endpoint

Your local security lab depends on both endpoints being ready. SSH tunneling cannot work until the Mac can authenticate to Ubuntu.

The trusted Mac needs verified command-line tools. Those tools create the protected transport path plus the encrypted payload.

Ubuntu needs an active SSH endpoint. Python later provides the loopback-only file service.

In this step, get ready to:
  • Verify curl, OpenSSH, and GnuPG on the Mac.
  • Install OpenSSH Server plus Python on Ubuntu.
  • Verify the SSH host key before authenticating from the Mac.
Verify the Mac tools

The Mac acts as your trusted client throughout this lab. macOS Terminal gives you one place to check every required command.

  • Press Cmd+Space to open Spotlight Search.
  • Type Terminal into the search field.
  • Press Enter to open macOS Terminal.
  • Check the Mac tools by running these commands:
curl --version
ssh -V
gpg --version

What do these checks prove?

  • The curl check confirms that the Mac can make the HTTP requests used later in the lab.
  • The ssh check confirms that the Mac has an OpenSSH client for authentication plus local forwarding.
  • The gpg check confirms that the Mac can encrypt files before upload.

Each working command prints version information. Choose the tab that matches the results in your Terminal.

✔️ All three commands print versions

Your Mac already has every required command. That trusted client is ready for the rest of the lab.

ⓧ ssh or gpg is missing

Use this path when Homebrew is already available on your Mac. Homebrew installs the missing OpenSSH plus GnuPG packages.

The installation can take a few minutes while Homebrew downloads the packages. Terminal output during that wait shows that the process is still moving.

  • Install OpenSSH plus GnuPG by running these commands:
brew install openssh
brew install gnupg

What does Homebrew install?

Homebrew installs the openssh formula for the SSH client. It installs the gnupg formula for file encryption.

  • Repeat the tool checks by running these commands:
curl --version
ssh -V
gpg --version

What should you see now?

Each command should print version information. This confirms that Terminal can find all three tools.

You have repaired the missing-tool gap. The Mac can now test endpoints plus create the encrypted workflow.

Mac tool checks still failing?

Close the current Terminal window after Homebrew finishes. Open a fresh Terminal window so the shell reloads its command paths.

If a command is still unavailable, confirm that the Homebrew installation completed without an error.

Help me troubleshoot the missing Mac command.

ⓧ Homebrew is missing too

This path installs Homebrew before using it to add OpenSSH plus GnuPG. The installer prints a PATH instruction tailored to your Mac.

The first installation can take several minutes. Continuous Terminal output means the installer is still working.

  • Install Homebrew by running the official installer command:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

What does the installer do?

The command downloads the Homebrew installer from its official repository. The installer then prepares Homebrew for your Mac.

  • Apply the PATH instruction printed by the Homebrew installer.
  • Install OpenSSH plus GnuPG by running these commands:
brew install openssh
brew install gnupg

What gets added to the Mac?

The openssh formula provides the SSH client used for login plus tunneling. The gnupg formula provides the encryption command used later.

  • Repeat the tool checks by running these commands:
curl --version
ssh -V
gpg --version

What should you see now?

All three commands should print version information. This confirms that the new Homebrew packages are available to Terminal.

The Mac now has its complete security toolkit. You can move to the Ubuntu endpoint with a verified client.

Homebrew command still missing?

Return to the final lines printed by the installer. Run the PATH instruction exactly as Homebrew displayed it.

Open a fresh Terminal window after applying that instruction. This gives the new shell the updated command path.

Help me fix the Homebrew command path.

Prepare Ubuntu's SSH endpoint

Ubuntu must accept SSH connections before the Mac can create a tunnel. APT installs the server package plus the Python runtime used by the private file endpoint.

Package installation can take a few minutes while Ubuntu refreshes its package index. Activity in the console means the process is still running.

  • Open your Ubuntu VM console from your virtualization app.
  • Enter your Ubuntu password if sudo requests it.
  • Confirm each installation prompt when APT asks whether to continue.
  • Prepare the Ubuntu endpoint by running these commands:
sudo apt update
sudo apt install openssh-server
sudo apt install python3
sudo systemctl restart ssh.service

What do these commands prepare?

  • The first command refreshes Ubuntu's local package index.
  • The next command installs the server component that accepts SSH connections.
  • The third command installs the Python runtime used by the temporary file server.
  • The final command restarts ssh.service so the endpoint is running.

You have the server side ready. A successful return to the prompt confirms that ssh.service restarted without an error.

Ubuntu package setup failed?

Check that the Ubuntu VM has network access if the package index cannot refresh. Confirm that your Ubuntu account can use sudo if installation is denied.

Help me troubleshoot the Ubuntu package setup.

  • Verify Python inside the Ubuntu VM by running this command:
python3 --version

What does this check prove?

The output confirms that Ubuntu can find its Python runtime. That runtime powers the loopback-only file server in the next step.

You should see a Python version line in the Ubuntu console. The endpoint now has both services required for the lab.

Python version not showing?

Check the package installation output for an interrupted or declined prompt. Repeat the documented Python installation command if the package did not finish.

Help me fix the Ubuntu Python check.

Verify the VM identity and log in

The Mac needs the VM's non-loopback IPv4 address before it can connect. The loopback address belongs only to the VM itself.

  • Display the Ubuntu VM's IPv4 addresses by running this command in the VM console:
ip -4 addr show

Which address do you need?

The output lists IPv4 addresses attached to the VM's network interfaces. Use the non-loopback address that your Mac can reach.

  • Find the IPv4 address that is not attached to the loopback interface.
  • Record the address here as <vm-ip>.
  • Record your Ubuntu username here as <ubuntu-user>.

A first SSH connection asks the Mac to trust the endpoint's host key. Checking the fingerprint at the VM console proves that the prompt belongs to this VM.

  • Display the VM's ED25519 host-key fingerprint by running this command in the Ubuntu console:
sudo ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key

Why check the fingerprint locally?

The VM console is your trusted view of the server's identity. Matching its fingerprint with the Mac's first-login prompt prevents you from accepting an unexpected endpoint.

  • Record the printed fingerprint here as the VM ED25519 fingerprint.
  • Switch back to the macOS Terminal window from your tool checks.
  • Prepare to start the SSH login with the command below.
  • Predict whether the Mac should show the same fingerprint as the VM console.
ssh [[UBUNTU_USER="<ubuntu-user>"]]@[[VM_IP="<vm-ip>"]]

What does this connection prove?

The SSH client connects to the VM address you recorded. The first connection presents the endpoint identity before password authentication begins.

  • Compare the fingerprint in the Mac prompt with the VM ED25519 fingerprint.
  • Accept the host-key prompt only when both fingerprints match.
  • Enter your Ubuntu password at the authentication prompt.

You should reach an Ubuntu shell prompt after authentication. This confirms that the Mac trusts the verified VM endpoint.

Fingerprint mismatch or login failure?

Do not accept a mismatched fingerprint. Return to the Ubuntu VM console to confirm the address plus the ED25519 fingerprint.

If the fingerprint matches but authentication fails, confirm the Ubuntu username. Re-enter the VM account password carefully.

Help me troubleshoot the trusted SSH login.

  • Predict whether the final Python check runs on the Mac or inside Ubuntu.
  • Verify Python through the authenticated SSH session by running this command:
python3 --version

What does the final check confirm?

This version output comes from Ubuntu because the command runs inside the authenticated session. It proves that remote login plus the server-side Python installation both work.

You should see a Python version line beneath the Ubuntu shell prompt. Your earlier Mac check also showed the GnuPG version.

Remote Python check failed?

Confirm that your prompt belongs to the Ubuntu session. If Python is unavailable there, return to the VM console and check that the APT installation completed.

Help me diagnose the remote Python check.

The trust boundary is now in place. The Mac accepts the verified VM endpoint.

Ubuntu is running its SSH service plus Python. The Mac has the tools needed for tunneling plus file encryption.

✔️ My endpoint setup matches

Your Mac plus Ubuntu VM are ready. Keep the authenticated SSH session available for the next step.

ⓧ I'd like to double check the full code

Compare your setup with these required end states:

  • Mac command-line tools: curl, ssh/OpenSSH, and gpg/GnuPG are installed and version-verified. Homebrew is available if it was needed for installation.
  • SSH endpoint trust: The Mac has verified and accepted the Ubuntu VM ED25519 host key for <ubuntu-user>@<vm-ip>.
  • Ubuntu Python: Python 3 is installed and version-verified on the Ubuntu VM.
  • Ubuntu SSH service: OpenSSH Server is installed and ssh.service is running on the Ubuntu VM.

Both endpoints are ready. Next, you will publish a plaintext file on Ubuntu's loopback interface and experience the direct-access failure that the tunnel solves.

Publish a Loopback-Only Plaintext File

Your verified SSH connection gives your Mac a trusted route into the Ubuntu VM. The lab now needs a file endpoint that the VM can reach without exposing it to the VM network.

A loopback binding keeps a temporary HTTP endpoint on Python reachable only inside Ubuntu. The readable server copy exposes the file-protection gap that GnuPG addresses later.

In this step, get ready to:
  • Prepare a two-line plaintext file inside the Mac lab directories.
  • Copy the plaintext file to Ubuntu over the trusted SSH connection.
  • Prove that Ubuntu's loopback-only endpoint rejects direct Mac access.
Create the Mac lab file

A recognizable payload makes every later transfer easy to verify. The separate source and downloads directories keep the original file apart from files retrieved through the tunnel.

  • Build the Mac-side lab structure by running these commands:
mkdir -p ~/tunnel-lab/source ~/tunnel-lab/downloads
printf 'Tunnel lab plaintext\nSSH protects the path. GnuPG protects the file.\n' > ~/tunnel-lab/source/secret.txt
cat ~/tunnel-lab/source/secret.txt

What do these commands do?

  • The mkdir command uses -p to create the two directories plus any missing parent directory.
  • The printf command writes two recognizable lines to ~/tunnel-lab/source/secret.txt.
  • The cat command prints the saved file so you can verify its contents immediately.

You'll see Tunnel lab plaintext on the first line. The second line states how SSH protection differs from GnuPG protection.

Don't see both lines?

  • Check that the file path ends with ~/tunnel-lab/source/secret.txt.
  • Run the commands again if the terminal reports that a directory does not exist.

Help me fix the Mac lab file creation commands.

Copy the plaintext to Ubuntu

The scp command transfers a file through the SSH connection you already trusted. The resulting Ubuntu file remains readable plaintext after the transfer completes.

Authentication may ask for the Ubuntu password you used earlier. The commands keep that credential out of your terminal history.

  • Enter your Ubuntu password if either command prompts for authentication.
  • Publish the plaintext copy on Ubuntu by running these commands:
ssh <ubuntu-user>@<vm-ip> 'mkdir -p ~/tunnel-share'
scp ~/tunnel-lab/source/secret.txt <ubuntu-user>@<vm-ip>:tunnel-share/

How does the transfer work?

  • The first command creates ~/tunnel-share inside the Ubuntu user's home directory.
  • The second command sends secret.txt to that directory through the authenticated SSH connection.
  • The transfer protects the network path while the copied file stays readable on Ubuntu.

You'll see the file transfer complete without an error. Ubuntu now has its own copy at ~/tunnel-share/secret.txt.

Did the transfer fail?

  • Check that <ubuntu-user> matches the username you used for the successful SSH login.
  • Check that <vm-ip> matches the non-loopback IPv4 address you recorded earlier.
  • Confirm that the Mac source path is ~/tunnel-lab/source/secret.txt.

Help me troubleshoot the plaintext SCP transfer.

Test a loopback-only server

The Python http.server module provides a temporary file endpoint with basic security checks. Binding it to 127.0.0.1 limits the listener to Ubuntu itself.

  • Return to the Ubuntu VM console from earlier.
  • Confirm the transferred plaintext by running this command:
cat ~/tunnel-share/secret.txt

What does this check prove?

The cat command reads the Ubuntu copy directly. Matching text proves the transfer preserved both lines.

You'll see the same two lines that appeared on your Mac. Ubuntu can read the plaintext without using a GnuPG key.

Is the Ubuntu file missing?

  • Check that you are signed in as the same Ubuntu user named in the transfer command.
  • Repeat the SCP transfer if ~/tunnel-share/secret.txt does not exist.

Help me find the transferred file on Ubuntu.

The HTTP server occupies this Ubuntu terminal until you stop it with Ctrl+C. Keeping it in the foreground makes each incoming request visible.

  • Start the loopback-only endpoint from the shared directory by running these commands:
cd ~/tunnel-share
python3 -m http.server --bind 127.0.0.1 8000

How is the endpoint restricted?

  • The cd command makes ~/tunnel-share the directory served by Python.
  • The --bind option limits the listener to Ubuntu's 127.0.0.1 loopback address.
  • Port 8000 becomes the private HTTP entry point for files in the shared directory.

You'll see a startup message that identifies port 8000. The terminal remains occupied while the server runs.

  • Leave the Ubuntu terminal running with the server in the foreground.

Did the server stop immediately?

  • Check that the terminal is inside ~/tunnel-share.
  • Confirm that no other process is already using port 8000.
  • Restart the command if you pressed Ctrl+C accidentally.

Help me troubleshoot the loopback-only Python HTTP server.

Before you test, take a moment to predict whether the Mac can reach a service bound to Ubuntu's loopback address.

  • Switch back to the macOS Terminal from earlier.
  • Test direct access to the Ubuntu endpoint by running this command:
curl http://<vm-ip>:8000/secret.txt

Why does direct access fail?

The request fails because 127.0.0.1 names Ubuntu itself from the server's point of view. The VM's network address has no service listening on port 8000.

You'll see curl report that it cannot connect to the VM on port 8000. This is the planned result.

That failed request is the proof you needed. Ubuntu is serving the plaintext file without exposing the endpoint to the VM network.

✔️ Awesome, I've got everything!

Your plaintext file is now available through an Ubuntu loopback endpoint. Direct access from the Mac is blocked as designed.

ⓧ I'd like to double check the full setup

A complete setup has the following state.

  • The Mac has ~/tunnel-lab/source plus ~/tunnel-lab/downloads.
  • The first line of ~/tunnel-lab/source/secret.txt reads Tunnel lab plaintext. The second line reads SSH protects the path. GnuPG protects the file..
  • Ubuntu has the matching plaintext copy at ~/tunnel-share/secret.txt.
  • The Python HTTP server is running from ~/tunnel-share on Ubuntu's 127.0.0.1:8000 endpoint.
  • The direct Mac request to http://<vm-ip>:8000/secret.txt fails because the service is absent from the VM network interface.

Your private endpoint is ready. Next, you'll give the Mac a secure path to it through an SSH local-forwarding tunnel.

Open the SSH Local Forward

Your Ubuntu endpoint now accepts requests only through its loopback interface. The failed direct request from your Mac proved that the private boundary is working.

SSH local forwarding bridges that boundary without exposing the HTTP server on the VM network. A Mac-side listener carries each request through the encrypted SSH channel to Ubuntu's private listener.

In this step, get ready to:
  • Start an encrypted local forward from the Mac to Ubuntu.
  • Download the plaintext file through the tunnel.
  • Inspect the original lines and identify the remaining storage risk.
Start the local forward

A local forward maps one listening address on your Mac to a destination viewed from Ubuntu. Loopback keeps each listener local to its own machine.

The command occupies its Terminal window for the lifetime of the tunnel. A quiet window after authentication means the forward is waiting for traffic.

  • In macOS Terminal, open a new window for the SSH process.
  • Start the local forward by running this command:
ssh -N -L 127.0.0.1:8080:127.0.0.1:8000 <ubuntu-user>@<vm-ip>

How does the local forward work?

  • The -N option keeps the SSH session focused on forwarding without starting a remote shell.
  • The -L option creates the local forwarding rule.
  • The 127.0.0.1:8080 endpoint is the listener on your Mac.
  • The 127.0.0.1:8000 endpoint is the destination reached from Ubuntu.
  • SSH carries traffic between the two endpoints through its encrypted channel.
  • Enter your existing Ubuntu password if SSH requests it.
  • Leave this Terminal window open.

After authentication, the Terminal window stays occupied without displaying a new shell prompt. That quiet session is your active tunnel.

Tunnel closes immediately?

  • Confirm that the VM is still reachable over SSH.
  • Stop any old tunnel that still owns Mac port 8080.

Ask for help if the tunnel still exits: Help me troubleshoot an SSH local forward that closes immediately.

Download the file through the tunnel

Mac port 8080 is now the local entrance to Ubuntu port 8000. Your request can use Mac loopback even though the VM network still blocks direct access to the HTTP service.

  • Return to the Mac Terminal window that you used for the failed direct request.

Before you send the request, do you think Mac loopback port 8080 can now reach the service that rejected direct access?

  • Download the plaintext file through the tunnel by running this command:
curl -o ~/tunnel-lab/downloads/plaintext-via-tunnel.txt http://127.0.0.1:8080/secret.txt

What does this command do?

  • The curl command sends an HTTP request to the listener on your Mac.
  • The -o option saves the response as ~/tunnel-lab/downloads/plaintext-via-tunnel.txt.
  • The local listener passes the request through SSH to Ubuntu's loopback-only HTTP server.

The command completes and creates ~/tunnel-lab/downloads/plaintext-via-tunnel.txt on your Mac. The direct URL failed earlier, but the loopback URL now works through the tunnel.

Download cannot connect?

  • Confirm that the SSH tunnel Terminal window is still open.
  • Confirm that the Ubuntu HTTP server Terminal from earlier is still serving files from ~/tunnel-share.

Ask for help with the request: Help me debug a curl request through my SSH local forward.

Inspect the downloaded plaintext

The saved file gives you a content-level check of what crossed the tunnel. Reading it also exposes the security limitation that remains on Ubuntu.

Before you inspect the download, do you think it contains ciphertext or the original readable lines?

  • Inspect the downloaded file by running this command:
cat ~/tunnel-lab/downloads/plaintext-via-tunnel.txt

What does this result prove?

  • The cat command prints the downloaded file in your Terminal.
  • You should see the same two recognizable lines from the original lab file.
  • The matching content proves that the response traveled through the SSH tunnel.

You have crossed the loopback boundary through SSH. The original two lines now appear in your Mac downloads folder.

Transport encryption protects the network path. Ubuntu still stores a readable copy of secret.txt.

Your local forward now solves the private-service access problem. Next, you will use GnuPG to protect the file stored on Ubuntu.

Encrypt and Restore the File

Your local SSH forward now retrieves the file over an encrypted channel. Ubuntu still stores the delivered file as readable plaintext.

GnuPG uses public-key encryption to protect the payload itself. The private key stays in the Mac keyring.

In this step, get ready to:
  • Create a passphrase-protected GnuPG key on the Mac.
  • Replace Ubuntu's readable payload with recipient-encrypted ciphertext.
  • Restore the original file locally from ciphertext downloaded through the tunnel.
Create or reuse the Mac encryption key

A GnuPG recipient identity connects a public key to the encrypted file. Its matching secret key is what lets your Mac restore the plaintext.

  • Switch back to a separate Mac terminal from earlier.
  • Check for the lab identity by running this command:
gpg --list-secret-keys "Tunnel Learner <tunnel@example.com>"

What does this check show?

The command searches your Mac keyring for a secret key attached to the exact lab identity. A matching entry means the private key is already available locally.

✔️ I see the lab identity

The secret-key entry belongs to the lab identity. Reuse this key so you do not create a duplicate.

ⓧ I do not see the lab identity

GnuPG opens a secure prompt during key creation. The passphrase protects your private key inside the Mac keyring.

  • Choose a private passphrase that you do not reuse elsewhere.
  • Generate the lab key by running this command:
gpg --quick-generate-key "Tunnel Learner <tunnel@example.com>" default default 1y

What does this command create?

  • The lab identity associates the key with the recipient used later during encryption.
  • The default algorithm settings create a primary key plus a subkey.
  • The expiration value limits the key lifetime to one year.

GnuPG returns control to the terminal after it creates the key. The private key remains inside the Mac keyring.

Key generation did not finish?

Check for a passphrase prompt behind your terminal window. Complete that prompt so GnuPG can finish creating the key.

If the command still does not finish, help me troubleshoot GnuPG key generation on my Mac.

  • Confirm the secret key is available by running the check again:
gpg --list-secret-keys "Tunnel Learner <tunnel@example.com>"

What confirms the key is ready?

The output lists a secret-key entry for the lab identity. This confirms your Mac holds the decryption key needed at the end of the workflow.

That completes the key setup. Your Mac can now encrypt for the lab identity without sending its private key to Ubuntu.

Still missing the secret key?

Make sure both commands use the identity exactly as shown. A different name or email address searches for a different key.

If the identity still does not appear, help me find the missing GnuPG secret key.

Replace the server plaintext with ciphertext

Encryption turns the readable source into ciphertext for the lab recipient. Ubuntu can store and serve that ciphertext without receiving the Mac's private key.

  • Encrypt the Mac source file by running this command:
gpg --output ~/tunnel-lab/source/secret.txt.gpg --encrypt --recipient "Tunnel Learner <tunnel@example.com>" ~/tunnel-lab/source/secret.txt

What does this encryption command do?

  • The recipient selects the public key attached to the lab identity.
  • The source path points to the original two-line plaintext file.
  • The output path stores the encrypted result as `secret.txt.gpg`.

The command creates `~/tunnel-lab/source/secret.txt.gpg` on the Mac. Your original source file remains available locally.

Ciphertext file not created?

Check that the recipient identity matches the secret-key listing. Check that `~/tunnel-lab/source/secret.txt` still exists on the Mac.

If encryption still fails, help me troubleshoot this GnuPG encryption command.

The encrypted output is now safe to place on the Ubuntu endpoint. The upload uses the trusted SSH connection you verified earlier.

  • Replace `<ubuntu-user>` with the Ubuntu username from earlier.
  • Replace `<vm-ip>` with the VM address from earlier.
  • Upload the ciphertext to `~/tunnel-share` by running this command:
scp ~/tunnel-lab/source/secret.txt.gpg <ubuntu-user>@<vm-ip>:tunnel-share/

How is the ciphertext uploaded?

The command copies `secret.txt.gpg` through an authenticated SSH connection. The destination places it beside the current server plaintext inside `~/tunnel-share`.

You should see transfer progress for `secret.txt.gpg`. A completed transfer means Ubuntu now has the encrypted payload.

Ciphertext upload failed?

Confirm that you substituted the same Ubuntu username and VM address used for your successful SSH login. Confirm that the local ciphertext path ends with `secret.txt.gpg`.

If the transfer still fails, help me troubleshoot this ciphertext upload.

Removing the server plaintext is the security boundary this step establishes. Your original source file remains safely on the Mac.

  • Remove the readable Ubuntu copy by running this command:
ssh <ubuntu-user>@<vm-ip> 'rm ~/tunnel-share/secret.txt'

What does this removal change?

SSH runs the removal command inside Ubuntu's `~/tunnel-share` directory. The HTTP server can now deliver only the encrypted `secret.txt.gpg` payload.

A successful command returns you to the Mac prompt without additional output. Ubuntu no longer holds the readable delivered payload.

Server plaintext was not removed?

Confirm that the command targets `~/tunnel-share/secret.txt` on Ubuntu. Keep the Mac source at `~/tunnel-lab/source/secret.txt` untouched.

If the remote removal does not complete, help me troubleshoot the SSH removal command.

Download and restore the protected file

The existing tunnel still exposes Ubuntu's loopback endpoint at the Mac address `127.0.0.1:8080`. This time the endpoint delivers ciphertext.

  • Download the encrypted payload through the existing tunnel by running this command:
curl -o ~/tunnel-lab/downloads/secret.txt.gpg http://127.0.0.1:8080/secret.txt.gpg

What does this download prove?

The local URL enters the SSH forward on the Mac. The response is saved as `~/tunnel-lab/downloads/secret.txt.gpg` without exposing Ubuntu's HTTP port to the VM network.

Curl prints transfer statistics when the download completes. The downloaded file remains encrypted at this point.

Ciphertext download failed?

Confirm that the original SSH tunnel terminal is still running. Confirm that the Ubuntu Python HTTP server is still running from `~/tunnel-share`.

If the tunnel download still fails, help me trace the local forward and HTTP endpoint.

Decryption uses the secret key held in the Mac keyring. GnuPG may request the private-key passphrase through a secure prompt.

  • Restore the ciphertext as `secret-restored.txt` by running this command:
gpg --output ~/tunnel-lab/downloads/secret-restored.txt --decrypt ~/tunnel-lab/downloads/secret.txt.gpg

How does the file get restored?

GnuPG finds the matching secret key in the Mac keyring. It writes the decrypted result to `~/tunnel-lab/downloads/secret-restored.txt`.

Before you display the restored file, what do you expect the Mac-held private key to recover from the ciphertext?

  • Display the restored plaintext by running this command:
cat ~/tunnel-lab/downloads/secret-restored.txt

What does this result prove?

You should see the same two recognizable lines from the original lab file. Matching content proves that the SSH tunnel delivered the ciphertext intact.

Local decryption proves that the Mac-held private key can restore the protected payload. Ubuntu stores no readable copy of the delivered file.

Restored text does not match?

Confirm that decryption used `~/tunnel-lab/downloads/secret.txt.gpg`. Confirm that `cat` reads `~/tunnel-lab/downloads/secret-restored.txt`.

If the restored file is missing or incorrect, help me trace the GnuPG decryption workflow.

You have completed the protected delivery workflow. Ubuntu serves only ciphertext while your Mac retains the power to restore the original file.

Secret mission

Secret Mission: Make the Tunnel Fail Fast and Stay Alive

Harden your tunnel so a forwarding failure stops the SSH process immediately. Add encrypted-channel keepalives without changing the server or the encrypted payload.

Clean Up Your Resources

Clean Up Your Resources

Your security lab runs entirely on your Mac and Ubuntu VM. There are no ongoing cloud costs, so decide whether to keep the lab active, pause its temporary processes, or remove its local files.

Resources you used:

  • Hardened SSH local-forwarding tunnel process on the Mac.
  • Python HTTP server process running from ~/tunnel-share on Ubuntu.
  • Mac lab directory ~/tunnel-lab containing the source file, ciphertext, and restored downloads.
  • Ubuntu share directory ~/tunnel-share containing only secret.txt.gpg.

Keep everything running

No action needed. Choose this if you want to repeat the encrypted transfer or test the hardened tunnel again.

  • Keep the hardened tunnel terminal running only while you are using the lab.
  • Keep the Ubuntu HTTP server terminal running only while you are using the lab.
  • Preserve ~/tunnel-lab on the Mac.
  • Preserve ~/tunnel-share on Ubuntu.
  • Retain the passphrase-protected GnuPG key and installed packages for future security labs.

Pause - I'll come back to this later

Stop both temporary processes to free their listening ports. Your files and GnuPG key stay in place.

  • Switch back to the Mac terminal running the hardened SSH tunnel.
  • Press Ctrl+C to stop the tunnel.
  • Switch back to the Ubuntu terminal running the Python HTTP server.
  • Press Ctrl+C to stop the server.

Your lab is safely paused. Both listening ports are free while the encrypted workflow remains saved.

Delete - I don't want to use this again

Remove the lab directories after stopping both temporary processes. The security tools and Ubuntu services remain installed for future projects.

Deletion Is Permanent

These commands permanently remove the lab files on both machines. Retain the passphrase-protected GnuPG key unless you are certain that no other ciphertext depends on it.

  • Switch back to the Mac terminal running the hardened SSH tunnel.
  • Press Ctrl+C to stop the tunnel.
  • Switch back to the Ubuntu terminal running the Python HTTP server.
  • Press Ctrl+C to stop the server.
  • Delete the Mac lab directory by running these commands in macOS Terminal:
rm -rf ~/tunnel-lab
ls ~

What Should You See?

The second command lists your Mac home directory after removal. You should no longer see tunnel-lab.

  • Delete the Ubuntu share directory by running these commands in the Ubuntu terminal:
rm -rf ~/tunnel-share
ls ~

What Should Ubuntu Show?

The second command lists your Ubuntu home directory after removal. You should no longer see tunnel-share.

That's a clean finish. Your lab files no longer remain on either machine.

Nice Work!

Nice Work!

You did it! Your private file-delivery workflow combines SSH transport protection with GnuPG file protection.

You've learned how to:

  • Publish a loopback-only endpoint with HTTP that visibly rejects direct requests from the Mac.
  • Route file downloads through an SSH local-forwarding tunnel that keeps both listening endpoints on loopback.
  • Apply public-key encryption before upload so Ubuntu stores only ciphertext while the Mac keeps the private key.
  • Harden the tunnel with fail-fast forwarding plus encrypted-channel keepalives in the optional Secret Mission.

Ready to quiz yourself?