-
Notifications
You must be signed in to change notification settings - Fork 11
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
verify signed messages(inband) #1097
Comments
Here, before loading the message, we'd pull public keys from local contacts that are recorded for the email that we received the message from. Then offer these to the verification method as possible public keys it was signed with. If none of them match the signed message, we'd show "could not verify message". This should be done after issues in https://github.com/FlowCrypt/flowcrypt-android/milestone/90 so that we can pull more than one public key per email. |
Here a code snippet from iOS, regarding rendering various signature statuses private func renderPgpSignatureCheckResult(
signature: VerifyRes?,
sender: RecipientWithOrderedPublicKeys
) -> ProcessedMessage.MessageSignature {
if signature is nil then return .unsigned // render "not signed" in red
if signature.error is not nil then return .error(signature.error) // render the "cannot verify signature: \(error)"
if signature.signer is nil then return .unsigned // render "not signed" in red
let signingPubKey = sender.pubkeys.find { $0.longids.contains(signature.signer) }
if signingPubKey is nil or signature.match is nil then return .missingPubkey(longid: signature.signer) // render "cannot verify signature: no Public Key $longid" in red
if signature.match is false then return .bad // render "bad signature" in red
return .good
} |
Final flow:
|
@DenBond7 I'd like you to start working in this tomorrow - please check if you have everything you need. |
@tomholub Do we have examples for all cases? I mean UI |
yup I'll take some screenshots |
Need to test the following cases:(in progress)
I. Inband signaturecan't verify signature (missing pub key)
valid signature (good)
valid signature (partially)
valid signature (good+mixed)
II. Cleartext signaturecan't verify signature (missing pub key)
valid signature (good)
valid signature (partially)
valid signature (good+mixed)(for example two different cleartext in sequence)
II. Detached signaturevalid signature (good)
|
Thank you! |
I'll send you credentials for that account. You can test |
Here is updated code from iOS, that also takes into account partial and mixed signatures. Maybe, that would be for another issue later. I think more interesting for you could be the TypeScript code that produces private func evaluateSignatureVerificationResult(
signature: MsgBlock.VerifyRes?
) async -> ProcessedMessage.MessageSignature {
guard let signature = signature else { return .unsigned }
if let error = signature.error { return .error(error) }
guard let signer = signature.signer else { return .unsigned }
guard signature.match != nil else { return .missingPubkey(signer) }
guard signature.match == true else { return .bad }
guard signature.partial != true else { return .partial }
guard signature.mixed != true else { return .goodMixed }
return .good
}
} |
Were you able to log into the e2e test account successfully? |
Yup, all works perfectly. |
@tomholub honestly, I'm a little confused with the current code. Currently, we support various types of different cases. I mean we support a lot of time MIME messages: with inner MIME, with PGP (encryption, signing), even PGP in PGP. We split all parsed msg blocks into 2 groups: Please loot at this example
Both of these PGP blocks are encrypted and signed. I understand that it is an irregular case. But how should I handle such a case? The Android app can parse, decrypt and show this content. But what about inband signatures? Here we can have a verified signature for the first PGP block and an unverified for the second. |
Good question. I'll share what we've done on iOS. |
Also, It would be nice to have the latest version of the iOS app, maybe @Kharchevskyi can update it |
I can make a build if you can tell me the uuid of your iOS device. |
export type VerifyRes = {
signer?: string;
match: boolean | null;
error?: string;
mixed?: boolean;
partial?: boolean;
};
export const fmtContentBlock = (allContentBlocks: MsgBlock[]): { contentBlock: MsgBlock, text: string } => {
let msgContentAsHtml = '';
let msgContentAsText = '';
const contentBlocks = allContentBlocks.filter(b => !Mime.isPlainImgAtt(b))
const imgsAtTheBottom: MsgBlock[] = [];
const inlineImgsByCid: { [cid: string]: MsgBlock } = {};
for (let plainImgBlock of allContentBlocks.filter(b => Mime.isPlainImgAtt(b))) {
if (plainImgBlock.attMeta!.cid) {
inlineImgsByCid[plainImgBlock.attMeta!.cid.replace(/>$/, '').replace(/^</, '')] = plainImgBlock;
} else {
imgsAtTheBottom.push(plainImgBlock);
}
}
var verifyRes: (VerifyRes | undefined) = undefined;
var mixedSignatures = false;
var signedBlockCount = 0;
for (const block of contentBlocks) {
if (block.verifyRes) {
++signedBlockCount;
if (!verifyRes) {
verifyRes = block.verifyRes;
} else if (!block.verifyRes.match) {
if (verifyRes.match) {
verifyRes = block.verifyRes;
}
} else if (verifyRes.match && block.verifyRes.signer !== verifyRes.signer) {
mixedSignatures = true;
}
}
if (block.type === 'decryptedText') {
msgContentAsHtml += fmtMsgContentBlockAsHtml(Str.asEscapedHtml(block.content.toString()), 'green');
msgContentAsText += block.content.toString() + '\n';
} else if (block.type === 'decryptedHtml') {
// todo - add support for inline imgs? when included using cid
msgContentAsHtml += fmtMsgContentBlockAsHtml(stripHtmlRootTags(block.content.toString()), 'green');
msgContentAsText += Xss.htmlUnescape(Xss.htmlSanitizeAndStripAllTags(block.content.toString(), '\n') + '\n');
} else if (block.type === 'plainText') {
msgContentAsHtml += fmtMsgContentBlockAsHtml(Str.asEscapedHtml(block.content.toString()), 'plain');
msgContentAsText += block.content.toString() + '\n';
} else if (block.type === 'plainHtml') {
const dirtyHtmlWithImgs = fillInlineHtmlImgs(stripHtmlRootTags(block.content.toString()), inlineImgsByCid);
msgContentAsHtml += fmtMsgContentBlockAsHtml(dirtyHtmlWithImgs, 'plain');
msgContentAsText += Xss.htmlUnescape(Xss.htmlSanitizeAndStripAllTags(dirtyHtmlWithImgs, '\n') + '\n');
} else if (block.type === 'verifiedMsg') {
msgContentAsHtml += fmtMsgContentBlockAsHtml(block.content.toString(), 'gray');
msgContentAsText += Xss.htmlSanitizeAndStripAllTags(block.content.toString(), '\n') + '\n';
} else {
msgContentAsHtml += fmtMsgContentBlockAsHtml(block.content.toString(), 'plain');
msgContentAsText += block.content.toString() + '\n';
}
}
if (verifyRes && verifyRes.match) {
if (mixedSignatures) {
verifyRes.mixed = true;
}
if (signedBlockCount > 0 && signedBlockCount != contentBlocks.length) {
verifyRes.partial = true;
}
}
for (const inlineImg of imgsAtTheBottom.concat(Object.values(inlineImgsByCid))) { // render any images we did not insert into content, at the bottom
let alt = `${inlineImg.attMeta!.name || '(unnamed image)'} - ${inlineImg.attMeta!.length! / 1024}kb`;
// in current usage, as used by `endpoints.ts`: `block.attMeta!.data` actually contains base64 encoded data, not Uint8Array as the type claims
let inlineImgTag = `<img src="data:${inlineImg.attMeta!.type};base64,${inlineImg.attMeta!.data}" alt="${Xss.escape(alt)} " />`;
msgContentAsHtml += fmtMsgContentBlockAsHtml(inlineImgTag, 'plain');
msgContentAsText += `[image: ${alt}]\n`;
}
msgContentAsHtml = `
<!DOCTYPE html><html>
<head>
<meta name="viewport" content="width=device-width" />
<style>
body { word-wrap: break-word; word-break: break-word; hyphens: auto; margin-left: 0px; padding-left: 0px; }
body img { display: inline !important; height: auto !important; max-width: 95% !important; }
body pre { white-space: pre-wrap !important; }
body > div.MsgBlock > table { zoom: 75% } /* table layouts tend to overflow - eg emails from fb */
</style>
</head>
<body>${msgContentAsHtml}</body>
</html>`;
const contentBlock = MsgBlock.fromContent('plainHtml', msgContentAsHtml);
contentBlock.verifyRes = verifyRes;
return { contentBlock: contentBlock, text: msgContentAsText.trim() };
} |
@tomholub Please let me clarify some questions: what does "bad signature" mean in detail? |
Signature could not be successfully verified for other reason than not having the right public key. For example, the message was changed in transit. |
What is already done?
I. Inband signaturecan't verify signature (missing pub key)
valid signature (good)
valid signature (partially)
valid signature (good+mixed)
What is left?
For simplicity, I propose to do 1. in the current issue, move 2 for soon, and do 3 and 4 in separate issues. @tomholub What do you think? |
What is |
Agree that 2 can be moved to |
It's PGPainless terminology :), means not detached. |
Got it. Yes - agree to finish 1 here, and then move onto the next task in current milestone. (forward attachments) |
* Added a workable version of signature verification(Not all cases).| #1097 * Renamed PgpDecrypt to PgpDecryptAndOrVerify.| #1097 * Renamed PgpEncrypt to PgpEncryptAndOrSign.| #1097 * Refactored code.| #1097 * Added signature verification for cleartext.| #1097 * Modifed docker-mailserver source.| #1097 * Added handling 'bad signature' case.| #1097 * Fixed Junit tests.| #1097 * Fixed some UI tests.| #1097 * MessageDetailsActivityTest. Restored "retry" rule usage.| #1097 * Added tests for signature verification(inband).| #1097 * Refactored code.| #1097
When we receive a signed message, currently we only render it in gray border. We should also parse out information from the message about which key signed it, and do signature verification.
Before loading the message, we'd pull public keys from local contacts that are recorded for the email that we received the message from. Then offer these to the verification method as possible public keys it was signed with. If none of them match the signed message, we'd show signature not verified.
See FlowCrypt/flowcrypt-ios#278 (comment) and further discussion below for what to render in what situation (not verified, valid, invalid)
The text was updated successfully, but these errors were encountered: