AIRGAP is a secure file management platform where users can upload, store, and retrieve encrypted files across repositories. With Role-Based Access Control (RBAC), AIRGAP ensures users can only perform actions their role allows. Every file remains encrypted both in storage and in transit, and is digitally signed to guarantee its integrity and authenticity. All actions are logged to ensure auditability and compliance.
-
Clone the repository and navigate to the project directory:
git clone https://github.com/adithya-menon-r/airgap.backend.git cd airgap.backend -
Create a virtual environment and activate it:
python -m venv .venv source .venv/bin/activate -
Install the dependencies:
pip install -r requirements.txt
-
Generate the
.envfile and update the environment variables:cp .env.example .env
-
Generate the RSA key pair:
mkdir -p keys openssl genrsa -out keys/private_key.pem 2048 openssl rsa -in keys/private_key.pem -pubout -out keys/public_key.pem
-
Run database migrations:
alembic upgrade head
-
Start the development server:
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
The API will be available at http://localhost:8000
AIRGAP implements a two-factor authentication flow following the NIST SP 800-63-2 E-Auth guidelines:
- Password Authentication: Users authenticate with a password, which is validated using bcrypt with a SHA-256 pre-hash. A successful validation only allows the user to proceed to the second factor.
- TOTP (Time-Based One-Time Password): Users verify a time-based OTP from an authenticator app (Google Authenticator / Authy) as the second factor. The
JWT access tokenis issued only after both factors are verified successfully.
AIRGAP implements a Role-Based Access Control (RBAC) model using an Access Control Matrix. It is enforced at the route level through the PermissionChecker class and FastAPI's Depends injection.
If a user has no role, or their role lacks the required permission, a 403 Forbidden is returned and the ACCESS_DENIED event is logged with the user's IP address.
| Role | Description |
|---|---|
admin |
Full system access |
senior_dev |
Can manage repositories and read/write files |
junior_dev |
Can read and write files within assigned repositories |
auditor |
Read only access to audit logs; cannot access files or repos |
| Resource | Description |
|---|---|
USER_MANAGEMENT |
Create users, assign roles |
REPOSITORY_MANAGEMENT |
Create, delete, assign users to repos |
REPOSITORY_READ |
List and view repositories |
FILE_WRITE |
Upload files |
FILE_READ |
Access and decrypt files |
AUDIT_LOGS |
View audit logs, run integrity scans |
| Role | User Management | Repository Management | Repository Read | File Write | File Read | Audit Logs |
|---|---|---|---|---|---|---|
admin |
Yes | Yes | Yes | Yes | Yes | Yes |
senior_dev |
No | Yes | Yes | Yes | Yes | No |
junior_dev |
No | No | Yes | Yes | Yes | No |
auditor |
No | No | No | No | No | Yes |
AIRGAP uses a Digital Envelope mechanism. It uses both symmetric and asymmetric encryption (AES-GCM & RSA) to ensure that the file content is always encrypted in transit and at rest.
For the asymmetric encryption, the server uses the RSA-2048 key pair that is generated and stored in the keys/ directory. The public key is exposed to the client via the GET /auth/server-public-key endpoint.
- The client first generates a random AES-256 key and encrypts the file content with it.
- It then fetches the server's public key and uses it to wrap the AES key.
- The encrypted AES key envelope is sent along with the encrypted file in the upload request.
- On the server, the AES key is recovered by decrypting the envelope using the server's private key.
- The recovered AES key is then re-encrypted using a server side master key via AES-256-GCM and then stored in the database.
- The encrypted file content is written to disk as received.
To access a file, the process is reversed:
- The server unwraps the AES key from the database using the master key.
- It then re-encrypts it with the client's RSA public key (received with the file access request). This ensures that only the requesting client can decrypt it.
- It then retrieves the encrypted file from disk and sends it along with the AES key envelope to the client.
- The client unwraps the AES key using its RSA private key and then uses it to decrypt the file content.
Passwords are saved using a double-hashing strategy:
- SHA-256 pre-hash: The raw password from the client is first hashed with SHA-256. This normalises the input length for bcrypt's 72-byte limit.
- bcrypt: brcypt generates a cryptographically random salt and uses it to hash the SHA-256 hex digest. The resulting salted hash is then stored in the database.
When uploading a file, the client computes a SHA-256 hash of the plaintext file and sends it in the upload payload. This hash is stored in the database and used for verification during the integrity scans.
Each uploaded file is signed with a per-user HMAC-SHA256 signature. This binds the file to its uploader and is used to detect tampering.
Instead of using a single shared signing key, a unique signing key is derived for each user using HKDF (SHA-256), keyed off the server's SERVER_SIGNING_SECRET with the user_id as the derivation context. This ensures each user's signature is unique and cryptographically isolated.
The Integrity Scan is implemented to check for tampering and inconsistencies in stored data. When run, each file is decrypted, their SHA-256 hashes are re-computed & compared with the stored hash, and their HMAC signatures are validated.
Any file that is missing, has a hash mismatch, fails decryption, or has an invalid signature is flagged as tampered and logged as an INTEGRITY_FAIL.
-
tamper.pyis used to simulate some kind of tampering with the file content when stored on disk:python tamper.py uploads/<repo_id>/<filename>
The script unwraps the AES Key using the server's master key and uses it to decrypt the file. It then appends extra bytes to the plaintext, and re-encrypts it and saves it back to disk.
The file on disk looks like a valid encrypted file but during the integrity scan, its hash will no longer match the hash computed during upload and it will be flagged.
-
restore.pyundoes the tampering process:python restore.py uploads/<repo_id>/<filename>
It decryps the file and removes the extra bytes that were appended to the plaintext and re-encrypts it. This restores the file to its original content.
This project is licensed under the MIT LICENSE.