Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
119 commits
Select commit Hold shift + click to select a range
fecd76f
Rename blinded -> blinded15
jagerman Jul 12, 2023
749c7e9
Add 25xxx blinding primitives
jagerman Jul 12, 2023
b50c094
Squash: more 15 renaming
jagerman Jul 13, 2023
c7f8e87
Squash: more 25 primitives
jagerman Jul 13, 2023
9cfb5c5
Fix importlib deprecation warning
jagerman Jul 13, 2023
40c5e8a
Add blinding script
jagerman Aug 16, 2023
c8f4993
WIP
jagerman Dec 6, 2023
7eecddd
fix some missing/deprecated function usage
tewinget Dec 7, 2023
5d5ca56
update db schema(s) for blinding v2 (25-blinding)
tewinget Dec 8, 2023
4d1953f
Migrate db to 25-blinding
tewinget Dec 13, 2023
980d353
python-black formatting
tewinget Dec 13, 2023
4e55acd
[WIP] 25-blinded usage changes
tewinget Dec 19, 2023
ff9e507
WIP, fixing tests
tewinget Dec 21, 2023
8aec739
squashme, legacy auth tests fixed
tewinget Dec 21, 2023
0b9c9c2
25-blind fixes, tests pass, migration working
tewinget Jan 23, 2024
e045278
format
tewinget Jan 29, 2024
f561793
give correct sender key for messages
tewinget Jan 29, 2024
ae489c3
bot api huge commit, needs split with proper commit msgs
tewinget Jun 18, 2024
f2815b2
WIP: improve captcha
Jul 10, 2024
40ad4d9
WIP: challenge bot
Jul 12, 2024
f3fd014
update capcha
RyanRory Jul 15, 2024
f50889d
WIP: challenge bot
RyanRory Jul 16, 2024
4096fdd
Refresh rate limit and file upload
RyanRory Jul 18, 2024
bf2bef7
improve the performance of captcha generation
RyanRory Jul 23, 2024
3d83f1b
add emoji list as the answer set
RyanRory Jul 23, 2024
7c5471d
Add file upload capability to bot API
tewinget Jul 23, 2024
cfcb89c
Merge branch 'bot-api' of https://github.com/tewinget/session-pysogs …
Jul 24, 2024
644d016
fix accidental bytes instead of str causing base64
tewinget Jul 24, 2024
468d4ae
Merge branch 'bot-api' of https://github.com/tewinget/session-pysogs …
Jul 24, 2024
e1005ff
plug in file upload logic
RyanRory Jul 24, 2024
cfd8d46
add fields to attachment protobuf messages
RyanRory Jul 24, 2024
6749902
session messages need 'attachments' (metadata) for files
tewinget Jul 24, 2024
2d05c96
Merge branch 'bot-api' of https://github.com/tewinget/session-pysogs …
Jul 25, 2024
e2bb87f
merge file upload
RyanRory Jul 26, 2024
919bd0a
bug fix and minor refactor
RyanRory Jul 29, 2024
c158690
change get file permission
RyanRory Jul 29, 2024
a447b16
modify adding and running bot
RyanRory Jul 30, 2024
9815c03
restructure
RyanRory Jul 30, 2024
8cdb57c
fix import issues
RyanRory Jul 30, 2024
25d886d
fix font path
RyanRory Jul 31, 2024
0542844
add try except for adding bot to database
RyanRory Jul 31, 2024
d29219e
fix sogs address issue
RyanRory Jul 31, 2024
bcd28e8
fix react with refresh will post 2 new captcha
RyanRory Jul 31, 2024
c34c9c6
minor fix
RyanRory Jul 31, 2024
e2d5012
delete outdated captcha when refresh
RyanRory Jul 31, 2024
51175b0
add guard logic for getting files for new members
RyanRory Jul 31, 2024
b4e4504
add more logs
RyanRory Jul 31, 2024
1619bf5
add more logs
RyanRory Aug 1, 2024
4dbceb8
fix a wrong call of bytestring_fixup()
RyanRory Aug 1, 2024
e712a0e
minor change on copy, add fail limit, add logs
RyanRory Aug 1, 2024
6685e06
add new CLI command for testing challenge bot
RyanRory Aug 1, 2024
c779d79
add logs
RyanRory Aug 1, 2024
bfeb552
add logs
RyanRory Aug 1, 2024
d95f5f3
fix query
RyanRory Aug 1, 2024
c699c56
add log
RyanRory Aug 1, 2024
a083d2a
update log
RyanRory Aug 1, 2024
dcd09d6
update log
RyanRory Aug 1, 2024
62eb830
minor fix
RyanRory Aug 1, 2024
57f351e
clean
RyanRory Aug 1, 2024
28b4318
disable deleting challenge messages for now
RyanRory Aug 1, 2024
e0b55f7
clean up
RyanRory Aug 1, 2024
9cb86cf
clean up
RyanRory Aug 1, 2024
24a5612
try to fix delete messages for bot
RyanRory Aug 1, 2024
d72c4cf
minor fix
RyanRory Aug 1, 2024
4e2f39c
increase timeout seconds
RyanRory Aug 1, 2024
35b79d9
increase timeout seconds
RyanRory Aug 1, 2024
5ae5845
Revert "increase timeout seconds"
Aug 1, 2024
6b9ed55
Revert "increase timeout seconds"
Aug 1, 2024
6376d6e
update message delete logic
RyanRory Aug 1, 2024
bc61bc1
try to add challenge bot as a mule
RyanRory Aug 1, 2024
45d1c12
try to add challenge bot as a mule
RyanRory Aug 1, 2024
79a3067
minor fix
RyanRory Aug 1, 2024
e0660c3
make challenge bot run with server
RyanRory Aug 2, 2024
e68901b
fix font path
RyanRory Aug 2, 2024
200d7d8
update requirements
RyanRory Aug 2, 2024
f5cc624
minor fix
RyanRory Aug 2, 2024
583587c
minor fix
RyanRory Aug 2, 2024
fd05aa5
increase timeout timer
RyanRory Aug 2, 2024
f5bf6ca
minor refactor
RyanRory Aug 2, 2024
eedad82
grant write permission right after receiving the correct answer
RyanRory Aug 2, 2024
235635c
change refresh limit to permanent.
RyanRory Aug 5, 2024
57a05e6
fix refresh limit logic
RyanRory Aug 5, 2024
8f4ca23
fix refresh limit
RyanRory Aug 5, 2024
a287982
update refresh and retry limit, update bot customization, update logi…
RyanRory Aug 6, 2024
ce100d2
rename parameters
RyanRory Aug 6, 2024
fba0f30
minor ifx
RyanRory Aug 6, 2024
92d988a
fix dict issues
RyanRory Aug 6, 2024
e67aca9
fix dict issues
RyanRory Aug 6, 2024
c2e0a29
reformat file
RyanRory Aug 6, 2024
4efb476
fix delete messages
RyanRory Aug 6, 2024
892efbe
fix refresh timeout message
RyanRory Aug 6, 2024
d2db0af
allow user to unreact for refresh
RyanRory Aug 6, 2024
dfcd2f9
allow user to unreact for refresh
RyanRory Aug 6, 2024
478116f
update comments
RyanRory Aug 6, 2024
12562ae
minor fix
RyanRory Aug 6, 2024
48e4a9b
remove refresh react
RyanRory Aug 6, 2024
9c2b09f
put read access required back
RyanRory Aug 6, 2024
723abbc
WIP: rename bot to plugin
RyanRory Aug 9, 2024
41cc68e
WIP: rename bot to plugin
RyanRory Aug 12, 2024
c7b13a9
WIP: rename bot to plugin
RyanRory Aug 12, 2024
08ac281
rename bot to plugin
RyanRory Aug 12, 2024
03953c7
minor fix
RyanRory Aug 12, 2024
54b5f23
captcha plugin instruction init
RyanRory Aug 12, 2024
d173cf8
WIP: instruction on setting up the captcha plugin
Aug 12, 2024
2f3926b
remove extra dependencies of python-magic and exif
RyanRory Aug 14, 2024
5df4fd9
minor refactor
RyanRory Aug 14, 2024
c790a35
generate plugin keys privately
RyanRory Aug 14, 2024
f0c1d97
update md
Aug 14, 2024
7396a99
minor fix
RyanRory Aug 14, 2024
14b9d18
Merge remote-tracking branch 'origin/bot-api' into bot-api
RyanRory Aug 14, 2024
3d3f190
minor fix
RyanRory Aug 14, 2024
bb6f2a7
add some debug info
RyanRory Aug 14, 2024
e343235
minor fix
RyanRory Aug 14, 2024
483b47c
clean
RyanRory Aug 14, 2024
f0d93b5
add last_id to users table for non-message user getters
tewinget Aug 23, 2024
62f8996
fix error with users table last_id column and migration
tewinget Aug 26, 2024
9936e99
Docs changes
KeeJef Aug 29, 2024
666d829
Update CAPTCHA Plugin Code
KeeJef Sep 3, 2024
c8ea40c
Fix issue with Android clients
KeeJef Sep 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions captcha.ini.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
; SOGS CAPTCHA plugin configuration.

