Convert new UserGen into POST. Fix misformed armored keys. Example encrypt/decrypt.

This commit is contained in:
Reckless_Satoshi 2022-05-23 04:21:01 -07:00
parent 789f9fbdb1
commit ac0969baf6
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
6 changed files with 172 additions and 73 deletions

View File

@ -9,6 +9,7 @@ RUN python -m pip install --upgrade pip
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install git+git://github.com/django/django.git
# copy current dir's content to container's WORKDIR root i.e. all the contents of the robosats app
COPY . .

View File

@ -426,13 +426,13 @@ class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
# PGP keys, used for E2E chat encrytion. Priv key is encrypted with user's passphrase (highEntropyToken)
public_key = models.CharField(
public_key = models.TextField(
max_length=999,
null=True,
default=None,
blank=True,
)
encrypted_private_key = models.CharField(
encrypted_private_key = models.TextField(
max_length=999,
null=True,
default=None,

View File

@ -80,6 +80,43 @@ class UpdateOrderSerializer(serializers.Serializer):
)
amount = serializers.DecimalField(max_digits=18, decimal_places=8, allow_null=True, required=False, default=None)
class UserGenSerializer(serializers.Serializer):
# Mandatory fields
token_sha256 = serializers.CharField(max_length=64,
allow_null=False,
allow_blank=False,
required=True,
help_text="SHA256 of user secret")
public_key = serializers.CharField(max_length=999,
allow_null=False,
allow_blank=False,
required=True,
help_text="Armored ASCII PGP public key block")
encrypted_private_key = serializers.CharField(max_length=999,
allow_null=False,
allow_blank=False,
required=True,
help_text="Armored ASCII PGP encrypted private key block")
# Optional fields
ref_code = serializers.CharField(max_length=30,
allow_null=True,
allow_blank=True,
required=False,
default=None)
counts = serializers.ListField(child=serializers.IntegerField(),
allow_null=True,
required=False,
default=None)
length = serializers.IntegerField(allow_null=True,
default=None,
required=False,
min_value=1)
unique_values = serializers.IntegerField(allow_null=True,
default=None,
required=False,
min_value=1)
class ClaimRewardSerializer(serializers.Serializer):
invoice = serializers.CharField(max_length=2000,
allow_null=True,

View File

@ -7,9 +7,11 @@ from rest_framework.views import APIView
from rest_framework.response import Response
from django.contrib.auth import authenticate, login, logout
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.models import User
from api.serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer, ClaimRewardSerializer, PriceSerializer
from api.serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer, ClaimRewardSerializer, PriceSerializer, UserGenSerializer
from api.models import LNPayment, MarketTick, Order, Currency, Profile
from control.models import AccountingDay
from api.logics import Logics
@ -520,7 +522,6 @@ class OrderView(viewsets.ViewSet):
return self.get(request)
class UserView(APIView):
NickGen = NickGenerator(lang="English",
use_adv=False,
@ -528,9 +529,15 @@ class UserView(APIView):
use_noun=True,
max_num=999)
# Probably should be turned into a post method
serializer_class = UserGenSerializer
def get(self, request, format=None):
"""
DEPRECATED
The old way to generate a robot and login.
Only for login. No new users allowed. Only available using API endpoint.
Frontend does not support it anymore.
Get a new user derived from a high entropy token
- Request has a high-entropy token,
@ -538,7 +545,7 @@ class UserView(APIView):
- Creates login credentials (new User object)
Response with Avatar and Nickname.
"""
context = {}
# If an existing user opens the main page by mistake, we do not want it to create a new nickname/profile for him
if request.user.is_authenticated:
context = {"nickname": request.user.username}
@ -554,52 +561,79 @@ class UserView(APIView):
# Deprecated, kept temporarily for legacy reasons
token = request.GET.get("token")
# The old way to generate a robot and login. Soon deprecated
# Only for login. No new users allowed. Only using API endpoint.
# Frontend does not support it anymore.
if token:
value, counts = np.unique(list(token), return_counts=True)
shannon_entropy = entropy(counts, base=62)
bits_entropy = log2(len(value)**len(token))
value, counts = np.unique(list(token), return_counts=True)
shannon_entropy = entropy(counts, base=62)
bits_entropy = log2(len(value)**len(token))
# Hash the token, only 1 iteration.
hash = hashlib.sha256(str.encode(token)).hexdigest()
# Hash the token, only 1 iteration.
hash = hashlib.sha256(str.encode(token)).hexdigest()
# Generate nickname deterministically
nickname = self.NickGen.short_from_SHA256(hash, max_length=18)[0]
context["nickname"] = nickname
# Payload
context = {
"token_shannon_entropy": shannon_entropy,
"token_bits_entropy": bits_entropy,
}
# Generate nickname deterministically
nickname = self.NickGen.short_from_SHA256(hash, max_length=18)[0]
context["nickname"] = nickname
# Payload
context = {
"token_shannon_entropy": shannon_entropy,
"token_bits_entropy": bits_entropy,
}
# Do not generate a new user for the old method! Only allow login.
if len(User.objects.filter(username=nickname)) == 1:
user = authenticate(request, username=nickname, password=token)
if user is not None:
login(request, user)
# Sends the welcome back message, only if created +3 mins ago
if request.user.date_joined < (timezone.now() -
timedelta(minutes=3)):
context["found"] = "We found your Robot avatar. Welcome back!"
return Response(context, status=status.HTTP_202_ACCEPTED)
else:
# It is unlikely, but maybe the nickname is taken (1 in 20 Billion change)
context["found"] = "Bad luck, this nickname is taken"
context["bad_request"] = "Enter a different token"
return Response(context, status.HTTP_403_FORBIDDEN)
# Do not generate a new user for the old method! Only allow login.
if len(User.objects.filter(username=nickname)) == 1:
user = authenticate(request, username=nickname, password=token)
if user is not None:
login(request, user)
# Sends the welcome back message, only if created +3 mins ago
if request.user.date_joined < (timezone.now() -
timedelta(minutes=3)):
context["found"] = "We found your Robot avatar. Welcome back!"
return Response(context, status=status.HTTP_202_ACCEPTED)
else:
# It is unlikely, but maybe the nickname is taken (1 in 20 Billion change)
context["found"] = "Bad luck, this nickname is taken"
context["bad_request"] = "Enter a different token"
return Response(context, status.HTTP_403_FORBIDDEN)
elif len(User.objects.filter(username=nickname)) == 0:
context["bad_request"] = "User Generation with explicit token deprecated. Only token_sha256 allowed."
elif len(User.objects.filter(username=nickname)) == 0:
context["bad_request"] = "User Generation with explicit token deprecated. Only token_sha256 allowed."
return Response(context, status.HTTP_400_BAD_REQUEST)
def post(self, request, format=None):
"""
Get a new user derived from a high entropy token
- Request has a hash of a high-entropy token
- Request includes pubKey and encrypted privKey
- Generates new nickname and avatar.
- Creates login credentials (new User object)
Response with Avatar, Nickname, pubKey, privKey.
"""
context = {}
serializer = self.serializer_class(data=request.data)
# Return bad request if serializer is not valid
if not serializer.is_valid():
context = {"bad_request": "Invalid serializer"}
return Response(context, status=status.HTTP_400_BAD_REQUEST)
# If an existing user opens the main page by mistake, we do not want it to create a new nickname/profile for him
if request.user.is_authenticated:
context = {"nickname": request.user.username}
not_participant, _, _ = Logics.validate_already_maker_or_taker(
request.user)
# Does not allow this 'mistake' if an active order
if not not_participant:
context[
"bad_request"] = f"You are already logged in as {request.user} and have an active order"
return Response(context, status.HTTP_400_BAD_REQUEST)
# The new way. The token is never sent. Only its SHA256
token_sha256 = request.GET.get("token_sha256") # New way to gen users and get credentials
public_key = request.GET.get("pub")
encrypted_private_key = request.GET.get("enc_priv")
ref_code = request.GET.get("ref_code")
token_sha256 = serializer.data.get("token_sha256")
public_key = serializer.data.get("public_key")
encrypted_private_key = serializer.data.get("encrypted_private_key")
ref_code = serializer.data.get("ref_code")
if not public_key or not encrypted_private_key:
context["bad_request"] = "Must provide valid 'pub' and 'enc_priv' PGP keys"
@ -609,13 +643,12 @@ class UserView(APIView):
# with computing length, counts and unique_values to confirm the high entropy of the token
# In any case, it is up to the client if they want to create a bad high entropy token.
# Supplying the pieces of info about the token to compute entropy is not mandatory
# If not supply, users can be created with garbage entropy token. Frontend will always supply.
# Submitting the three params needed to compute token entropy is not mandatory
# If not submitted, avatars can be created with garbage entropy token. Frontend will always submit them.
try:
unique_values = int(request.GET.get("unique_values"))
counts = request.GET.get("counts").split(",")
counts = [int(x) for x in counts]
length = int(request.GET.get("length"))
unique_values = serializer.data.get("unique_values")
counts = serializer.data.get("counts")
length = serializer.data.get("length")
shannon_entropy = entropy(counts, base=62)
bits_entropy = log2(unique_values**length)
@ -671,11 +704,12 @@ class UserView(APIView):
user.profile.referred_by = queryset[0]
user.profile.save()
context["public_key"] = public_key
context["encrypted_private_key"] = encrypted_private_key
context["public_key"] = user.profile.public_key
context["encrypted_private_key"] = user.profile.encrypted_private_key
return Response(context, status=status.HTTP_201_CREATED)
# log in user and return pub/priv keys if existing
else:
user = authenticate(request, username=nickname, password=token_sha256)
if user is not None:
@ -684,11 +718,11 @@ class UserView(APIView):
if request.user.date_joined < (timezone.now() -
timedelta(minutes=3)):
context["found"] = "We found your Robot avatar. Welcome back!"
context["public_key"] = request.user.profile.public_key
context["encrypted_private_key"] = request.user.profile.encrypted_private_key
context["public_key"] = user.profile.public_key
context["encrypted_private_key"] = user.profile.encrypted_private_key
return Response(context, status=status.HTTP_202_ACCEPTED)
else:
# It is unlikely, but maybe the nickname is taken (1 in 20 Billion change)
# It is unlikely, but maybe the nickname is taken (1 in 20 Billion chance)
context["found"] = "Bad luck, this nickname is taken"
context["bad_request"] = "Enter a different token"
return Response(context, status.HTTP_403_FORBIDDEN)

View File

@ -34,6 +34,7 @@ services:
- .:/usr/src/robosats
- /mnt/development/lnd:/lnd
network_mode: service:tor
command: python3 -u manage.py runserver 0.0.0.0:8000
frontend:
build: ./frontend

View File

@ -13,7 +13,7 @@ import { RoboSatsNoTextIcon } from "./Icons";
import { sha256 } from 'js-sha256';
import { genBase62Token, tokenStrength } from "../utils/token";
import { genKey } from "../utils/pgp";
import { genKey , encryptMessage , decryptMessage} from "../utils/pgp";
import { getCookie, writeCookie } from "../utils/cookies";
@ -51,19 +51,31 @@ class UserGenPage extends Component {
getGeneratedUser=(token)=>{
var strength = tokenStrength(token);
const strength = tokenStrength(token);
const refCode = this.refCode
genKey(token).then((key) =>
fetch('/api/user' +
'?token_sha256=' + sha256(token) +
'&pub=' + key.publicKeyArmored +
'&enc_priv=' + key.encryptedPrivateKeyArmored +
'&unique_values=' + strength.uniqueValues +
'&counts=' + strength.counts +
'&length=' + token.length +
'&ref_code=' + this.refCode)
const requestOptions = genKey(token).then(function(key) {
return {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken')},
body: JSON.stringify({
token_sha256: sha256(token),
public_key: key.publicKeyArmored,
encrypted_private_key: key.encryptedPrivateKeyArmored,
unique_values: strength.uniqueValues,
counts: strength.counts,
length: token.length,
ref_code: refCode,
})
}}
);
console.log(requestOptions)
requestOptions.then((options) =>
fetch("/api/user/",options)
.then((response) => response.json())
.then((data) => {
.then((data) => { console.log(data) &
this.setState({
nickname: data.nickname,
bit_entropy: data.token_bits_entropy,
@ -84,11 +96,13 @@ class UserGenPage extends Component {
nickname: data.nickname,
token: token,
avatarLoaded: false,
})) & writeCookie("robot_token",token) & writeCookie("pub_key",data.public_key) & writeCookie("enc_priv_key",data.encrypted_private_key))
})) & writeCookie("robot_token",token)
& writeCookie("pub_key",data.public_key.split('\n').join('\\'))
& writeCookie("enc_priv_key",data.encrypted_private_key.split('\n').join('\\')))
&
// If the robot has been found (recovered) we assume the token is backed up
(data.found ? this.props.setAppState({copiedToken:true}) : null)
})
})
);
}
@ -108,6 +122,18 @@ class UserGenPage extends Component {
tokenHasChanged: true,
});
this.props.setAppState({copiedToken: true})
// Encryption decryption test
console.log(encryptMessage('Example text to encrypt!',
getCookie('pub_key').split('\\').join('\n'),
getCookie('enc_priv_key').split('\\').join('\n'),
getCookie('robot_token'))
.then((encryptedMessage)=> decryptMessage(
encryptedMessage,
getCookie('pub_key').split('\\').join('\n'),
getCookie('enc_priv_key').split('\\').join('\n'),
getCookie('robot_token'))
))
}
handleChangeToken=(e)=>{
@ -169,7 +195,7 @@ class UserGenPage extends Component {
{
this.state.found ?
<Grid item xs={12} align="center">
<Typography component="subtitle2" variant="subtitle2" color='primary'>
<Typography variant="subtitle2" color='primary'>
{this.state.found ? t("A robot avatar was found, welcome back!"):null}<br/>
</Typography>
</Grid>
@ -179,7 +205,7 @@ class UserGenPage extends Component {
<Grid container align="center">
<Grid item xs={12} align="center">
<TextField sx={{maxWidth: 280}}
error={this.state.bad_request}
error={this.state.bad_request ? true : false}
label={t("Store your token safely")}
required={true}
value={this.state.token}