Skip to content

Sign outgoing messages - #650

Merged
tomholub merged 14 commits into
masterfrom
feature/issue-274-sign-outgoing-messages
Oct 16, 2021
Merged

Sign outgoing messages#650
tomholub merged 14 commits into
masterfrom
feature/issue-274-sign-outgoing-messages

Conversation

@ivan-ushakov

@ivan-ushakovivan-ushakov commented Oct 9, 2021

Copy link
Copy Markdown
Contributor

close#274


Tests(delete all except exactly one):


To be filled by reviewers

I have reviewed that this PR... (tick whichever items you personally focused on during this review):

  • addresses the issue it closes (if any)
  • code is readable and understandable
  • is accompanied with tests, or tests are not needed
  • is free of vulnerabilities
  • is documented clearly and usefully, or doesn't need documentation

@ivan-ushakov

Copy link
Copy Markdown
ContributorAuthor

Test are broken because I need to know if I'm moving in the right direction first. Please take a look.

@ivan-ushakovivan-ushakov changed the title Sign outgoing messages[WIP] Sign outgoing messagesOct 9, 2021
@tomholub

Copy link
Copy Markdown
Collaborator

when it's a WIP you should be marking it as a draft:

image

@tomholubtomholub left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Roughly - see comments

Comment threadCore/source/mobile-interface/endpoints.ts Outdated
Comment threadCore/source/mobile-interface/endpoints.ts Outdated
Comment threadCore/source/mobile-interface/endpoints.ts Outdated
Comment threadCore/source/mobile-interface/validate-input.ts Outdated
Comment threadCore/source/mobile-interface/validate-input.ts Outdated
Comment threadFlowCrypt/Controllers/Compose/ComposeViewController.swift Outdated
}