[plugin]

; The name of the CAPTCHA plugin. This will be displayed to Session clients when they interact with the plugin.
;
name = CAPTCHA Plugin

; Path to the x25519 private key file. This is a 32-byte file containing the raw private key data.
; A new key is generated by default if a key is not found at this location.
;
key_file = captcha_x25519

; This is the total number of unique CAPTCHAs the SOGS will provide to the user if they fail a CAPTCHA or refresh the CAPTCHA.
; If the user exceeds the retry limit, they will need to contact the SOGS operator to have their permissions manually updated.
;
retry_limit = 3

; This is the number of seconds the SOGS will wait after a user successfully solves a CAPTCHA before the user is given write permissions.
;
write_timeout = 0

; This is the number of seconds the SOGS will wait before allowing a user to refresh the CAPTCHA.
;
refresh_timeout = 60

; This is the number of seconds the SOGS will wait before providing the next CAPTCHA if a user fails the CAPTCHA.
;
retry_timeout = 120

[sogs]

; The public key of your SOGS. This can be found in your room URLs.
; For example, if the URL to join your room was http://xx.xx.xx.xx/roomname?public_key=e6c5c8afb023a02fc2cbb6c6bc34929df5985c87b320b3d9cc360b7d47becf0b
; your sogs_pubkey_hex would be sogs_pubkey_hex = e6c5c8afb023a02fc2cbb6c6bc34929df5985c87b320b3d9cc360b7d47becf0b
; Your SOGS public key is shared across all rooms on your server; Your SOGS public key is NOT unique per room.
;
sogs_pubkey_hex =

