* 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:
Ben Gotow 2016-06-14 13:22:38 -07:00 committed by GitHub
parent 5810aeeefc
commit 72cd732ebf
7 changed files with 86 additions and 57 deletions

View file

@ -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

View file

@ -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>

View file

@ -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) =>

View file

@ -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?
km.unlock_pgp { passphrase: passphrase }, (err) =>
if err
console.warn err
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()
@getKeybaseData(key)
# 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
key.key = km
key.setTimeout()
if callback?
callback(key)
else
# public key - get keybase data
key.key = km
key.setTimeout()
@getKeybaseData(key)
if callback?
callback(key)
@trigger(@)
if callback?
callback()
)
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)
@ -190,14 +200,14 @@ class PGPKeyStore extends NylasStore
fs.writeFile(keyPath, pgp_private, (err) =>
if (err)
@_displayError(err)
shell.showItemInFolder(keyPath)
shell.showItemInFolder(keyPath)
)
else
identity.key.export_pgp_public {}, (err, pgp_public) =>
fs.writeFile(keyPath, pgp_public, (err) =>
if (err)
@_displayError(err)
shell.showItemInFolder(keyPath)
shell.showItemInFolder(keyPath)
)
)
)
@ -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."

View file

@ -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;