mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-03-01 10:33:14 +08:00
Pgp patch (#2430)
* update icons * style commit * Debugs export function The key Export function used to not successfully show items in their directories and also depend on the most recent attachment download location. This commit adds a new savedState attribute just for Keybase keys and also handles the case where that value is null. * Forces delete to populate fs.watch() was acting up and not triggering populates on deletes. Now deleteKey() just triggers a populate. * Re-enables decryption of attachments from Enigmail Decryption of attachments was disabled in the Great Password Popover Refactor of Early June 2016. This commit adds that feature back (and makes some changes to getKeyContents to facilitate that change).
This commit is contained in:
parent
5810aeeefc
commit
72cd732ebf
7 changed files with 86 additions and 57 deletions
|
@ -2,17 +2,14 @@
|
|||
|
||||
TODO:
|
||||
-----
|
||||
* decryption error handling
|
||||
* remove the .asc extension from decrypted downloads
|
||||
* final refactor
|
||||
* tests
|
||||
* docs
|
||||
|
||||
|
||||
WISHLIST:
|
||||
-----
|
||||
* message signing
|
||||
* encrypted attachment handling
|
||||
* improved decryption error handling
|
||||
* encrypted file handling
|
||||
* integrate MIT PGP Keyserver search into Keybase searchbar
|
||||
* make the decrypt interface a message body overlay instead of a button in the header
|
||||
* improve search result deduping with keys on file
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 15 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 18 KiB |
|
@ -42,6 +42,14 @@ class DecryptMessageButton extends React.Component
|
|||
{originRect: popoverTarget, direction: 'down'}
|
||||
)
|
||||
|
||||
_onClickDecryptAttachments: (event) =>
|
||||
popoverTarget = event.target.getBoundingClientRect()
|
||||
|
||||
Actions.openPopover(
|
||||
<PassphrasePopover onPopoverDone={ @_decryptAttachmentsPopoverDone } />,
|
||||
{originRect: popoverTarget, direction: 'down'}
|
||||
)
|
||||
|
||||
_decryptPopoverDone: (passphrase) =>
|
||||
{message} = @props
|
||||
for recipient in message.to
|
||||
|
@ -51,31 +59,33 @@ class DecryptMessageButton extends React.Component
|
|||
for privateKey in privateKeys
|
||||
PGPKeyStore.getKeyContents(key: privateKey, passphrase: passphrase)
|
||||
|
||||
_onDecryptAttachments: =>
|
||||
console.warn("decrypt attachments")
|
||||
|
||||
###
|
||||
_decryptAttachments: =>
|
||||
@_onClick() # unlock keys
|
||||
PGPKeyStore.decryptAttachments(@state.encryptedAttachments)
|
||||
###
|
||||
_decryptAttachmentsPopoverDone: (passphrase) =>
|
||||
{message} = @props
|
||||
for recipient in message.to
|
||||
privateKeys = PGPKeyStore.privKeys(address: recipient.email, timed: false)
|
||||
for privateKey in privateKeys
|
||||
PGPKeyStore.getKeyContents(key: privateKey, passphrase: passphrase, callback: (identity) => PGPKeyStore.decryptAttachments(identity, @state.encryptedAttachments))
|
||||
|
||||
render: =>
|
||||
# TODO inform user of errors/etc. instead of failing without showing it
|
||||
if not (@state.wasEncrypted or @state.encryptedAttachments.length > 0)
|
||||
return false
|
||||
|
||||
# TODO a message saying "this was decrypted with the key for ___@___.com"
|
||||
title = if @state.isDecrypted then "Message Decrypted" else "Message Encrypted"
|
||||
|
||||
decryptBody = false
|
||||
if !@state.isDecrypted
|
||||
decryptBody = <button title="Decrypt email body" className="btn btn-toolbar" onClick={@_onClickDecrypt} ref="button">Decrypt</button>
|
||||
|
||||
decryptAttachments = false
|
||||
###
|
||||
if @state.encryptedAttachments?.length == 1
|
||||
decryptAttachments = <button onClick={ @_decryptAttachments } className="btn btn-toolbar">Decrypt Attachment</button>
|
||||
decryptAttachments = <button onClick={ @_onClickDecryptAttachments } className="btn btn-toolbar">Decrypt Attachment</button>
|
||||
title = "Attachment Encrypted"
|
||||
else if @state.encryptedAttachments?.length > 1
|
||||
decryptAttachments = <button onClick={ @_decryptAttachments } className="btn btn-toolbar">Decrypt Attachments</button>
|
||||
###
|
||||
decryptAttachments = <button onClick={ @_onClickDecryptAttachments } className="btn btn-toolbar">Decrypt Attachments</button>
|
||||
title = "Attachments Encrypted"
|
||||
|
||||
|
||||
if decryptAttachments or decryptBody
|
||||
decryptionInterface = (<div className="decryption-interface">
|
||||
|
@ -83,14 +93,15 @@ class DecryptMessageButton extends React.Component
|
|||
{ decryptAttachments }
|
||||
</div>)
|
||||
|
||||
# TODO a message saying "this was decrypted with the key for ___@___.com"
|
||||
title = if @state.isDecrypted then "Message Decrypted" else "Message Encrypted"
|
||||
|
||||
<div className="keybase-decrypt">
|
||||
<div className="line-w-label">
|
||||
<div className="border"></div>
|
||||
<div className="title-text">{ title }</div>
|
||||
{decryptionInterface}
|
||||
<div className="decrypt-bar">
|
||||
<div className="title-text">
|
||||
{ title }
|
||||
</div>
|
||||
{ decryptionInterface }
|
||||
</div>
|
||||
<div className="border"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -15,15 +15,12 @@ class EmailPopover extends React.Component
|
|||
participants = @state
|
||||
|
||||
<div className="keybase-import-popover">
|
||||
<span className="title">
|
||||
Associate Emails with Key
|
||||
</span>
|
||||
<ParticipantsTextField
|
||||
field="to"
|
||||
className="keybase-participant-field"
|
||||
participants={ participants }
|
||||
change={ @_onRecipientFieldChange } />
|
||||
<button className="btn btn-toolbar" onClick={ @_onDone }>Done</button>
|
||||
<button className="btn btn-toolbar" onClick={ @_onDone }>Associate Emails with Key</button>
|
||||
</div>
|
||||
|
||||
_onRecipientFieldChange: (contacts) =>
|
||||
|
|
|
@ -7,6 +7,7 @@ pgp = require 'kbpgp'
|
|||
_ = require 'underscore'
|
||||
path = require 'path'
|
||||
fs = require 'fs'
|
||||
os = require 'os'
|
||||
|
||||
class PGPKeyStore extends NylasStore
|
||||
|
||||
|
@ -125,20 +126,26 @@ class PGPKeyStore extends NylasStore
|
|||
console.warn err
|
||||
else
|
||||
if km.is_pgp_locked()
|
||||
if passphrase?
|
||||
# private key - check passphrase
|
||||
passphrase ?= ""
|
||||
km.unlock_pgp { passphrase: passphrase }, (err) =>
|
||||
if err
|
||||
# decrypt checks all keys, so DON'T open an error dialog
|
||||
console.warn err
|
||||
return
|
||||
else
|
||||
console.error "No passphrase provided, but key is encrypted."
|
||||
# NOTE this only allows for one priv key per address
|
||||
# if it's already there, update, else insert
|
||||
key.key = km
|
||||
key.setTimeout()
|
||||
if callback?
|
||||
callback(key)
|
||||
else
|
||||
# public key - get keybase data
|
||||
key.key = km
|
||||
key.setTimeout()
|
||||
@getKeybaseData(key)
|
||||
@trigger(@)
|
||||
if callback?
|
||||
callback()
|
||||
callback(key)
|
||||
@trigger(@)
|
||||
)
|
||||
|
||||
getKeybaseData: (identity) =>
|
||||
|
@ -174,15 +181,18 @@ class PGPKeyStore extends NylasStore
|
|||
|
||||
exportKey: ({identity, passphrase}) =>
|
||||
atIndex = identity.addresses[0].indexOf("@")
|
||||
shortName = identity.addresses[0].slice(0, atIndex).concat(".asc")
|
||||
savePath = path.join(NylasEnv.savedState.lastDownloadDirectory, shortName)
|
||||
@getKeyContents(key: identity, passphrase: passphrase, callback: ( =>
|
||||
suffix = if identity.isPriv then "-private.asc" else ".asc"
|
||||
shortName = identity.addresses[0].slice(0, atIndex).concat(suffix)
|
||||
NylasEnv.savedState.lastKeybaseDownloadDirectory ?= os.homedir()
|
||||
savePath = path.join(NylasEnv.savedState.lastKeybaseDownloadDirectory, shortName)
|
||||
@getKeyContents(key: identity, passphrase: passphrase, callback: ( (identity) =>
|
||||
NylasEnv.showSaveDialog({
|
||||
title: "Export PGP Key",
|
||||
defaultPath: savePath,
|
||||
}, (keyPath) =>
|
||||
if (!keyPath)
|
||||
return
|
||||
NylasEnv.savedState.lastKeybaseDownloadDirectory = keyPath.slice(0, keyPath.length - shortName.length)
|
||||
if passphrase?
|
||||
identity.key.export_pgp_private {passphrase: passphrase}, (err, pgp_private) =>
|
||||
if (err)
|
||||
|
@ -212,6 +222,7 @@ class PGPKeyStore extends NylasStore
|
|||
fs.unlink(key.keyPath, (err) =>
|
||||
if (err)
|
||||
@_displayError(err)
|
||||
@_populate()
|
||||
)
|
||||
|
||||
addAddressToKey: (profile, address) =>
|
||||
|
@ -421,14 +432,13 @@ class PGPKeyStore extends NylasStore
|
|||
console.warn "Unable to decrypt message."
|
||||
@_msgStatus.push({"clientId": message.clientId, "time": Date.now(), "message": "Unable to decrypt message."})
|
||||
|
||||
decryptAttachments: (files) =>
|
||||
decryptAttachments: (identity, files) =>
|
||||
# fill our keyring with all possible private keys
|
||||
keyring = new pgp.keyring.KeyRing
|
||||
# (the unbox function will use the right one)
|
||||
|
||||
for key in @privKeys({timed: true})
|
||||
if key.key?
|
||||
keyring.add_key_manager(key.key)
|
||||
if identity.key?
|
||||
keyring.add_key_manager(identity.key)
|
||||
|
||||
FileDownloadStore._fetchAndSaveAll(files).then((filepaths) ->
|
||||
# open, decrypt, and resave each of the newly-downloaded files in place
|
||||
|
@ -460,11 +470,10 @@ class PGPKeyStore extends NylasStore
|
|||
|
||||
if literalLen == 1
|
||||
# success! replace old encrypted file with awesome decrypted file
|
||||
filepath = filepath.slice(0, filepath.length-3).concat("txt")
|
||||
fs.writeFile(filepath, literals[0].toBuffer(), (err) =>
|
||||
if err
|
||||
console.warn err
|
||||
|
||||
# TODO mv the file -> remove .asc extension
|
||||
)
|
||||
else
|
||||
console.warn "Attempt to decrypt attachment failed: #{literalLen} literals found, expected 1."
|
||||
|
|
|
@ -177,11 +177,6 @@
|
|||
}
|
||||
|
||||
.keybase-decrypt {
|
||||
.decryption-interface {
|
||||
button {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
div.line-w-label {
|
||||
display: flex;
|
||||
|
@ -189,8 +184,24 @@
|
|||
color: rgba(128, 128, 128, 0.5);
|
||||
}
|
||||
|
||||
div.title-text {
|
||||
padding: 0 10px;
|
||||
div.decrypt-bar {
|
||||
padding: 5px;
|
||||
border: 1.5px solid rgba(128, 128, 128, 0.5);
|
||||
border-radius: @border-radius-large;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
.title-text {
|
||||
flex: 1;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.decryption-interface {
|
||||
button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
div.border {
|
||||
|
@ -317,6 +328,10 @@
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
|
|
Loading…
Reference in a new issue