; Your SOGS OMQ address. This address can be found in sogs.ini under the [net] section with the title omq_listen.
; Make sure in sogs.ini, omq_listen is uncommented, and ensure the port listed matches.
;
sogs_address = tcp://127.0.0.1:22028
22 changes: 18 additions & 4 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,17 +251,31 @@ def banned_user(db):


@pytest.fixture
def blind_user(db):
def blind15_user(db):
import user

return user.User(blinded=True)
return user.User(blinded15=True)


@pytest.fixture
def blind_user2(db):
def blind15_user2(db):
import user

return user.User(blinded=True)
return user.User(blinded15=True)


@pytest.fixture
def blind25_user(db):
import user

return user.User(blinded25=True)


@pytest.fixture
def blind25_user2(db):
import user

return user.User(blinded25=True)


@pytest.fixture
Expand Down
1 change: 0 additions & 1 deletion contrib/auth-example.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ def get_signing_headers(
body,
blinded: bool = True,
):

assert len(server_pk) == 32
assert len(nonce) == 16

Expand Down
64 changes: 64 additions & 0 deletions contrib/blind.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env python3

import sys
import nacl.bindings as sodium
import nacl.hash
import nacl.signing
from nacl.encoding import RawEncoder
from pyonionreq import xed25519

if len(sys.argv) < 3:
print(
f"Usage: {sys.argv[0]} SERVERPUBKEY {{SESSIONID|\"RANDOM\"}} [SESSIONID ...] -- blinds IDs",
file=sys.stderr,
)
sys.exit(1)

server_pk = sys.argv[1]
sids = sys.argv[2:]

if len(server_pk) != 64 or not all(c in '0123456789ABCDEFabcdef' for c in server_pk):
print(f"Invalid argument: expected 64 hex digit server pk as first argument")
sys.exit(2)

server_pk = bytes.fromhex(server_pk)

print(nacl.hash.blake2b(server_pk, digest_size=64, encoder=RawEncoder))

k15 = sodium.crypto_core_ed25519_scalar_reduce(
nacl.hash.blake2b(server_pk, digest_size=64, encoder=RawEncoder)
)


