Properly load keyrings of Mailvelope, OpenPGP.js and GnuPG

This commit is contained in:
the-djmaze 2022-01-19 20:14:21 +01:00
parent 9f0b872839
commit c2e162b01b
8 changed files with 98 additions and 46 deletions

View file

@ -4,6 +4,7 @@
* @enum {string} * @enum {string}
*/ */
export const Capa = { export const Capa = {
GnuPGP: 'GNUGP',
OpenPGP: 'OPEN_PGP', OpenPGP: 'OPEN_PGP',
Prefetch: 'PREFETCH', Prefetch: 'PREFETCH',
Contacts: 'CONTACTS', Contacts: 'CONTACTS',

View file

@ -57,7 +57,7 @@ export class SettingsUserScreen extends AbstractSettingsScreen {
settingsAddViewModel(ThemesUserSettings, 'SettingsThemes', 'SETTINGS_LABELS/LABEL_THEMES_NAME', 'themes'); settingsAddViewModel(ThemesUserSettings, 'SettingsThemes', 'SETTINGS_LABELS/LABEL_THEMES_NAME', 'themes');
} }
if (Settings.capa(Capa.OpenPGP)) { if (Settings.capa(Capa.OpenPGP) || Settings.capa(Capa.GnuPGP)) {
settingsAddViewModel(OpenPgpUserSettings, 'SettingsOpenPGP', 'OpenPGP', 'openpgp'); settingsAddViewModel(OpenPgpUserSettings, 'SettingsOpenPGP', 'OpenPGP', 'openpgp');
} }

View file

@ -40,8 +40,7 @@ export const PgpUserStore = new class {
} }
init() { init() {
if (Settings.capa(Capa.OpenPGP)) { if (Settings.capa(Capa.OpenPGP) && window.crypto && crypto.getRandomValues) {
if (window.crypto && crypto.getRandomValues) {
const script = createElement('script', {src:openPgpJs()}); const script = createElement('script', {src:openPgpJs()});
script.onload = () => { script.onload = () => {
if (window.Worker) { if (window.Worker) {
@ -53,18 +52,22 @@ export const PgpUserStore = new class {
} }
this.loadKeyrings(); this.loadKeyrings();
}; };
script.onerror = () => console.error(script.src); script.onerror = () => {
this.loadKeyrings();
console.error(script.src);
}
doc.head.append(script); doc.head.append(script);
} else { } else {
this.loadKeyrings(); this.loadKeyrings();
} }
} }
}
loadKeyrings(identifier) { loadKeyrings(identifier) {
if (window.mailvelope) { if (window.mailvelope) {
var fn = keyring => {
this.mailvelopeKeyring = keyring;
console.log('mailvelope ready'); console.log('mailvelope ready');
var fn = keyring => this.mailvelopeKeyring = keyring; };
mailvelope.getKeyring().then(fn, err => { mailvelope.getKeyring().then(fn, err => {
if (identifier) { if (identifier) {
// attempt to create a new keyring for this app/user // attempt to create a new keyring for this app/user
@ -79,19 +82,24 @@ export const PgpUserStore = new class {
} else { } else {
addEventListener('mailvelope', () => this.loadKeyrings(identifier)); addEventListener('mailvelope', () => this.loadKeyrings(identifier));
} }
if (openpgp) { if (openpgp) {
console.log('openpgp.js ready');
this.openpgpKeyring = new openpgp.Keyring(); this.openpgpKeyring = new openpgp.Keyring();
this.reloadOpenPgpKeys(); this.reloadOpenPgpKeys();
} }
if (Settings.capa(Capa.GnuPGP)) {
this.gnupgkeys = []; this.gnupgkeys = [];
Remote.request('PgpGetKeysEmails', Remote.request('GnupgGetKeysEmails',
(iError, oData) => { (iError, oData) => {
console.dir(oData); if (oData.Result) {
this.gnupgkeys = oData.Result;
console.log('gnupg ready');
}
} }
); );
} }
}
reloadOpenPgpKeys() { reloadOpenPgpKeys() {
if (this.openpgpKeyring) { if (this.openpgpKeyring) {
@ -148,6 +156,7 @@ export const PgpUserStore = new class {
delegateRunOnDestroy(this.openpgpkeys()); delegateRunOnDestroy(this.openpgpkeys());
this.openpgpkeys(keys); this.openpgpkeys(keys);
console.log('openpgp.js ready');
} }
} }
@ -213,6 +222,13 @@ export const PgpUserStore = new class {
this.openpgpkeys.find(item => item && !item.isPrivate && item.emails.includes(email)) this.openpgpkeys.find(item => item && !item.isPrivate && item.emails.includes(email))
); );
if (this.gnupgkeys) {
let length = recipients.filter(email => this.gnupgkeys[email] && this.gnupgkeys[email].can_encrypt).length;
if (length && (!all || length === count)) {
return 'gnupg';
}
}
if (openpgp && openpgp.length && (!all || openpgp.length === count)) { if (openpgp && openpgp.length && (!all || openpgp.length === count)) {
return 'openpgp'; return 'openpgp';
} }
@ -234,6 +250,10 @@ export const PgpUserStore = new class {
* Returns the first library that can. * Returns the first library that can.
*/ */
async hasPrivateKeyForEmail(email) { async hasPrivateKeyForEmail(email) {
if (this.gnupgkeys && this.gnupgkeys[email] && this.gnupgkeys[email].can_sign) {
return 'gnupg';
}
if (this.openpgpkeys && this.openpgpkeys.find(item => item && item.isPrivate && item.emails.includes(email))) { if (this.openpgpkeys && this.openpgpkeys.find(item => item && item.isPrivate && item.emails.includes(email))) {
return 'openpgp'; return 'openpgp';
} }

View file

@ -1191,6 +1191,9 @@ class Actions
if ($oConfig->Get('security', 'openpgp', false)) { if ($oConfig->Get('security', 'openpgp', false)) {
$aResult[] = Enumerations\Capa::OPEN_PGP; $aResult[] = Enumerations\Capa::OPEN_PGP;
} }
if (\SnappyMail\PGP\GnuPG::isSupported()) {
$aResult[] = Enumerations\Capa::GNUGP;
}
if ($bAdmin || ($oAccount && $oAccount->Domain()->UseSieve())) { if ($bAdmin || ($oAccount && $oAccount->Domain()->UseSieve())) {
$aResult[] = Enumerations\Capa::SIEVE; $aResult[] = Enumerations\Capa::SIEVE;

View file

@ -9,39 +9,60 @@ trait Pgp
*/ */
public function GnuPG() : ?\SnappyMail\PGP\GnuPG public function GnuPG() : ?\SnappyMail\PGP\GnuPG
{ {
$pgp_dir = $this->StorageProvider()->GenerateFilePath( $pgp_dir = \dirname($this->StorageProvider()->GenerateFilePath(
$this->getAccountFromToken(), $this->getAccountFromToken(),
\RainLoop\Providers\Storage\Enumerations\StorageType::PGP \RainLoop\Providers\Storage\Enumerations\StorageType::PGP
); ));
return \SnappyMail\PGP\GnuPG::getInstance($pgp_dir); return \SnappyMail\PGP\GnuPG::getInstance($pgp_dir);
} }
public function DoPgpGetKeysEmails() : array public function DoGnupgGetKeysEmails() : array
{ {
$GPG = $this->GnuPG(); $GPG = $this->GnuPG();
if ($GPG) { if ($GPG) {
$sign = $encrypt = $keys = []; $keys = [];
foreach ($GPG->keyInfo('') as $info) { foreach ($GPG->keyInfo('') as $info) {
if (!$info['disabled'] && !$info['expired'] && !$info['revoked']) { if (!$info['disabled'] && !$info['expired'] && !$info['revoked']) {
if ($info['can_sign']) {
foreach ($info['uids'] as $uid) { foreach ($info['uids'] as $uid) {
$private[] = $info['email']; $id = $uid['email'];
if (isset($keys[$id])) {
$keys[$id]['can_sign'] = $keys[$id]['can_sign'] || $info['can_sign'];
$keys[$id]['can_encrypt'] = $keys[$id]['can_encrypt'] || $info['can_encrypt'];
} else {
$keys[$id] = [
'name' => $uid['name'],
'email' => $uid['email'],
'can_sign' => $info['can_sign'],
'can_encrypt' => $info['can_encrypt']
];
} }
} }
if ($info['can_encrypt']) { /*
foreach ($info['uids'] as $uid) { foreach ($info['subkeys'] as $key) {
$public[] = $info['email']; $key['can_authenticate'] = true
$key['can_certify'] = true
$key['can_encrypt'] = true
$key['can_sign'] = true
$key['disabled'] = false
$key['expired'] = false
$key['expires'] = 0
$key['fingerprint'] = "99BBB6F2FDDE9E20CD78B98DC85B364A5A6CCF52"
$key['invalid'] = false
$key['is_cardkey'] = false
$key['is_de_vs'] = true
$key['is_qualified'] = false
$key['is_secret'] = false
$key['keygrip'] = "CBCCF45D4F6D300417F044A08E08F8F14522BABE"
$key['keyid'] = "C85B364A5A6CCF52"
$key['length'] = 4096
$key['pubkey_algo'] = 1
$key['revoked'] = false
$key['timestamp'] = 1428449321
}
*/
} }
} }
} return $this->DefaultResponse(__FUNCTION__, $keys);
$keys[] = $info;
}
return $this->DefaultResponse(__FUNCTION__, [
'sign' => $sign,
'encrypt' => $encrypt,
'keys' => $keys,
'info' => $GPG->getEngineInfo()
]);
} }
return $this->FalseResponse(__FUNCTION__); return $this->FalseResponse(__FUNCTION__);
} }

View file

@ -254,7 +254,7 @@ trait Response
$mResult['Plain'] = $mResponse->Plain(); $mResult['Plain'] = $mResponse->Plain();
// $this->GetCapa(false, Capa::OPEN_PGP) // $this->GetCapa(false, Capa::OPEN_PGP) || $this->GetCapa(false, Capa::GNUGP)
$mResult['isPgpEncrypted'] = $mResponse->isPgpEncrypted(); $mResult['isPgpEncrypted'] = $mResponse->isPgpEncrypted();
$mResult['PgpSigned'] = $mResponse->PgpSigned(); $mResult['PgpSigned'] = $mResponse->PgpSigned();

View file

@ -4,6 +4,7 @@ namespace RainLoop\Enumerations;
class Capa class Capa
{ {
const GNUGP = 'GNUGP';
const OPEN_PGP = 'OPEN_PGP'; const OPEN_PGP = 'OPEN_PGP';
const PREFETCH = 'PREFETCH'; const PREFETCH = 'PREFETCH';
const THEMES = 'THEMES'; const THEMES = 'THEMES';

View file

@ -11,10 +11,16 @@ class GnuPG
// Instance of PEAR Crypt_GPG // Instance of PEAR Crypt_GPG
$Crypt_GPG; $Crypt_GPG;
public static function getInstance(string $home) : ?self public static function isSupported() : bool
{
return \class_exists('gnupg')
|| \stream_resolve_include_path('Crypt/GPG.php');
}
public static function getInstance(string $base_dir) : ?self
{ {
$self = null; $self = null;
$home .= '/.gnupg'; $home = $base_dir . '/.gnupg';
if (\class_exists('gnupg')) { if (\class_exists('gnupg')) {
$self = new self; $self = new self;
$self->GnuPG = new \gnupg([ $self->GnuPG = new \gnupg([