Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Signatures and encryption: Preliminary work #1257

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ebe6ee0
Signatures and encryption: Preliminary work
corrideat May 1, 2022
05976eb
Fix typo
corrideat May 1, 2022
c690fe3
Updated tests, added key ops, added keys to contracts
corrideat May 15, 2022
93274b3
New OP_KEYSHARE
corrideat Jun 5, 2022
b1e3763
Merge branch 'master' into feature/password-state-new
corrideat Jun 5, 2022
0e366f7
Broken changes
corrideat Jun 19, 2022
94a7601
ZKPP password salt
corrideat Jul 28, 2022
e9e3851
ZKPP: Frontend implementation
corrideat Aug 8, 2022
c6802d4
Merge branch 'master' into feature/password-state-new
corrideat Aug 15, 2022
1051639
Prevent Grunt from running
corrideat Aug 15, 2022
e86df63
OP_KEY_REQUEST
corrideat Aug 22, 2022
158b35d
Feedback on ZKPP and started moving invites into Chelonia
corrideat Sep 12, 2022
c6b0dfb
Error handling
corrideat Sep 19, 2022
742902e
Implemented OP_KEY_REQUEST
corrideat Sep 19, 2022
f2812fe
Bugfixes
corrideat Sep 26, 2022
55d126a
Merge branch 'master' into feature/password-state-new
corrideat Oct 10, 2022
750703b
Group joining (broken)
corrideat Oct 10, 2022
096f016
Internals: group joining
corrideat Oct 24, 2022
03bd885
Merge branch 'master' into feature/password-state-new
corrideat Oct 24, 2022
579474c
Invite link state, some bugfixes
corrideat Oct 31, 2022
64e34d3
- Implement feedback from PR
corrideat Nov 7, 2022
b44dcb2
Updates: better approach to withEnv as suggestion or code + Enforce m…
corrideat Nov 14, 2022
42df852
Bugfixes: login, group joining
corrideat Jan 9, 2023
ce5223e
Functionality for joining chat
corrideat Jan 16, 2023
2f70afa
State management: support chat
corrideat Jan 23, 2023
8fa39c1
Bugfixes and PoC for tests
corrideat Jan 30, 2023
a6a3ebd
Merge branch 'master' into feature/password-state-new
corrideat Jan 30, 2023
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
2 changes: 1 addition & 1 deletion .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
.*/frontend/assets/.*
.*/frontend/controller/service-worker.js
.*/frontend/utils/blockies.js
.*/frontend/utils/crypto.js
#.*/frontend/utils/crypto.js
corrideat marked this conversation as resolved.
Show resolved Hide resolved
.*/frontend/utils/flowTyper.js
.*/frontend/utils/vuexQueue.js
.*/historical/.*
Expand Down
2 changes: 1 addition & 1 deletion backend/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default (sbp('sbp/selectors/register', {
const json = `"${strToB64(entry.serialize())}"`
if (currentHEAD !== hash) {
this.push(prefix + json)
currentHEAD = entry.message().previousHEAD
currentHEAD = entry.head().previousHEAD
prefix = ','
} else {
this.push(prefix + json + ']')
Expand Down
1 change: 1 addition & 0 deletions frontend/controller/actions/chatroom.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default (sbp('sbp/selectors/register', {
try {
return await sbp('chelonia/out/registerContract', {
...omit(params, ['options']), // any 'options' are for this action, not for Chelonia
keys: [],
contractName: 'gi.contracts/chatroom'
})
} catch (e) {
Expand Down
1 change: 1 addition & 0 deletions frontend/controller/actions/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export default (sbp('sbp/selectors/register', {
const message = await sbp('chelonia/out/registerContract', {
contractName: 'gi.contracts/group',
publishOptions,
keys: [],
data: {
invites: {
[initialInvite.inviteSecret]: initialInvite
Expand Down
112 changes: 105 additions & 7 deletions frontend/controller/actions/identity.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
'use strict'

import sbp from '@sbp/sbp'
import { keyId, keygen, deriveKeyFromPassword, serializeKey, encrypt } from '@utils/crypto.js'
taoeffect marked this conversation as resolved.
Show resolved Hide resolved
import { GIErrorUIRuntimeError } from '@model/errors.js'
import L, { LError } from '@view-utils/translations.js'
import { imageUpload } from '@utils/image.js'
import './mailbox.js'

import { encryptedAction } from './utils.js'
import { GIMessage } from '~/shared/domains/chelonia/GIMessage.js'

// eslint-disable-next-line camelcase
const salt_TODO_CHANGEME_NEEDS_TO_BE_DYNAMIC = 'SALT CHANGEME'

export default (sbp('sbp/selectors/register', {
'gi.actions/identity/retrieveSalt': async (username: string, password: string) => {
// TODO RETRIEVE FROM SERVER
return await Promise.resolve(salt_TODO_CHANGEME_NEEDS_TO_BE_DYNAMIC)
},
'gi.actions/identity/create': async function ({
data: { username, email, password, picture },
options: { sync = true } = {},
Expand Down Expand Up @@ -39,22 +48,106 @@ export default (sbp('sbp/selectors/register', {
// and do this outside of a try block so that if it throws the error just gets passed up
const mailbox = await sbp('gi.actions/mailbox/create', { options: { sync: true } })
const mailboxID = mailbox.contractID()

// Create the necessary keys to initialise the contract
// TODO: The salt needs to be dynamically generated
// eslint-disable-next-line camelcase
const salt = salt_TODO_CHANGEME_NEEDS_TO_BE_DYNAMIC
const IPK = await deriveKeyFromPassword('edwards25519sha512batch', password, salt)
const IEK = await deriveKeyFromPassword('curve25519xsalsa20poly1305', password, salt)
const CSK = keygen('edwards25519sha512batch')
const CEK = keygen('curve25519xsalsa20poly1305')

// Key IDs
const IPKid = keyId(IPK)
const IEKid = keyId(IEK)
const CSKid = keyId(CSK)
const CEKid = keyId(CEK)

// Public keys to be stored in the contract
const IPKp = serializeKey(IPK, false)
const IEKp = serializeKey(IEK, false)
const CSKp = serializeKey(CSK, false)
const CEKp = serializeKey(CEK, false)

// Secret keys to be stored encrypted in the contract
const CSKs = encrypt(IEK, serializeKey(CSK, true))
const CEKs = encrypt(IEK, serializeKey(CEK, true))
taoeffect marked this conversation as resolved.
Show resolved Hide resolved

let userID
// next create the identity contract itself and associate it with the mailbox
try {
const user = await sbp('chelonia/out/registerContract', {
const user = await sbp('chelonia/with-env', '', {
additionalKeys: {
[IPKid]: IPK,
[CSKid]: CSK,
[CEKid]: CEK
}
}, ['chelonia/out/registerContract', {
contractName: 'gi.contracts/identity',
publishOptions,
signingKeyId: IPKid,
actionSigningKeyId: CSKid,
actionEncryptionKeyId: CEKid,
data: {
attributes: { username, email, picture: finalPicture }
}
})
},
keys: [
{
id: IPKid,
type: IPK.type,
data: IPKp,
perm: [GIMessage.OP_CONTRACT, GIMessage.OP_KEY_ADD, GIMessage.OP_KEY_DEL],
meta: {
type: 'ipk'
}
},
{
id: IEKid,
type: IEK.type,
data: IEKp,
perm: ['gi.contracts/identity/keymeta'],
meta: {
type: 'iek'
}
},
{
id: CSKid,
type: CSK.type,
data: CSKp,
perm: [GIMessage.OP_ACTION_UNENCRYPTED, GIMessage.OP_ACTION_ENCRYPTED, GIMessage.OP_ATOMIC, GIMessage.OP_CONTRACT_AUTH, GIMessage.OP_CONTRACT_DEAUTH],
meta: {
type: 'csk',
private: {
keyId: IEKid,
content: CSKs
}
}
},
{
id: CEKid,
type: CEK.type,
data: CEKp,
perm: [GIMessage.OP_ACTION_ENCRYPTED],
meta: {
type: 'cek',
private: {
keyId: IEKid,
content: CEKs
}
}
}
]
}])
userID = user.contractID()
if (sync) {
await sbp('chelonia/contract/sync', userID)
await sbp('chelonia/with-env', userID, { additionalKeys: { [IEKid]: IEK } }, ['chelonia/contract/sync', userID])
}
await sbp('gi.actions/identity/setAttributes', {
contractID: userID, data: { mailbox: mailboxID }
contractID: userID,
data: { mailbox: mailboxID },
signingKeyId: CSKid,
encryptionKeyId: CEKid
})
} catch (e) {
console.error('gi.actions/identity/create failed!', e)
Expand Down Expand Up @@ -98,17 +191,22 @@ export default (sbp('sbp/selectors/register', {
}) {
// TODO: Insert cryptography here
const userId = await sbp('namespace/lookup', username)

if (!userId) {
throw new GIErrorUIRuntimeError(L('Invalid username or password'))
}

const salt = await sbp('gi.actions/identity/retrieveSalt', username, password)
const IEK = await deriveKeyFromPassword('curve25519xsalsa20poly1305', password, salt)
const IEKid = keyId(IEK)

try {
console.debug(`Retrieved identity ${userId}`)
// TODO: move the login vuex action code into this function (see #804)
await sbp('state/vuex/dispatch', 'login', { username, identityContractID: userId })
await sbp('chelonia/with-env', userId, { additionalKeys: { [IEKid]: IEK } }, ['state/vuex/dispatch', 'login', { username, identityContractID: userId }])

if (sync) {
await sbp('chelonia/contract/sync', userId)
await sbp('chelonia/with-env', userId, { additionalKeys: { [IEKid]: IEK } }, ['chelonia/contract/sync', userId])
}

return userId
Expand Down
3 changes: 2 additions & 1 deletion frontend/controller/actions/mailbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ export default (sbp('sbp/selectors/register', {
}): Promise<GIMessage> {
try {
const mailbox = await sbp('chelonia/out/registerContract', {
contractName: 'gi.contracts/mailbox', publishOptions, data
contractName: 'gi.contracts/mailbox', publishOptions, keys: [], data
})
console.log('gi.actions/mailbox/create', { mailbox })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logging will be removed when the PR is considered no longer a draft, I assume?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep!

if (sync) {
await sbp('chelonia/contract/sync', mailbox.contractID())
}
Expand Down
12 changes: 9 additions & 3 deletions frontend/controller/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ export type GIRegParams = {

// keep in sync with ChelActionParams
export type GIActionParams = {
action: string;
contractID: string;
data: Object;
options?: Object; // these are options for the action wrapper
hooks?: Object;
publishOptions?: Object
signingKeyId: string;
encryptionKeyId: ?string;
hooks?: {
prepublishContract?: (Object) => void;
prepublish?: (Object) => void;
postpublish?: (Object) => void;
};
publishOptions?: { maxAttempts: number };
}
6 changes: 5 additions & 1 deletion frontend/controller/actions/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ export function encryptedAction (action: string, humanError: string | Function):
return {
[action]: async function (params: GIActionParams) {
try {
const state = await sbp('chelonia/latestContractState', params.contractID)
return await sbp('chelonia/out/actionEncrypted', {
...params, action: action.replace('gi.actions', 'gi.contracts')
signingKeyId: (state?._vm?.authorizedKeys?.find((k) => k.meta?.type === 'csk')?.id: ?string),
encryptionKeyId: (state?._vm?.authorizedKeys?.find((k) => k.meta?.type === 'cek')?.id: ?string),
...params,
action: action.replace('gi.actions', 'gi.contracts')
})
} catch (e) {
console.error(`${action} failed!`, e)
Expand Down
Loading