mirror of
https://github.com/balzack/databag.git
synced 2025-02-14 12:39:17 +00:00
support seal key handling in webapp
This commit is contained in:
parent
538094bf7f
commit
009f5c3a96
@ -3699,9 +3699,12 @@ components:
|
|||||||
- privateKeyEncrypted
|
- privateKeyEncrypted
|
||||||
- publicKey
|
- publicKey
|
||||||
properties:
|
properties:
|
||||||
salt:
|
passwordSalt:
|
||||||
type: string
|
type: string
|
||||||
format: base64 encoded data
|
format: hex encoded data
|
||||||
|
privateKeyIv:
|
||||||
|
type: string
|
||||||
|
format: hex encoded data
|
||||||
privateKeyEncrypted:
|
privateKeyEncrypted:
|
||||||
type: string
|
type: string
|
||||||
format: base64 encoded data
|
format: base64 encoded data
|
||||||
|
@ -23,7 +23,8 @@ func GetAccountStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// construct response
|
// construct response
|
||||||
seal := &Seal{}
|
seal := &Seal{}
|
||||||
seal.Salt = account.AccountDetail.SealSalt
|
seal.PasswordSalt = account.AccountDetail.SealSalt
|
||||||
|
seal.PrivateKeyIV = account.AccountDetail.SealIV
|
||||||
seal.PrivateKeyEncrypted = account.AccountDetail.SealPrivate
|
seal.PrivateKeyEncrypted = account.AccountDetail.SealPrivate
|
||||||
seal.PublicKey = account.AccountDetail.SealPublic
|
seal.PublicKey = account.AccountDetail.SealPublic
|
||||||
status := &AccountStatus{}
|
status := &AccountStatus{}
|
||||||
|
@ -22,7 +22,8 @@ func SetAccountSeal(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update record
|
// update record
|
||||||
account.AccountDetail.SealSalt = seal.Salt
|
account.AccountDetail.SealSalt = seal.PasswordSalt
|
||||||
|
account.AccountDetail.SealIV = seal.PrivateKeyIV
|
||||||
account.AccountDetail.SealPrivate = seal.PrivateKeyEncrypted
|
account.AccountDetail.SealPrivate = seal.PrivateKeyEncrypted
|
||||||
account.AccountDetail.SealPublic = seal.PublicKey
|
account.AccountDetail.SealPublic = seal.PublicKey
|
||||||
|
|
||||||
|
@ -406,9 +406,11 @@ type Revision struct {
|
|||||||
|
|
||||||
//Seal key for channel sealing
|
//Seal key for channel sealing
|
||||||
type Seal struct {
|
type Seal struct {
|
||||||
Salt string `json:"salt"`
|
PasswordSalt string `json:"passwordSalt"`
|
||||||
|
|
||||||
PrivateKeyEncrypted string `json:"privateKeyEncrypted,omitempty"`
|
PrivateKeyIV string `json:"privateKeyIv,omitempty"`
|
||||||
|
|
||||||
|
PrivateKeyEncrypted string `json:"privateKeyEncrypted,omitempty"`
|
||||||
|
|
||||||
PublicKey string `json:"publicKey,omitempty"`
|
PublicKey string `json:"publicKey,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,7 @@ type AccountDetail struct {
|
|||||||
Location string
|
Location string
|
||||||
Image string
|
Image string
|
||||||
SealSalt string
|
SealSalt string
|
||||||
|
SealIV string
|
||||||
SealPrivate string
|
SealPrivate string
|
||||||
SealPublic string
|
SealPublic string
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,11 @@
|
|||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^12.0.0",
|
"@testing-library/react": "^12.0.0",
|
||||||
"@testing-library/user-event": "^13.2.1",
|
"@testing-library/user-event": "^13.2.1",
|
||||||
"antd": "^4.19.1",
|
"antd": "^5.0.4",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"base-64": "^1.0.0",
|
"base-64": "^1.0.0",
|
||||||
|
"crypto-js": "^4.1.1",
|
||||||
|
"jsencrypt": "^2.3.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
@ -17,7 +19,6 @@
|
|||||||
"react-resize-detector": "^7.0.0",
|
"react-resize-detector": "^7.0.0",
|
||||||
"react-router-dom": "^6.2.2",
|
"react-router-dom": "^6.2.2",
|
||||||
"react-scripts": "5.0.0",
|
"react-scripts": "5.0.0",
|
||||||
"react-virtualized": "^9.22.3",
|
|
||||||
"styled-components": "^5.3.3",
|
"styled-components": "^5.3.3",
|
||||||
"web-vitals": "^2.1.0"
|
"web-vitals": "^2.1.0"
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import 'antd/dist/antd.min.css';
|
import 'antd/dist/reset.css';
|
||||||
import { HashRouter as Router, Routes, Route } from "react-router-dom";
|
import { HashRouter as Router, Routes, Route } from "react-router-dom";
|
||||||
|
|
||||||
import { AppContextProvider } from 'context/AppContext';
|
import { AppContextProvider } from 'context/AppContext';
|
||||||
@ -29,8 +29,8 @@ function App() {
|
|||||||
<GroupContextProvider>
|
<GroupContextProvider>
|
||||||
<ArticleContextProvider>
|
<ArticleContextProvider>
|
||||||
<ProfileContextProvider>
|
<ProfileContextProvider>
|
||||||
<AccountContextProvider>
|
<StoreContextProvider>
|
||||||
<StoreContextProvider>
|
<AccountContextProvider>
|
||||||
<ViewportContextProvider>
|
<ViewportContextProvider>
|
||||||
<AppContextProvider>
|
<AppContextProvider>
|
||||||
<AppWrapper>
|
<AppWrapper>
|
||||||
@ -51,8 +51,8 @@ function App() {
|
|||||||
</AppWrapper>
|
</AppWrapper>
|
||||||
</AppContextProvider>
|
</AppContextProvider>
|
||||||
</ViewportContextProvider>
|
</ViewportContextProvider>
|
||||||
</StoreContextProvider>
|
</AccountContextProvider>
|
||||||
</AccountContextProvider>
|
</StoreContextProvider>
|
||||||
</ProfileContextProvider>
|
</ProfileContextProvider>
|
||||||
</ArticleContextProvider>
|
</ArticleContextProvider>
|
||||||
</GroupContextProvider>
|
</GroupContextProvider>
|
||||||
|
7
net/web/src/api/setAccountSeal.js
Normal file
7
net/web/src/api/setAccountSeal.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||||
|
|
||||||
|
export async function setAccountSeal(token, seal) {
|
||||||
|
let res = await fetchWithTimeout('/account/seal?agent=' + token, { method: 'PUT', body: JSON.stringify(seal) })
|
||||||
|
checkResponse(res);
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,23 @@
|
|||||||
import { useState, useRef } from 'react';
|
import { useContext, useState, useRef } from 'react';
|
||||||
import { setAccountSearchable } from 'api/setAccountSearchable';
|
import { setAccountSearchable } from 'api/setAccountSearchable';
|
||||||
|
import { setAccountSeal } from 'api/setAccountSeal';
|
||||||
import { getAccountStatus } from 'api/getAccountStatus';
|
import { getAccountStatus } from 'api/getAccountStatus';
|
||||||
import { setAccountLogin } from 'api/setAccountLogin';
|
import { setAccountLogin } from 'api/setAccountLogin';
|
||||||
|
import { StoreContext } from './StoreContext';
|
||||||
|
|
||||||
export function useAccountContext() {
|
export function useAccountContext() {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
init: false,
|
init: false,
|
||||||
status: null,
|
status: null,
|
||||||
|
seal: null,
|
||||||
|
sealPrivate: null,
|
||||||
});
|
});
|
||||||
const access = useRef(null);
|
const access = useRef(null);
|
||||||
const revision = useRef(null);
|
const revision = useRef(null);
|
||||||
const next = useRef(null);
|
const next = useRef(null);
|
||||||
|
|
||||||
|
const storeContext = useContext(StoreContext);
|
||||||
|
|
||||||
const updateState = (value) => {
|
const updateState = (value) => {
|
||||||
setState((s) => ({ ...s, ...value }))
|
setState((s) => ({ ...s, ...value }))
|
||||||
}
|
}
|
||||||
@ -20,7 +26,30 @@ export function useAccountContext() {
|
|||||||
if (next.current == null) {
|
if (next.current == null) {
|
||||||
if (revision.current !== rev) {
|
if (revision.current !== rev) {
|
||||||
let status = await getAccountStatus(access.current);
|
let status = await getAccountStatus(access.current);
|
||||||
updateState({ init: true, status });
|
let seal = status.seal?.publicKey ? status.seal : null;
|
||||||
|
let sealPrivate = null;
|
||||||
|
const pubKey = await storeContext.actions.getValue("seal:public");
|
||||||
|
const privKey = await storeContext.actions.getValue("seal:private");
|
||||||
|
if (status.seal?.publicKey == null) {
|
||||||
|
if (pubKey != null) {
|
||||||
|
await storeContext.actions.setValue("seal:public", null);
|
||||||
|
}
|
||||||
|
if (privKey != null) {
|
||||||
|
await storeContext.actions.setValue("seal:private", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (pubKey !== status.seal?.publicKey) {
|
||||||
|
if (privKey != null) {
|
||||||
|
await storeContext.actions.setValue("seal:private", null);
|
||||||
|
}
|
||||||
|
await storeContext.actions.setValue("seal:public", status.seal?.publicKey);
|
||||||
|
}
|
||||||
|
if (privKey != null) {
|
||||||
|
sealPrivate = privKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateState({ init: true, status, seal, sealPrivate });
|
||||||
revision.current = rev;
|
revision.current = rev;
|
||||||
}
|
}
|
||||||
if (next.current != null) {
|
if (next.current != null) {
|
||||||
@ -48,6 +77,18 @@ export function useAccountContext() {
|
|||||||
setSearchable: async (flag) => {
|
setSearchable: async (flag) => {
|
||||||
await setAccountSearchable(access.current, flag);
|
await setAccountSearchable(access.current, flag);
|
||||||
},
|
},
|
||||||
|
setSeal: async (seal, sealPrivate) => {
|
||||||
|
await storeContext.actions.setValue("seal:private", null);
|
||||||
|
await storeContext.actions.setValue("seal:public", seal.publicKey);
|
||||||
|
await storeContext.actions.setValue("seal:private", sealPrivate);
|
||||||
|
await setAccountSeal(access.current, seal);
|
||||||
|
updateState({ seal, sealPrivate });
|
||||||
|
},
|
||||||
|
unlockSeal: async (sealPrivate) => {
|
||||||
|
console.log("UNLOCKING: ", sealPrivate);
|
||||||
|
await storeContext.actions.setValue("seal:private", sealPrivate);
|
||||||
|
updateState({ sealPrivate });
|
||||||
|
},
|
||||||
setLogin: async (username, password) => {
|
setLogin: async (username, password) => {
|
||||||
await setAccountLogin(access.current, username, password);
|
await setAccountLogin(access.current, username, password);
|
||||||
},
|
},
|
||||||
|
@ -1,16 +1,30 @@
|
|||||||
import { AccountAccessWrapper, EditFooter } from './AccountAccess.styled';
|
import { AccountAccessWrapper, SealModal, EditFooter } from './AccountAccess.styled';
|
||||||
import { useAccountAccess } from './useAccountAccess.hook';
|
import { useAccountAccess } from './useAccountAccess.hook';
|
||||||
import { AccountLogin } from './accountLogin/AccountLogin';
|
import { AccountLogin } from './accountLogin/AccountLogin';
|
||||||
import { Button, Modal, Checkbox } from 'antd';
|
import { Button, Modal, Switch, Input } from 'antd';
|
||||||
import { LockOutlined } from '@ant-design/icons';
|
import { SettingOutlined, LockOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
export function AccountAccess() {
|
export function AccountAccess() {
|
||||||
|
|
||||||
const { state, actions } = useAccountAccess();
|
const { state, actions } = useAccountAccess();
|
||||||
|
|
||||||
const saveSearchable = async (e) => {
|
const saveSeal = async () => {
|
||||||
try {
|
try {
|
||||||
await actions.setSearchable(e.target.checked);
|
await actions.saveSeal();
|
||||||
|
actions.clearEditSeal();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
Modal.error({
|
||||||
|
title: 'Failed to Set Sealing Key',
|
||||||
|
comment: 'Please try again.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveSearchable = async (enable) => {
|
||||||
|
try {
|
||||||
|
await actions.setSearchable(enable);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
@ -43,13 +57,71 @@ export function AccountAccess() {
|
|||||||
</EditFooter>
|
</EditFooter>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const editSealFooter = (
|
||||||
|
<EditFooter>
|
||||||
|
<div class="select"></div>
|
||||||
|
<Button key="back" onClick={actions.clearEditSeal}>Cancel</Button>
|
||||||
|
{ state.editSealMode == null && state.seal && !state.sealPrivate && (
|
||||||
|
<Button key="save" type="primary" onClick={() => saveSeal()} disabled={!actions.canSaveSeal()} loading={state.busy}>Unlock</Button>
|
||||||
|
)}
|
||||||
|
{ !(state.editSealMode == null && state.seal && !state.sealPrivate) && (
|
||||||
|
<Button key="save" type="primary" onClick={() => saveSeal()} disabled={!actions.canSaveSeal()} loading={state.busy}>Save</Button>
|
||||||
|
)}
|
||||||
|
</EditFooter>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountAccessWrapper>
|
<AccountAccessWrapper>
|
||||||
<Checkbox checked={state.searchable} onChange={(e) => saveSearchable(e)}>Visible in Registry</Checkbox>
|
<div class="switch">
|
||||||
|
<Switch size="small" checked={state.searchable} onChange={enable => saveSearchable(enable)} />
|
||||||
|
<div class="switchLabel">Visible in Registry </div>
|
||||||
|
</div>
|
||||||
|
<div class="link" onClick={actions.setEditSeal}>
|
||||||
|
<SettingOutlined />
|
||||||
|
<div class="label">Sealed Topics</div>
|
||||||
|
</div>
|
||||||
<div class="link" onClick={actions.setEditLogin}>
|
<div class="link" onClick={actions.setEditLogin}>
|
||||||
<LockOutlined />
|
<LockOutlined />
|
||||||
<div class="label">Change Login</div>
|
<div class="label">Change Login</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Modal title="Topic Sealing Key" centered visible={state.editSeal} footer={editSealFooter} onCancel={actions.clearEditSeal}>
|
||||||
|
<SealModal>
|
||||||
|
<div class="switch">
|
||||||
|
<Switch size="small" checked={state.editSealEnabled} onChange={enable => actions.enableSeal(enable)} />
|
||||||
|
<div class="switchLabel">Enable Sealed Topics</div>
|
||||||
|
</div>
|
||||||
|
{ (state.editSealMode === 'updating' || state.editSealMode === 'sealing') && (
|
||||||
|
<div class="sealPassword">
|
||||||
|
<Input.Password placeholder="New Password" spellCheck="false" onChange={(e) => actions.setSealPassword(e.target.value)}
|
||||||
|
autocomplete="new-password" prefix={<LockOutlined />} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{ (state.editSealMode === 'updating' || state.editSealMode === 'sealing') && (
|
||||||
|
<div class="sealPassword">
|
||||||
|
<Input.Password placeholder="Confirm Password" spellCheck="false" onChange={(e) => actions.setSealConfirm(e.target.value)}
|
||||||
|
autocomplete="new-password" prefix={<LockOutlined />} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{ state.editSealMode === 'unsealing' && (
|
||||||
|
<div class="sealPassword">
|
||||||
|
<Input placeholder="Type 'delete' to remove key" spellCheck="false" onChange={(e) => actions.setUnseal(e.target.value)}
|
||||||
|
prefix={<ExclamationCircleOutlined />} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{ state.editSealMode == null && state.editSealEnabled && state.sealPrivate && (
|
||||||
|
<div class="sealPassword" onClick={() => actions.updateSeal()}>
|
||||||
|
<Input.Password defaultValue="xxxxxxxxxx" disabled={true} prefix={<LockOutlined />} />
|
||||||
|
<div class="editPassword" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{ state.editSealMode == null && state.seal && !state.sealPrivate && (
|
||||||
|
<div class="sealPassword">
|
||||||
|
<Input placeholder="Password" spellCheck="false" onChange={(e) => actions.setUnlock(e.target.value)}
|
||||||
|
prefix={<LockOutlined />} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</SealModal>
|
||||||
|
</Modal>
|
||||||
<Modal title="Account Login" centered visible={state.editLogin} footer={editLoginFooter}
|
<Modal title="Account Login" centered visible={state.editLogin} footer={editLoginFooter}
|
||||||
onCancel={actions.clearEditLogin}>
|
onCancel={actions.clearEditLogin}>
|
||||||
<AccountLogin state={state} actions={actions} />
|
<AccountLogin state={state} actions={actions} />
|
||||||
|
@ -8,6 +8,27 @@ export const AccountAccessWrapper = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
|
|
||||||
|
.switch {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
|
||||||
|
.switchEnabled {
|
||||||
|
color: ${Colors.primary};
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switchDisabled {
|
||||||
|
color: ${Colors.grey};
|
||||||
|
}
|
||||||
|
|
||||||
|
.switchLabel {
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -15,6 +36,7 @@ export const AccountAccessWrapper = styled.div`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: ${Colors.primary};
|
color: ${Colors.primary};
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
@ -22,6 +44,42 @@ export const AccountAccessWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const SealModal = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
|
||||||
|
.switch {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.switchLabel {
|
||||||
|
color: ${Colors.text};
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sealPassword {
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.editPassword {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export const EditFooter = styled.div`
|
export const EditFooter = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -2,20 +2,31 @@ import { useRef, useState, useEffect, useContext } from 'react';
|
|||||||
import { AccountContext } from 'context/AccountContext';
|
import { AccountContext } from 'context/AccountContext';
|
||||||
import { ProfileContext } from 'context/ProfileContext';
|
import { ProfileContext } from 'context/ProfileContext';
|
||||||
import { getUsername } from 'api/getUsername';
|
import { getUsername } from 'api/getUsername';
|
||||||
|
import CryptoJS from 'crypto-js';
|
||||||
|
import { JSEncrypt } from 'jsencrypt'
|
||||||
|
|
||||||
export function useAccountAccess() {
|
export function useAccountAccess() {
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
editLogin: false,
|
editLogin: false,
|
||||||
|
editSeal: false,
|
||||||
handle: null,
|
handle: null,
|
||||||
editHandle: null,
|
editHandle: null,
|
||||||
editStatus: null,
|
editStatus: null,
|
||||||
editMessage: null,
|
editMessage: null,
|
||||||
editPassword: null,
|
editPassword: null,
|
||||||
editConfirm: null,
|
EditConfirm: null,
|
||||||
busy: false,
|
busy: false,
|
||||||
searchable: null,
|
searchable: null,
|
||||||
checked: true,
|
checked: true,
|
||||||
|
editSealEnabled: false,
|
||||||
|
editSealMode: null,
|
||||||
|
sealPassword: null,
|
||||||
|
sealConfirm: null,
|
||||||
|
unseal: null,
|
||||||
|
unlock: null,
|
||||||
|
seal: null,
|
||||||
|
sealPrivate: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const profile = useContext(ProfileContext);
|
const profile = useContext(ProfileContext);
|
||||||
@ -35,11 +46,241 @@ export function useAccountAccess() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (account?.state?.status) {
|
if (account?.state?.status) {
|
||||||
updateState({ searchable: account.state.status.searchable });
|
const { seal, sealPrivate, status } = account.state;
|
||||||
|
updateState({ searchable: status.searchabled, seal, sealPrivate });
|
||||||
}
|
}
|
||||||
}, [account]);
|
}, [account]);
|
||||||
|
|
||||||
|
const convertPem = (pem) => {
|
||||||
|
var lines = pem.split('\n');
|
||||||
|
var encoded = '';
|
||||||
|
for(var i = 0;i < lines.length;i++){
|
||||||
|
if (lines[i].trim().length > 0 &&
|
||||||
|
lines[i].indexOf('-BEGIN RSA PRIVATE KEY-') < 0 &&
|
||||||
|
lines[i].indexOf('-BEGIN RSA PUBLIC KEY-') < 0 &&
|
||||||
|
lines[i].indexOf('-BEGIN PUBLIC KEY-') < 0 &&
|
||||||
|
lines[i].indexOf('-END PUBLIC KEY-') < 0 &&
|
||||||
|
lines[i].indexOf('-END RSA PRIVATE KEY-') < 0 &&
|
||||||
|
lines[i].indexOf('-END RSA PUBLIC KEY-') < 0) {
|
||||||
|
encoded += lines[i].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return encoded
|
||||||
|
};
|
||||||
|
|
||||||
|
const sealUnlock = async () => {
|
||||||
|
|
||||||
|
console.log("UNLOCKING");
|
||||||
|
console.log(state.seal.passwordSalt);
|
||||||
|
console.log(state.seal.privateKeyIv);
|
||||||
|
console.log(state.unlock);
|
||||||
|
// generate key to encrypt private key
|
||||||
|
const salt = CryptoJS.enc.Hex.parse(state.seal.passwordSalt);
|
||||||
|
const aes = CryptoJS.PBKDF2(state.unlock, salt, {
|
||||||
|
keySize: 256 / 32,
|
||||||
|
iterations: 1024,
|
||||||
|
});
|
||||||
|
|
||||||
|
// decrypt private key
|
||||||
|
const iv = CryptoJS.enc.Hex.parse(state.seal.privateKeyIv);
|
||||||
|
const enc = CryptoJS.enc.Base64.parse(state.seal.privateKeyEncrypted)
|
||||||
|
let cipherParams = CryptoJS.lib.CipherParams.create({
|
||||||
|
ciphertext: enc,
|
||||||
|
iv: iv
|
||||||
|
});
|
||||||
|
const dec = CryptoJS.AES.decrypt(cipherParams, aes, { iv: iv });
|
||||||
|
|
||||||
|
// store keuy
|
||||||
|
await account.actions.unlockSeal(dec.toString(CryptoJS.enc.Utf8))
|
||||||
|
};
|
||||||
|
|
||||||
|
const sealEnable = async () => {
|
||||||
|
|
||||||
|
// generate key to encrypt private key
|
||||||
|
const salt = CryptoJS.lib.WordArray.random(128 / 8);
|
||||||
|
const aes = CryptoJS.PBKDF2(state.sealPassword, salt, {
|
||||||
|
keySize: 256 / 32,
|
||||||
|
iterations: 1024,
|
||||||
|
});
|
||||||
|
|
||||||
|
// generate rsa key for sealing channel
|
||||||
|
const crypto = new JSEncrypt({ default_key_size: 2048 });
|
||||||
|
const key = crypto.getKey();
|
||||||
|
|
||||||
|
// encrypt private key
|
||||||
|
const iv = CryptoJS.lib.WordArray.random(128 / 8);
|
||||||
|
const privateKey = convertPem(crypto.getPrivateKey());
|
||||||
|
const enc = CryptoJS.AES.encrypt(privateKey, aes, { iv: iv });
|
||||||
|
|
||||||
|
// update account
|
||||||
|
const seal = {
|
||||||
|
passwordSalt: salt.toString(),
|
||||||
|
privateKeyIv: iv.toString(),
|
||||||
|
privateKeyEncrypted: enc.ciphertext.toString(CryptoJS.enc.Base64),
|
||||||
|
publicKey: convertPem(crypto.getPublicKey()),
|
||||||
|
}
|
||||||
|
await account.actions.setSeal(seal, privateKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sealRemove = async () => {
|
||||||
|
await account.actions.setSeal({});
|
||||||
|
};
|
||||||
|
|
||||||
|
const sealUpdate = async () => {
|
||||||
|
|
||||||
|
// generate key to encrypt private key
|
||||||
|
const salt = CryptoJS.lib.WordArray.random(128 / 8);
|
||||||
|
const aes = CryptoJS.PBKDF2(state.sealPassword, salt, {
|
||||||
|
keySize: 256 / 32,
|
||||||
|
iterations: 1024,
|
||||||
|
});
|
||||||
|
|
||||||
|
// encrypt private key
|
||||||
|
const iv = CryptoJS.lib.WordArray.random(128 / 8);
|
||||||
|
const enc = CryptoJS.AES.encrypt(state.privateKey, aes, { iv: iv });
|
||||||
|
|
||||||
|
// update account
|
||||||
|
const seal = {
|
||||||
|
passwordSalt: salt.toString(),
|
||||||
|
privateKeyIv: iv.toString(),
|
||||||
|
privateKeyEncrypted: enc.ciphertext.toString(CryptoJS.enc.Base64),
|
||||||
|
publicKey: state.seal.publicKey,
|
||||||
|
}
|
||||||
|
await account.actions.setSeal(seal, state.privateKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const test = async () => {
|
||||||
|
console.log("TESTING");
|
||||||
|
var salt = CryptoJS.lib.WordArray.random(128 / 8);
|
||||||
|
var key256Bits = CryptoJS.PBKDF2("Secret Passphrase", salt, {
|
||||||
|
keySize: 256 / 32,
|
||||||
|
iterations: 1024,
|
||||||
|
});
|
||||||
|
console.log(key256Bits);
|
||||||
|
|
||||||
|
const crypto = new JSEncrypt({ default_key_size: 2048 });
|
||||||
|
console.log(crypto);
|
||||||
|
|
||||||
|
const key = crypto.getKey();
|
||||||
|
console.log(key);
|
||||||
|
console.log(crypto.getPrivateKey());
|
||||||
|
|
||||||
|
const encrypted = crypto.encrypt("TEST MESSAGE");
|
||||||
|
|
||||||
|
console.log(encrypted);
|
||||||
|
|
||||||
|
const decrypted = crypto.decrypt(encrypted);
|
||||||
|
|
||||||
|
console.log(decrypted);
|
||||||
|
|
||||||
|
const recrypt = crypto.encrypt("TEST MESSAGE");
|
||||||
|
|
||||||
|
console.log(recrypt);
|
||||||
|
|
||||||
|
const output = crypto.decrypt(recrypt);
|
||||||
|
|
||||||
|
console.log(output);
|
||||||
|
|
||||||
|
|
||||||
|
var aes = CryptoJS.lib.WordArray.random(256 / 8);
|
||||||
|
var iv = CryptoJS.lib.WordArray.random(128 / 8);
|
||||||
|
var enc = CryptoJS.AES.encrypt("Message", key, { iv: iv });
|
||||||
|
|
||||||
|
console.log(aes);
|
||||||
|
console.log(key256Bits);
|
||||||
|
|
||||||
|
console.log(enc);
|
||||||
|
|
||||||
|
var cipherParams = CryptoJS.lib.CipherParams.create({
|
||||||
|
ciphertext: enc.ciphertext,
|
||||||
|
iv: iv
|
||||||
|
});
|
||||||
|
|
||||||
|
var dec = CryptoJS.AES.decrypt(cipherParams, key, { iv: iv });
|
||||||
|
|
||||||
|
console.log(dec);
|
||||||
|
|
||||||
|
console.log(dec.toString(CryptoJS.enc.Utf8));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
|
setEditSeal: () => {
|
||||||
|
updateState({ editSeal: true, editSealMode: null, unlock: null, editSealEnabled: state.seal });
|
||||||
|
},
|
||||||
|
clearEditSeal: () => {
|
||||||
|
updateState({ editSeal: false });
|
||||||
|
},
|
||||||
|
setSealPassword: (sealPassword) => {
|
||||||
|
updateState({ sealPassword });
|
||||||
|
},
|
||||||
|
setSealConfirm: (sealConfirm) => {
|
||||||
|
updateState({ sealConfirm });
|
||||||
|
},
|
||||||
|
setUnseal: (unseal) => {
|
||||||
|
updateState({ unseal });
|
||||||
|
},
|
||||||
|
setUnlock: (unlock) => {
|
||||||
|
updateState({ unlock });
|
||||||
|
},
|
||||||
|
updateSeal: () => {
|
||||||
|
updateState({ editSealMode: 'updating', sealConfirm: null, sealPassword: null });
|
||||||
|
},
|
||||||
|
enableSeal: (enable) => {
|
||||||
|
if (enable && state.seal) {
|
||||||
|
updateState({ editSealEnabled: true, editSealMode: null });
|
||||||
|
}
|
||||||
|
else if (enable) {
|
||||||
|
updateState({ editSealEnabled: true, editSealMode: 'sealing', sealConfirm: null, sealPassword: null });
|
||||||
|
}
|
||||||
|
else if (!enable && state.seal) {
|
||||||
|
updateState({ editSealEnabled: false, editSealMode: 'unsealing', unseal: null });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updateState({ editSealEnabled: false, editSealMode: null });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
canSaveSeal: () => {
|
||||||
|
if (state.editSealMode === 'unsealing' && state.unseal === 'delete') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (state.editSealMode === 'sealing' && state.sealPassword && state.sealPassword === state.sealConfirm) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (state.editSealMode === 'updating' && state.sealPassword && state.sealPassword === state.sealConfirm) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (state.editSealMode == null && state.seal && !state.sealPrivate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
saveSeal: async () => {
|
||||||
|
if (state.busy) {
|
||||||
|
throw new Error("operation in progress");
|
||||||
|
}
|
||||||
|
updateState({ busy: true });
|
||||||
|
try {
|
||||||
|
if (state.editSealMode === 'sealing') {
|
||||||
|
await sealEnable();
|
||||||
|
}
|
||||||
|
else if (state.editSealMode === 'unsealing') {
|
||||||
|
await sealRemove();
|
||||||
|
}
|
||||||
|
else if (state.editSealMode === 'updating') {
|
||||||
|
await sealUpdate();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await sealUnlock();
|
||||||
|
}
|
||||||
|
updateState({ busy: false });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
updateState({ busy: false });
|
||||||
|
console.log(err);
|
||||||
|
throw new Error("failed to save seal");
|
||||||
|
}
|
||||||
|
},
|
||||||
setEditLogin: () => {
|
setEditLogin: () => {
|
||||||
updateState({ editLogin: true });
|
updateState({ editLogin: true });
|
||||||
},
|
},
|
||||||
|
15620
net/web/yarn.lock
15620
net/web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user