Skip to content

Commit

Permalink
merge
Browse files Browse the repository at this point in the history
  • Loading branch information
klml committed Sep 15, 2024
2 parents 74b5e1a + 878fad9 commit 16d5c13
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 192 deletions.
12 changes: 1 addition & 11 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,4 @@
Dockerfile
.dockerignore

# Byte-compiled / optimized / DLL files
__pycache__/
*/__pycache__/
*/*/__pycache__/
*/*/*/__pycache__/
*.py[cod]
*/*.py[cod]
*/*/*.py[cod]
*/*/*/*.py[cod]

README.md
README.md
96 changes: 96 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Docker

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

on:
schedule:
- cron: '17 17 * * 5'
push:
branches: [ "main" ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: [ "main" ]

env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}


jobs:
build:

runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@main

# Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0

# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0

# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

# Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
# transparency data even for private images, pass --force to cosign below.
# https://github.com/sigstore/cosign
- name: Sign the published Docker image
if: ${{ github.event_name != 'pull_request' }}
env:
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
TAGS: ${{ steps.meta.outputs.tags }}
DIGEST: ${{ steps.build-and-push.outputs.digest }}
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
96 changes: 1 addition & 95 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,85 +1,5 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py
msgsplit

# Environments
.env
Expand All @@ -88,17 +8,3 @@ env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
11 changes: 8 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
FROM python:3.9.6-alpine
FROM docker.io/library/golang:alpine AS builder-env
WORKDIR /app
COPY ./ /app
RUN CGO_ENABLED=0 go build ./msgsplit.go
RUN rm ./msgsplit.go

FROM scratch
WORKDIR /app
COPY . /app
COPY --from=builder-env /app/ /app/

EXPOSE 8080
CMD [ "python3", "msgsplit.py" ]
CMD [ "./msgsplit" ]
39 changes: 24 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ message split allows you to send messages (passwords etc.) to another person wit

* msgsplit encrypts the message with a [one-time-pad](https://en.wikipedia.org/wiki/One-time_pad) in Alices browser into a _ciphertext_ and _cryptographic-key_
* sends the _ciphertext_ to the server
* the server stores the _ciphertext_ in a key-value storage
* the server stores the _ciphertext_ in a prefixed environment variable.
* returns the _storage-key_ to Alice
* Alices browser creates a hyperlink with the _storage-key_ as URL-query (''?'') and the _cryptographic-key_ as URL-Fragment (''#'')
* Alices sends this link via email or messenger to Bob
Expand All @@ -30,7 +30,7 @@ There are several __security concerns__:
* if ciphers don't get deleted and the offender gets your mail, your message is disclosed
* The browser [generates](https://github.com/klml/msgsplit/blob/master/static/msgsplit.js#L6) the key for the message, if your browsers [Crypto.getRandomValues()](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) is compromised, everything is worthless.
* Only the transmitted message is encrypted. The receiver is not authenticated. The first one who receives the link, has the message.
* brutforce all ciphertexts (`for i in {1..99999999999} ; do curl -s -X POST http://msg.exmple.net:8080/writeread --form "storage_key=$1" ; done ;`): a ciphertext is still useless without the cryptographic-key.
* brutforce all ciphertexts (`for i in {1..99999999999} ; do curl -s -X POST http://msg.exmple.net:8080/writeread --form "key=$1" ; done ;`): a ciphertext is still useless without the cryptographic-key.

## persistent storage

Expand All @@ -48,20 +48,22 @@ Do not use msgsplit for current used passwords.
If the hyperlink gets stolen, this message is disclosed.


## tech stack
## Build

msgsplit uses no dependencies (like [web.py](https://webpy.org/) oder bottle)
But plain [http.server](https://docs.python.org/3/library/http.server.html).
golang>1.20 is required

```
CGO_ENABLED=0 go build ./msgsplit.go
```

run with:
## Run

```
python3 msgsplit.py
./msgsplit.go
```

Alternative:
Use plain image [klml/msgsplit](https://hub.docker.com/r/klml/msgsplit) or with [msgsplit-kubernetes](https://github.com/klml/msgsplit-kubernetes).
Use plain image `ghcr.io/klml/msgsplit:main` or with [msgsplit-kubernetes](https://github.com/klml/msgsplit-kubernetes).


## demo
Expand All @@ -70,18 +72,21 @@ Working demo, you can use it, but there is no safety guarantee!

[msgsplit.klml.de](https://msgsplit.klml.de)

Hostet on [uberspace.de](https://uberspace.de) with [supervisord](https://manual.uberspace.de/daemons-supervisord.html) `msgsplit.py` as [web backend](https://manual.uberspace.de/web-backends.html), static files (index.html, css, js) as default apache and [access log is disabled](https://manual.uberspace.de/web-logs).
Hostet on [uberspace.de](https://uberspace.de) with [supervisord](https://manual.uberspace.de/daemons-supervisord.html) as [web backend](https://manual.uberspace.de/web-backends.html), static files (index.html, css, js) as default apache and [access log is disabled](https://manual.uberspace.de/web-logs).

```
[msgsplit@erinome ~]$ cat ~/etc/services.d/msgsplit.ini
[program:msgsplit]
command=python3 /home/msgsplit/msgsplit/msgsplit.py
command=/home/msgsplit/msgsplit/msgsplit
autostart=yes
autorestart=yes
# `startsecs` is set by Uberspace monitoring team, to prevent a broken service from looping
startsecs=30
[msgsplit@erinome ~]$ supervisorctl status
msgsplit RUNNING pid 29057, uptime 0:25:35
msgsplit RUNNING pid 7138, uptime 0:09:34
[msgsplit@erinome ~]$ uberspace web backend list
/writeread http:8080 => OK, listening: PID 29057, python3 /home/msgsplit/msgsplit/msgsplit.py
/writeread http:8080 => OK, listening: PID 7138, /home/msgsplit/msgsplit/msgsplit
/ apache (default)
[msgsplit@erinome ~]$ uberspace web log access status
Expand All @@ -90,11 +95,15 @@ access log is disabled

## better

There are better ways for real crypto:
There are better ways:

* [openpgp.org](https://www.openpgp.org)
* [ots.private.coffee](https://ots.private.coffee/) uses also a hash parameter and an expiration date.
* [horuspass.com/send](https://horuspass.com/send) uses also a hash parameter, an expiration date, but depends on `workers.cloudflare.com`.
* [pwpush.com](https://pwpush.com/) Passwords automatically expire after a certain number of views and/or time has passed. Track who, what and when. But more complex.
* [privatebin.info](https://privatebin.info) is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. But [requires](https://github.com/PrivateBin/PrivateBin/blob/master/INSTALL.md#minimal-requirements) a database.
* and always [openpgp.org](https://www.openpgp.org)


## Similar

* windmemo.com was a service to send messages you [could read only once](https://www.sebastian-kraus.com/windmemo-nur-der-erste-kann-es-lesen/).
* windmemo.com was a service to send messages you [could read only once](https://www.sebastian-kraus.com/windmemo-nur-der-erste-kann-es-lesen/).
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<title>message split</title>
<link rel="stylesheet" href="./static/msgsplit.css" type="text/css" />
<script type="text/javascript" src="./static/msgsplit.js"></script>
<meta name="version" content="0.1.4"/>
</head>
<body>
<div class="panel">
Expand Down
63 changes: 63 additions & 0 deletions msgsplit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package main

import (
"fmt"
"os"
"net/http"
math_rand "math/rand"
)


func main() {
// this is a downscaled CRUD service
// Create a environment variable (max 256) for cipher
// Read AND Delete cipher
// no Update

http.HandleFunc("/writeread", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.Header().Set("Allow", "POST")
http.Error(w, "Method Not Allowed", 405)
return
}
// prefix environment variable, to prevent reading all system environment variables
storage_key := "msgsplit_"
r.ParseForm()

// stores _ciphertext_ on the server in env
if cipher, found := r.Form["cipher"]; found {

if len(cipher[0]) > 256 {
http.Error(w, "Payload Too Large", 413)
}
// create unique identifier name for environment variable
storage_rand := fmt.Sprintf("%d", math_rand.Int()) // example: 6334824724549167320
storage_key += storage_rand // example: msgsplit_6334824724549167320
os.Setenv(storage_key, cipher[0])

fmt.Fprintf(w, storage_rand) // example: 6334824724549167320
// server reads the _ciphertext_
} else if key, found := r.Form["key"]; found {
storage_key += key[0] // example: msgsplit_6334824724549167320
cipher, cipher_exists := os.LookupEnv(storage_key)
if !cipher_exists {
http.Error(w, "Gone", 410)
}
// delete cipher
os.Unsetenv(storage_key) // example: msgsplit_6334824724549167320
fmt.Fprintf(w, cipher)
} else {
http.Error(w, "Not Found", 404)
}
})

http.Handle("/", http.FileServer(http.Dir("./")))


httpPort, Port_exist := os.LookupEnv("PORT")
if !Port_exist {
httpPort = "8080"
}
fmt.Println("Start msgsplit server at port " + httpPort)
http.ListenAndServe(":" + httpPort, nil)
}
Loading

0 comments on commit 16d5c13

Please sign in to comment.