guard key.passphrase != nil else {
let alert = AlertsFactory.makePassPhraseAlert(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Does makePassPhraseAlert also check that the pass phrase is correct for the key? (I don't have the code in front of me)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

No, only requires pass phrase from user.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Got it. In that case I will need to give this a closer review, to make sure makes sense as implemented.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Do you want to check signing key's pass phrase before sending it to TypeScript side?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Whether we check ahead or not, the important thing here is that it behaves properly (asks user to retry) when the user enters wrong pass phrase. So it's the UX that matters here. Have you tried that scenario?

@ivan-ushakov
ivan-ushakov marked this pull request as draft October 10, 2021 06:36
@ivan-ushakov

Copy link
Copy Markdown
ContributorAuthor

@tomholub
How to know if signature was added to message? I did some debug and Message.prototype.sign is invoked when I provide signingPrv, but I don't see signature in result MIME message. Maybe because we use default compression.

@ivan-ushakov

Copy link
Copy Markdown
ContributorAuthor

@tomholub
We have following code:

 private static cryptoMsgGetSignedBy = async (msg: OpenpgpMsgOrCleartext, keys: SortedKeysForDecrypt) => {
keys.signedBy = Value.arr.unique(await PgpKey.longids(msg.getSigningKeyIds ? msg.getSigningKeyIds() : []));
if (keys.signedBy.length && typeof Store.dbContactGet === 'function') {
const verificationContacts = await Store.dbContactGet(undefined, keys.signedBy);
keys.verificationContacts = verificationContacts.filter(contact => contact && contact.pubkey) as Contact[];
keys.forVerification = [];
for (const contact of keys.verificationContacts) {
const { keys: keysForVerification } = await openpgp.key.readArmored(contact.pubkey!);
keys.forVerification.push(...keysForVerification);
}
}
}

but Store.dbContactGet always empty:

 static dbContactGet = async (db: void, emailOrLongid: string[]): Promise<(Contact | undefined)[]> => {
return [];
}

@tomholub

Copy link
Copy Markdown
Collaborator

@tomholub How to know if signature was added to message? I did some debug and Message.prototype.sign is invoked when I provide signingPrv, but I don't see signature in result MIME message. Maybe because we use default compression.

It's because the message is armored, so you wouldn't see the signature be human-readable.

Open the message in browser extension, and see if it's showing as signed.

@tomholub

tomholub commented Oct 10, 2021

Copy link
Copy Markdown
Collaborator

@tomholub We have following code:

 private static cryptoMsgGetSignedBy = async (msg: OpenpgpMsgOrCleartext, keys: SortedKeysForDecrypt) => {
keys.signedBy = Value.arr.unique(await PgpKey.longids(msg.getSigningKeyIds ? msg.getSigningKeyIds() : []));
if (keys.signedBy.length && typeof Store.dbContactGet === 'function') {
const verificationContacts = await Store.dbContactGet(undefined, keys.signedBy);
keys.verificationContacts = verificationContacts.filter(contact => contact && contact.pubkey) as Contact[];
keys.forVerification = [];
for (const contact of keys.verificationContacts) {
const { keys: keysForVerification } = await openpgp.key.readArmored(contact.pubkey!);
keys.forVerification.push(...keysForVerification);
}
}
}

but Store.dbContactGet always empty:

 static dbContactGet = async (db: void, emailOrLongid: string[]): Promise<(Contact | undefined)[]> => {
return [];
}

This is for signature verification - you shouldn't concern yourself with that. Ivan P. is working on that in #274 . For this issue, only signing of messages will be addressed.

@ivan-ushakov

Copy link
Copy Markdown
ContributorAuthor

This is for signature verification - you shouldn't concern yourself with that. Ivan P. is working on that in #274 . For this issue, only signing of messages will be addressed.

OK, so I don't need to write test for this at TypeScript side? Because now I can not test if message was signed.

@ivan-ushakov

Copy link
Copy Markdown
ContributorAuthor

Open the message in browser extension, and see if it's showing as signed.

Here is how signed message looks like in Chrome browser with extension:
Screenshot 2021-10-10 at 22 46 53

@tomholub

Copy link
Copy Markdown
Collaborator

This is for signature verification - you shouldn't concern yourself with that. Ivan P. is working on that in #274 . For this issue, only signing of messages will be addressed.

OK, so I don't need to write test for this at TypeScript side? Because now I can not test if message was signed.

Please file an issue to write a test for this later. After #609 is done, it will be a lot easier to write the test. Thank you.

@ivan-ushakovivan-ushakov changed the title [WIP] Sign outgoing messagesSign outgoing messagesOct 12, 2021
Comment threadFlowCrypt/Functionality/Services/Key Services/KeyService.swift Outdated
@ivan-ushakov
ivan-ushakovforce-pushed the feature/issue-274-sign-outgoing-messages branch from 9bd0974 to 40bdc92CompareOctober 13, 2021 18:21
@ivan-ushakov
ivan-ushakovforce-pushed the feature/issue-274-sign-outgoing-messages branch from 40bdc92 to 828c587CompareOctober 13, 2021 18:22
@ivan-ushakov
ivan-ushakov marked this pull request as ready for review October 13, 2021 18:25
}

guard key.passphrase != nil else {
let alert = AlertsFactory.makePassPhraseAlert(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Whether we check ahead or not, the important thing here is that it behaves properly (asks user to retry) when the user enters wrong pass phrase. So it's the UX that matters here. Have you tried that scenario?

Comment threadFlowCrypt/Functionality/Services/Key Services/KeyService.swift Outdated
@tomholub
tomholub marked this pull request as draft October 14, 2021 22:11
@ivan-ushakov
ivan-ushakov marked this pull request as ready for review October 15, 2021 17:57

@tomholubtomholub left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks correctly implemented, see comments for code improvements.

Comment threadFlowCrypt/Functionality/Services/Key Services/KeyService.swift Outdated
Comment on lines +55 to +60
let keyParser: KeyService.KeyParser = { data in
return CoreRes.ParseKeys(
format: .armored,
keyDetails: [keyDetails_1, keyDetails_2]
)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There is no need to create a KeyParser type and mock this: this mock can be avoided by instead generating the actual keys with desired properties. That will be a much more direct test of the functionality, and it will simplify production code by removing KeyService.KeyParser which is there only for these tests.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Idea was to have pure unit test not integration one. Since we have separate test for the parsing inside Core class there is no need to test it again. Also this makes test slower because parsing and executing TS code takes time. But I can use your approach if you want.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you - I'm not too worried between a 5ms test and 30ms test. What worries me is this:

https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2FsYz9N4FHOLlxC%2Fgiphy.gif&f=1&nofb=1

And also complex production code.

For our codebase, mocks are more appropriate for things that are really heavy like network calls, or situations where it's not possible to easily induce the desired test behavior without a mock.

But for a bit of parsing, it's better to let the production code do its job, even if it's already covered by other tests.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I tried to use Core.generateKey but private key always have only one KeyDetails for there is no way to check algorithm completely like I did in my original test. Maybe it is not possible to have several KeyDetails for the one armored private key?

@tomholubtomholubOct 16, 2021

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One armored private key gives you a single KeyDetails, but nothing stops you from generating several private keys. After all, in the app storage, there is one private key per database record, and they all get parsed separately, each producing one KeyDetails structure.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

OK, looks like my understanding of this was wrong. I changed algorithm once again and modified unit test to work with real keys.

let parsedKeys = try self.coreService.parseKeys(
armoredOrBinary: $0.`private`.data()
)
return ($0, parsedKeys.keyDetails.first)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Parsed private key could have only one KeyDetails. Am I right here?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If the private key came from our own storage, then yes - we only store one key per record.

@tomholubtomholub left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This looks good. I understand it's just a different coding philosophy, but the sooner we unite this, the better. Thanks!

let parsedKeys = try self.coreService.parseKeys(
armoredOrBinary: $0.`private`.data()
)
return ($0, parsedKeys.keyDetails.first)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If the private key came from our own storage, then yes - we only store one key per record.

@tomholub
tomholub merged commit aa85f16 into masterOct 16, 2021
@tomholub
tomholub deleted the feature/issue-274-sign-outgoing-messages branch October 16, 2021 12:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sign outgoing messages

2 participants

@ivan-ushakov@tomholub