From 40d44d8d5940d9c8d6e315a3b6ce19fb8dfaacee Mon Sep 17 00:00:00 2001
From: Reckless_Satoshi <reckless.satoshi@protonmail.com>
Date: Wed, 25 May 2022 06:13:39 -0700
Subject: [PATCH] Add download robot credential button. Add timedate to
 messages in chat.

---
 frontend/src/components/EncryptedChat.js |  8 +++---
 frontend/src/components/UserGenPage.js   | 35 ++++++++++++++++++++----
 frontend/src/utils/pgp.js                | 35 +++++++++++++++---------
 3 files changed, 56 insertions(+), 22 deletions(-)

diff --git a/frontend/src/components/EncryptedChat.js b/frontend/src/components/EncryptedChat.js
index c272a37b..ed92a1fb 100644
--- a/frontend/src/components/EncryptedChat.js
+++ b/frontend/src/components/EncryptedChat.js
@@ -208,7 +208,7 @@ class Chat extends Component {
               </div>
             </Tooltip>
           }
-          subheader={this.state.showPGP[props.index] ? props.message.encryptedMessage : props.message.plainTextMessage}
+          subheader={this.state.showPGP[props.index] ? <a> {props.message.time} <br/> {"Valid signature: " + props.message.validSignature} <br/>  {props.message.encryptedMessage} </a> : props.message.plainTextMessage}
           subheaderTypographyProps={{sx: {wordWrap: "break-word", width: '200px', color: '#444444', fontSize: this.state.showPGP[props.index]? 11 : null }}}
         />
       </Card>