for i in range(len(sids)):
if sids[i] == "RANDOM":
sids[i] = (
"05"
+ nacl.signing.SigningKey.generate()
.verify_key.to_curve25519_public_key()
.encode()
.hex()
)
if (
len(sids[i]) != 66
or not sids[i].startswith('05')
or not all(c in '0123456789ABCDEFabcdef' for c in sids[i])
):
print(f"Invalid session id: expected 66 hex digit id as first argument")

print(f"SOGS pubkey: {server_pk.hex()}")

for s in sids:
s = bytes.fromhex(s)

if s[0] == 0x05:
k25 = sodium.crypto_core_ed25519_scalar_reduce(
nacl.hash.blake2b(s[1:] + server_pk, digest_size=64, encoder=RawEncoder)
)

pk15 = sodium.crypto_scalarmult_ed25519_noclamp(k15, xed25519.pubkey(s[1:]))
pk25 = sodium.crypto_scalarmult_ed25519_noclamp(k25, xed25519.pubkey(s[1:]))

print(
f"{s.hex()} blinds to:\n - 15{pk15.hex()} or …{pk15[31] ^ 0x80:02x}\n - 25{pk25.hex()} or …{pk25[31] ^ 0x80:02x}"
)
111 changes: 111 additions & 0 deletions contrib/blind25-testing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env python3

import sys
import nacl.bindings as sodium
import nacl.hash
import nacl.signing
from nacl.encoding import RawEncoder
from pyonionreq import xed25519

server_pk = bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001")

to_sign = "hello!"

for i in range(1000):
sk = nacl.signing.SigningKey.generate()
pk = sk.verify_key
xpk = pk.to_curve25519_public_key()
sid = "05" + xpk.encode().hex()

k25 = sodium.crypto_core_ed25519_scalar_reduce(
nacl.hash.blake2b(
bytes.fromhex(sid) + server_pk, digest_size=64, encoder=RawEncoder, key=b"SOGS_blind_v2"
)
)

# Comment notation:
# P = server pubkey
# a/A = ed25519 keypair
# b/B = x25519 keypair, converted from a/A
# S = session id = 0x05 || B
# T = |A|, that is, A with the sign bit cleared
# t = private scalar s.t. tG = T (which is ± the private scalar associated with A)
# k = blinding factor = H_64(S || P, key="SOGS_blind_v2")

# This is simulating what the blinding client (i.e. with full keys) can compute:

# k * A
pk25a = sodium.crypto_scalarmult_ed25519_noclamp(k25, pk.encode())
# -k * A
neg_k25 = sodium.crypto_core_ed25519_scalar_negate(k25)
pk25b = sodium.crypto_scalarmult_ed25519_noclamp(neg_k25, pk.encode())

# print(f"k: {k25.hex()}")
# print(f"-k: {neg_k25.hex()}")
#
# print(f"a: {pk25a.hex()}")
# print(f"b: {pk25b.hex()}")

assert pk25a != pk25b
assert pk25a[0:31] == pk25b[0:31]
assert pk25a[31] ^ 0x80 == pk25b[31]

# The one we want to use is what we would end up with *if* our Ed25519 had been positive (but of
# course there's a 50% chance it's negative).
ed_pk_is_positive = pk.encode()[31] & 0x80 == 0

pk25 = pk25a if ed_pk_is_positive else pk25b

###########
# Make sure we can get to pk25 from the session id
# We know sid and server_pk, so we can compute k25
T_pk25 = sodium.crypto_scalarmult_ed25519_noclamp(k25, xed25519.pubkey(xpk.encode()))
assert T_pk25 == pk25

# To sign something that validates with pk25 we have a bit more work

# First get our blinded, private scalar; we'll call it j

# We want to pick j such that it is always associated with |A|, that is, our positive pubkey,
# even if our pubkey is negative, so that someone with our session id can get our signing pubkey
# deterministically.

t = (
sk.to_curve25519_private_key().encode()
) # The value we get here is actually our private scalar, despite the name
if pk.encode()[31] & 0x80:
# If our actual pubkey is negative then negate j so that it is as if we are working from the
# positive version of our pubkey
t = sodium.crypto_core_ed25519_scalar_negate(t)

kt = sodium.crypto_core_ed25519_scalar_mul(k25, t)

kT = sodium.crypto_scalarmult_ed25519_base_noclamp(kt)
assert kT == pk25

