diff --git a/api/models.py b/api/models.py index 7115ec9b..f2ec1abe 100644 --- a/api/models.py +++ b/api/models.py @@ -425,6 +425,20 @@ def delete_lnpayment_at_order_deletion(sender, instance, **kwargs): 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( + max_length=999, + null=True, + default=None, + blank=True, + ) + encrypted_private_key = models.CharField( + max_length=999, + null=True, + default=None, + blank=True, + ) + # Total trades total_contracts = models.PositiveIntegerField(null=False, default=0) diff --git a/api/views.py b/api/views.py index c45aa518..3a03a38d 100644 --- a/api/views.py +++ b/api/views.py @@ -597,7 +597,13 @@ class UserView(APIView): # 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") + + if not public_key or not encrypted_private_key: + context["bad_request"] = "Must provide valid 'pub' and 'enc_priv' PGP keys" + return Response(context, status.HTTP_400_BAD_REQUEST) # Now the server only receives a hash of the token. So server trusts the client # with computing length, counts and unique_values to confirm the high entropy of the token @@ -655,14 +661,19 @@ class UserView(APIView): context['referral_code'] = token_urlsafe(8) user.profile.referral_code = context['referral_code'] user.profile.avatar = "static/assets/avatars/" + nickname + ".png" + user.profile.public_key = public_key + user.profile.encrypted_private_key = encrypted_private_key # If the ref_code was created by another robot, this robot was referred. queryset = Profile.objects.filter(referral_code=ref_code) if len(queryset) == 1: user.profile.is_referred = True user.profile.referred_by = queryset[0] - + user.profile.save() + + context["public_key"] = public_key + context["encrypted_private_key"] = encrypted_private_key return Response(context, status=status.HTTP_201_CREATED) else: @@ -673,6 +684,8 @@ 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 return Response(context, status=status.HTTP_202_ACCEPTED) else: # It is unlikely, but maybe the nickname is taken (1 in 20 Billion change) diff --git a/frontend/src/components/UserGenPage.js b/frontend/src/components/UserGenPage.js index bbdeb9c1..a88b6400 100644 --- a/frontend/src/components/UserGenPage.js +++ b/frontend/src/components/UserGenPage.js @@ -13,6 +13,7 @@ import { RoboSatsNoTextIcon } from "./Icons"; import { sha256 } from 'js-sha256'; import { genBase62Token, tokenStrength } from "../utils/token"; +import { genKey } from "../utils/pgp"; import { getCookie, writeCookie } from "../utils/cookies"; @@ -49,36 +50,46 @@ class UserGenPage extends Component { } getGeneratedUser=(token)=>{ - var strength = tokenStrength(token) - fetch('/api/user' + '?token_sha256=' + sha256(token) + '&unique_values=' + strength.uniqueValues +'&counts=' + strength.counts +'&length=' + token.length + '&ref_code=' + this.refCode) - .then((response) => response.json()) - .then((data) => { - this.setState({ + var strength = tokenStrength(token); + + 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) + .then((response) => response.json()) + .then((data) => { + this.setState({ + nickname: data.nickname, + bit_entropy: data.token_bits_entropy, + avatar_url: '/static/assets/avatars/' + data.nickname + '.png', + shannon_entropy: data.token_shannon_entropy, + bad_request: data.bad_request, + found: data.found, + loadingRobot:false, + }) + & + // Add nick and token to App state (token only if not a bad request) + (data.bad_request ? this.props.setAppState({ nickname: data.nickname, - bit_entropy: data.token_bits_entropy, - avatar_url: '/static/assets/avatars/' + data.nickname + '.png', - shannon_entropy: data.token_shannon_entropy, - bad_request: data.bad_request, - found: data.found, - loadingRobot:false, + avatarLoaded: false, + }) + : + (this.props.setAppState({ + 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)) + & + // If the robot has been found (recovered) we assume the token is backed up + (data.found ? this.props.setAppState({copiedToken:true}) : null) }) - & - // Add nick and token to App state (token only if not a bad request) - (data.bad_request ? this.props.setAppState({ - nickname: data.nickname, - avatarLoaded: false, - }) - : - (this.props.setAppState({ - nickname: data.nickname, - token: token, - avatarLoaded: false, - })) & writeCookie("robot_token",token)) - & - // If the robot has been found (recovered) we assume the token is backed up - (data.found ? this.props.setAppState({copiedToken:true}) : null) - }); + ); } delGeneratedUser() { diff --git a/frontend/src/utils/pgp.js b/frontend/src/utils/pgp.js index 889d7763..3d42894e 100644 --- a/frontend/src/utils/pgp.js +++ b/frontend/src/utils/pgp.js @@ -1,7 +1,7 @@ import * as openpgp from 'openpgp/lightweight'; // Generate KeyPair. Private Key is encrypted with the highEntropyToken -export async function genKeys(highEntropyToken) { +export async function genKey(highEntropyToken) { const keyPair = await openpgp.generateKey({ type: 'ecc', // Type of the key, defaults to ECC @@ -10,13 +10,8 @@ export async function genKeys(highEntropyToken) { passphrase: highEntropyToken, format: 'armored' }) - - console.log(keyPair) - - const publicKeyArmored = keyPair.publicKey; - const privateKeyArmored = keyPair.privateKey; // encrypted private key - return {publicKeyArmored: publicKeyArmored, privateKeyArmored: privateKeyArmored} + return {publicKeyArmored: keyPair.publicKey, encryptedPrivateKeyArmored: keyPair.privateKey} }; // Encrypt and sign a message