@@ -256,7 +256,7 @@ class Chat extends Component {
                 label={t("Type a message")}
                 variant="standard"
                 size="small"
-                helperText={this.state.connected ? null : t("Connecting...")}
+                helperText={this.state.connected ? (this.state.peer_pub_key ? null : t("Waiting for peer public key...")) : t("Connecting...")}
                 value={this.state.value}
                 onChange={e => {
                   this.setState({ value: e.target.value });
@@ -266,7 +266,7 @@ class Chat extends Component {
               />
             </Grid>
             <Grid item alignItems="stretch" style={{ display: "flex" }}>
-              <Button sx={{'width':68}} disabled={!this.state.connected || this.state.waitingEcho} type="submit" variant="contained" color="primary">
+              <Button sx={{'width':68}} disabled={!this.state.connected || this.state.waitingEcho || this.state.peer_pub_key == null} type="submit" variant="contained" color="primary">
                 {this.state.waitingEcho ?
                   <div style={{display:'flex',alignItems:'center', flexWrap:'wrap', minWidth:68, width:68, position:"relative",left:15}}>
                     <div style={{width:20}}><KeyIcon sx={{width:18}}/></div>
@@ -301,7 +301,7 @@ class Chat extends Component {
           
           <Grid item xs={6}>
             <Tooltip placement="bottom" enterTouchDelay={0} enterDelay={500} enterNextDelay={2000} title={t("Save full log as a JSON file (messages and credentials)")}>
-              <Button size="small" color="primary" variant="outlined" onClick={()=>saveAsJson('chat_'+this.props.orderId+'.json', this.createJsonFile())}><div style={{width:28,height:20}}><ExportIcon sx={{width:20,height:20}}/></div> {t("Export")} </Button>
+              <Button size="small" color="primary" variant="outlined" onClick={()=>saveAsJson('complete_log_chat_'+this.props.orderId+'.json', this.createJsonFile())}><div style={{width:28,height:20}}><ExportIcon sx={{width:20,height:20}}/></div> {t("Export")} </Button>
             </Tooltip>
           </Grid>
         </Grid>
diff --git a/frontend/src/components/UserGenPage.js b/frontend/src/components/UserGenPage.js
index 18dc2bda..883cc243 100644
--- a/frontend/src/components/UserGenPage.js
+++ b/frontend/src/components/UserGenPage.js
@@ -9,12 +9,14 @@ import SmartToyIcon from '@mui/icons-material/SmartToy';
 import CasinoIcon from '@mui/icons-material/Casino';
 import ContentCopy from "@mui/icons-material/ContentCopy";
 import BoltIcon from '@mui/icons-material/Bolt';
+import DownloadIcon from '@mui/icons-material/Download';
 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";
+import { saveAsJson } from "../utils/saveFile";
 
 
 class UserGenPage extends Component {
@@ -146,6 +148,16 @@ class UserGenPage extends Component {
     this.setState({openInfo: false});
   };
 
+  createJsonFile = () => {
+    return ({
+      "token":getCookie('robot_token'),
+      "token_shannon_entropy": this.state.shannon_entropy,
+      "token_bit_entropy": this.state.bit_entropy,
+      "public_key": getCookie('pub_key').split('\\').join('\n'), 
+      "encrypted_private_key": getCookie('enc_priv_key').split('\\').join('\n'),
+    })
+  }
+
   render() {
     const { t, i18n} = this.props;
     return (
@@ -208,11 +220,24 @@ class UserGenPage extends Component {
                 }}
                 InputProps={{
                   startAdornment:
-                  <Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
-                    <IconButton  onClick= {()=> (navigator.clipboard.writeText(this.state.token) & this.props.setAppState({copiedToken:true}))}>
-                      <ContentCopy color={this.props.avatarLoaded & !this.props.copiedToken & !this.state.bad_request ? 'primary' : 'inherit' } sx={{width:18, height:18}}/>
-                    </IconButton>
-                  </Tooltip>,
+                  <div style={{width:50, minWidth:50, position:'relative',left:-6}}>
+                    <Grid container xs={12}>
+                    <Grid item xs={6}>
+                        <Tooltip enterTouchDelay={250} title={t("Save token and PGP credentials to file")}>
+                          <IconButton  color="primary" disabled={getCookie('robot_token')==null || !this.props.avatarLoaded} onClick= {()=> saveAsJson(this.state.nickname+'.json', this.createJsonFile())}>
+                            <DownloadIcon sx={{width:22, height:22}}/>
+                          </IconButton>
+                        </Tooltip>
+                      </Grid>
+                      <Grid item xs={6}>
+                        <Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
+                          <IconButton  onClick= {()=> (navigator.clipboard.writeText(this.state.token) & this.props.setAppState({copiedToken:true}))}>
+                            <ContentCopy color={this.props.avatarLoaded & !this.props.copiedToken & !this.state.bad_request ? 'primary' : 'inherit' } sx={{width:18, height:18}}/>
+                          </IconButton>
+                        </Tooltip>
+                      </Grid>
+                    </Grid>
+                  </div>,
                   endAdornment:
                   <Tooltip enterTouchDelay={250} title={t("Generate a new token")}>
                     <IconButton onClick={this.handleClickNewRandomToken}><CasinoIcon/></IconButton>
diff --git a/frontend/src/utils/pgp.js b/frontend/src/utils/pgp.js
index 5e2a01dc..32947778 100644
--- a/frontend/src/utils/pgp.js
+++ b/frontend/src/utils/pgp.js
@@ -1,9 +1,18 @@
-import * as openpgp from 'openpgp/lightweight';
+import { 
+    generateKey, 
+    readKey, 
+    readPrivateKey, 
+    decryptKey, 
+    encrypt,
+    decrypt,
+    createMessage,
+    readMessage
+} from 'openpgp/lightweight';
 
 // Generate KeyPair. Private Key is encrypted with the highEntropyToken
 export async function genKey(highEntropyToken) {
 
-  const keyPair = await openpgp.generateKey({
+  const keyPair = await generateKey({
     type: 'ecc', // Type of the key, defaults to ECC
     curve: 'curve25519', // ECC curve name, defaults to curve25519
     userIDs: [{name: 'RoboSats Avatar ID'+ parseInt(Math.random() * 1000000)}], //Just for identification. Ideally it would be the avatar nickname, but the nickname is generated only after submission
@@ -17,15 +26,15 @@ export async function genKey(highEntropyToken) {
 // Encrypt and sign a message
 export async function encryptMessage(plaintextMessage, ownPublicKeyArmored, peerPublicKeyArmored, privateKeyArmored, passphrase) {
 
-  const ownPublicKey = await openpgp.readKey({ armoredKey: ownPublicKeyArmored });
-  const peerPublicKey = await openpgp.readKey({ armoredKey: peerPublicKeyArmored });
-  const privateKey = await openpgp.decryptKey({
-      privateKey: await openpgp.readPrivateKey({ armoredKey: privateKeyArmored }),
+  const ownPublicKey = await readKey({ armoredKey: ownPublicKeyArmored });
+  const peerPublicKey = await readKey({ armoredKey: peerPublicKeyArmored });
+  const privateKey = await decryptKey({
+      privateKey: await readPrivateKey({ armoredKey: privateKeyArmored }),
       passphrase
   });
 
-  const encryptedMessage = await openpgp.encrypt({
-      message: await openpgp.createMessage({ text: plaintextMessage }), // input as Message object, message must be string
+  const encryptedMessage = await encrypt({
+      message: await createMessage({ text: plaintextMessage }), // input as Message object, message must be string
       encryptionKeys: [ ownPublicKey, peerPublicKey ],
       signingKeys: privateKey // optional
   });
@@ -36,16 +45,16 @@ export async function encryptMessage(plaintextMessage, ownPublicKeyArmored, peer
 // Decrypt and check signature of a message
 export async function decryptMessage(encryptedMessage, publicKeyArmored, privateKeyArmored, passphrase) {
 
-  const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
-  const privateKey = await openpgp.decryptKey({
-      privateKey: await openpgp.readPrivateKey({ armoredKey: privateKeyArmored }),
+  const publicKey = await readKey({ armoredKey: publicKeyArmored });
+  const privateKey = await decryptKey({
+      privateKey: await readPrivateKey({ armoredKey: privateKeyArmored }),
       passphrase
   });
 
-  const message = await openpgp.readMessage({
+  const message = await readMessage({
       armoredMessage: encryptedMessage // parse armored message
   });
-  const { data: decrypted, signatures } = await openpgp.decrypt({
+  const { data: decrypted, signatures } = await decrypt({
       message,
       verificationKeys: publicKey, // optional
       decryptionKeys: privateKey