# Now we more or less follow EdDSA, but with our blinded scalar instead of real scalar, and with
# a different hash function. (See comments in libsession-util config/groups/keys.cpp for more
# details).
hseed = nacl.hash.blake2b(
sk.encode()[0:31], key=b"SOGS25Seed", encoder=nacl.encoding.RawEncoder
)
r = sodium.crypto_core_ed25519_scalar_reduce(
nacl.hash.blake2b(
hseed + pk25 + to_sign.encode(), 64, key=b"SOGS25Sig", encoder=nacl.encoding.RawEncoder
)
)
R = sodium.crypto_scalarmult_ed25519_base_noclamp(r)

# S = r + H(R || A || M) a (with A=kT, a=kt)
hram = nacl.hash.sha512(R + kT + to_sign.encode(), encoder=nacl.encoding.RawEncoder)
S = sodium.crypto_core_ed25519_scalar_reduce(hram)
S = sodium.crypto_core_ed25519_scalar_mul(S, kt)
S = sodium.crypto_core_ed25519_scalar_add(S, r)

sig = R + S

###########################################
# Test bog standard Ed25519 signature verification:

vk = nacl.signing.VerifyKey(pk25)
vk.verify(to_sign.encode(), sig)
2 changes: 0 additions & 2 deletions contrib/pg-import.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@


with pgsql.transaction():

curin = old.cursor()
curout = pgsql.cursor()

Expand Down Expand Up @@ -131,7 +130,6 @@
curout.execute("ALTER TABLE rooms DROP CONSTRAINT room_image_fk")

def copy(table):

cols = [r['name'] for r in curin.execute(f"PRAGMA table_info({table})")]
if not cols:
raise RuntimeError(f"Expected table {table} does not exist in sqlite db")
Expand Down
16 changes: 9 additions & 7 deletions contrib/upgrade-tests/dump-db.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ def dump_rows(table, extra=None, where=None, order="id", skip=set()):
for r in cur:
table.add_row(
[
'NULL'
if r[i] is None
else int(r[i])
if isinstance(r[i], bool)
else f"{r[i]:.3f}"
if isinstance(r[i], float)
else r[i]
(
'NULL'
if r[i] is None
else (
int(r[i])
if isinstance(r[i], bool)
else f"{r[i]:.3f}" if isinstance(r[i], float) else r[i]
)
)
for i in indices
]
)
Expand Down
1 change: 1 addition & 0 deletions contrib/uwsgi-sogs-standalone.ini
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enable-threads = true
http = :80
mount = /=sogs.web:app
mule = sogs.mule:run
;mule = sogs.mule:run_captcha
log-4xx = true
log-5xx = true
disable-logging = true
6 changes: 3 additions & 3 deletions install-uwsgi.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Python using:
sudo curl -so /etc/apt/trusted.gpg.d/oxen.gpg https://deb.oxen.io/pub.gpg
echo "deb https://deb.oxen.io $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/oxen.list
sudo apt update
sudo apt install python3-{oxenmq,oxenc,pyonionreq,coloredlogs,uwsgidecorators,flask,cryptography,nacl,pil,protobuf,openssl,qrcode,better-profanity,sqlalchemy,sqlalchemy-utils} uwsgi-plugin-python3
sudo apt install python3-{oxenmq,oxenc,session-util,coloredlogs,uwsgidecorators,flask,cryptography,nacl,pil,protobuf,openssl,qrcode,better-profanity,sqlalchemy,sqlalchemy-utils} uwsgi-plugin-python3
```

If you want to use a postgresql database backend then you will also need the python3-psycopg2
Expand All @@ -45,7 +45,7 @@ Copy the `sogs.ini.sample` to `sogs.ini`:
cp sogs.ini.sample sogs.ini
```

and edit it to change settings as desired. At a minimum you must uncomment and set the `base_url`
Use a text editor like nano to open the file and edit it to change settings as desired. At a minimum you must uncomment and set the `base_url`
setting to your SOGS URL; this can be a domain name or a public ip address. Using a domain name is
recommended over a bare IP as it can later be moved to a new host or new ISP, while while a bare IP
cannot.
Expand All @@ -55,7 +55,7 @@ For example:
base_url = http://sogs.example.com
```

### uwsgi.ini
### uwsgi-sogs.ini

SOGS requires uwsgi to manage processes; sample configurations are available in the contrib/
directory. For a simple setup listening directly on a public IP/port you can use the standalone
Expand Down
Loading