read private keys from external device
This commit is contained in:
parent
94df9fbe30
commit
5460848ea9
12
package.json
12
package.json
|
@ -7,9 +7,9 @@
|
|||
"doc": "docs"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "npm run-script translations && npm run-script update",
|
||||
"update": "node scripts/update-plugins.js",
|
||||
"translations": "node scripts/download-translations.js"
|
||||
"start": "node --harmony-async-await scripts/start.js",
|
||||
"update": "node --harmony-async-await scripts/update-plugins.js",
|
||||
"translations": "node --harmony-async-await scripts/download-translations.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -20,5 +20,9 @@
|
|||
"bugs": {
|
||||
"url": "https://github.com/keeweb/keeweb-plugins/issues"
|
||||
},
|
||||
"homepage": "https://github.com/keeweb/keeweb-plugins#readme"
|
||||
"homepage": "https://github.com/keeweb/keeweb-plugins#readme",
|
||||
"devDependencies": {
|
||||
"keychain": "^1.3.0",
|
||||
"pkcs15-smartcard-sign": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
const https = require('https');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const sign = require('./sign');
|
||||
|
||||
const keys = require('../keys/onesky.json');
|
||||
|
||||
|
@ -26,176 +27,185 @@ const urlParams = {
|
|||
|
||||
const pluginManifest = fs.readFileSync('tmpl/manifest.json', 'utf8');
|
||||
const pluginIndexPage = fs.readFileSync('tmpl/language-index.html', 'utf8');
|
||||
const privateKey = fs.readFileSync('keys/private-key.pem', 'binary');
|
||||
const publicKey = fs.readFileSync('keys/public-key.pem', 'utf8')
|
||||
.match(/-+BEGIN PUBLIC KEY-+([\s\S]+?)-+END PUBLIC KEY-+/)[1]
|
||||
.replace(/\n/g, '');
|
||||
const defaultCountries = { 'SE': 'sv' };
|
||||
|
||||
loadLanguages(languages =>
|
||||
loadTranslations(translations =>
|
||||
processData(languages, translations)
|
||||
)
|
||||
);
|
||||
|
||||
function loadLanguages(callback) {
|
||||
if (USE_FILES) {
|
||||
return callback(JSON.parse(fs.readFileSync('./data/languages.json', 'utf8')));
|
||||
}
|
||||
console.log('Loading language names...');
|
||||
const url = API_URL_LANGUAGES.replace(':project_id', PROJECT_ID) + '?' +
|
||||
Object.keys(urlParams).map(param => param + '=' + urlParams[param]).join('&');
|
||||
https.get(url, res => {
|
||||
if (res.statusCode !== 200) {
|
||||
console.error(`API error ${res.statusCode}`);
|
||||
return;
|
||||
}
|
||||
console.log('Response received, reading...');
|
||||
const data = [];
|
||||
res.on('data', chunk => data.push(chunk));
|
||||
res.on('end', () => {
|
||||
console.log('Data received, parsing...');
|
||||
const json = Buffer.concat(data).toString('utf8');
|
||||
const parsed = JSON.parse(json);
|
||||
fs.writeFileSync('data/languages.json', JSON.stringify(parsed, null, 2));
|
||||
callback(parsed);
|
||||
});
|
||||
module.exports = function() {
|
||||
return new Promise(resolve => {
|
||||
loadLanguages(languages =>
|
||||
loadTranslations(translations =>
|
||||
resolve(processData(languages, translations))
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function loadTranslations(callback) {
|
||||
if (USE_FILES) {
|
||||
return callback(JSON.parse(fs.readFileSync('./data/translations.json', 'utf8')));
|
||||
}
|
||||
console.log('Loading translations...');
|
||||
const url = API_URL.replace(':project_id', PROJECT_ID) + '?' +
|
||||
Object.keys(urlParams).map(param => param + '=' + urlParams[param]).join('&');
|
||||
https.get(url, res => {
|
||||
if (res.statusCode !== 200) {
|
||||
console.error(`API error ${res.statusCode}`);
|
||||
return;
|
||||
async function loadLanguages(callback) {
|
||||
if (USE_FILES) {
|
||||
return callback(JSON.parse(fs.readFileSync('./data/languages.json', 'utf8')));
|
||||
}
|
||||
console.log('Response received, reading...');
|
||||
const data = [];
|
||||
res.on('data', chunk => data.push(chunk));
|
||||
res.on('end', () => {
|
||||
console.log('Data received, parsing...');
|
||||
const json = Buffer.concat(data).toString('utf8');
|
||||
const parsed = JSON.parse(json);
|
||||
fs.writeFileSync('data/translations.json', JSON.stringify(parsed, null, 2));
|
||||
callback(parsed);
|
||||
console.log('Loading language names...');
|
||||
const url = API_URL_LANGUAGES.replace(':project_id', PROJECT_ID) + '?' +
|
||||
Object.keys(urlParams).map(param => param + '=' + urlParams[param]).join('&');
|
||||
https.get(url, res => {
|
||||
if (res.statusCode !== 200) {
|
||||
console.error(`API error ${res.statusCode}`);
|
||||
return;
|
||||
}
|
||||
console.log('Response received, reading...');
|
||||
const data = [];
|
||||
res.on('data', chunk => data.push(chunk));
|
||||
res.on('end', () => {
|
||||
console.log('Data received, parsing...');
|
||||
const json = Buffer.concat(data).toString('utf8');
|
||||
const parsed = JSON.parse(json);
|
||||
fs.writeFileSync('data/languages.json', JSON.stringify(parsed, null, 2));
|
||||
callback(parsed);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function processData(languages, translations) {
|
||||
let langCount = 0;
|
||||
let skipCount = 0;
|
||||
const enUs = translations['en-US'].translation;
|
||||
const totalPhraseCount = Object.keys(enUs).length;
|
||||
let errors = 0;
|
||||
const meta = {};
|
||||
Object.keys(translations).forEach(lang => {
|
||||
const languageTranslations = translations[lang].translation;
|
||||
if (lang === 'en-US' || !languageTranslations) {
|
||||
return;
|
||||
async function loadTranslations (callback) {
|
||||
if (USE_FILES) {
|
||||
return callback(JSON.parse(fs.readFileSync('./data/translations.json', 'utf8')));
|
||||
}
|
||||
const langPhraseCount = Object.keys(languageTranslations).length;
|
||||
const percentage = Math.round(langPhraseCount / totalPhraseCount * 100);
|
||||
const included = percentage >= PHRASE_COUNT_THRESHOLD_PERCENT;
|
||||
const action = included ? '\x1b[36mOK\x1b[0m' : '\x1b[35mSKIP\x1b[0m';
|
||||
console.log(`[${lang}] ${langPhraseCount} / ${totalPhraseCount} (${percentage}%) -> ${action}`);
|
||||
if (included) {
|
||||
langCount++;
|
||||
for (const name of Object.keys(languageTranslations)) {
|
||||
let text = languageTranslations[name];
|
||||
let enText = enUs[name];
|
||||
if (text instanceof Array) {
|
||||
if (!(enText instanceof Array)) {
|
||||
languageTranslations[name] = text.join('\n');
|
||||
console.error(`[${lang}] \x1b[31mERROR:ARRAY\x1b[0m ${name}`);
|
||||
enText = [enText];
|
||||
console.log('Loading translations...');
|
||||
const url = API_URL.replace(':project_id', PROJECT_ID) + '?' +
|
||||
Object.keys(urlParams).map(param => param + '=' + urlParams[param]).join('&');
|
||||
https.get(url, res => {
|
||||
if (res.statusCode !== 200) {
|
||||
console.error(`API error ${res.statusCode}`);
|
||||
return;
|
||||
}
|
||||
console.log('Response received, reading...');
|
||||
const data = [];
|
||||
res.on('data', chunk => data.push(chunk));
|
||||
res.on('end', () => {
|
||||
console.log('Data received, parsing...');
|
||||
const json = Buffer.concat(data).toString('utf8');
|
||||
const parsed = JSON.parse(json);
|
||||
fs.writeFileSync('data/translations.json', JSON.stringify(parsed, null, 2));
|
||||
callback(parsed);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function processData (languages, translations) {
|
||||
let langCount = 0;
|
||||
let skipCount = 0;
|
||||
const enUs = translations['en-US'].translation;
|
||||
const totalPhraseCount = Object.keys(enUs).length;
|
||||
let errors = 0;
|
||||
const meta = {};
|
||||
for (const lang of Object.keys(translations)) {
|
||||
const languageTranslations = translations[lang].translation;
|
||||
if (lang === 'en-US' || !languageTranslations) {
|
||||
return;
|
||||
}
|
||||
const langPhraseCount = Object.keys(languageTranslations).length;
|
||||
const percentage = Math.round(langPhraseCount / totalPhraseCount * 100);
|
||||
const included = percentage >= PHRASE_COUNT_THRESHOLD_PERCENT;
|
||||
const action = included ? '\x1b[36mOK\x1b[0m' : '\x1b[35mSKIP\x1b[0m';
|
||||
console.log(`[${lang}] ${langPhraseCount} / ${totalPhraseCount} (${percentage}%) -> ${action}`);
|
||||
if (included) {
|
||||
langCount++;
|
||||
for (const name of Object.keys(languageTranslations)) {
|
||||
let text = languageTranslations[name];
|
||||
let enText = enUs[name];
|
||||
if (text instanceof Array) {
|
||||
if (!(enText instanceof Array)) {
|
||||
languageTranslations[name] = text.join('\n');
|
||||
console.error(`[${lang}] \x1b[31mERROR:ARRAY\x1b[0m ${name}`);
|
||||
enText = [enText];
|
||||
errors++;
|
||||
}
|
||||
text = text.join('\n');
|
||||
enText = enText.join('\n');
|
||||
}
|
||||
if (!enText) {
|
||||
console.warn(`[${lang}] SKIP ${name}`);
|
||||
delete languageTranslations[name];
|
||||
continue;
|
||||
}
|
||||
const textMatches = text.match(/"/g);
|
||||
const textMatchesCount = textMatches && textMatches.length || 0;
|
||||
const enTextMatches = enText.match(/"/g);
|
||||
const enTextMatchesCount = enTextMatches && enTextMatches.length || 0;
|
||||
if (enTextMatchesCount !== textMatchesCount) {
|
||||
const textHl = text.replace(/"/g, '\x1b[33m"\x1b[0m');
|
||||
console.warn(`[${lang}] \x1b[33mWARN:"\x1b[0m ${name}: ${textHl}`);
|
||||
}
|
||||
if (/[<>&]/.test(text)) {
|
||||
const textHl = text.replace(/([<>&])/g, '\x1b[31m$1\x1b[0m');
|
||||
console.error(`[${lang}] \x1b[31mERROR:<>\x1b[0m ${name}: ${textHl}`);
|
||||
errors++;
|
||||
}
|
||||
if (text.indexOf('{}') >= 0 && enText.indexOf('{}') < 0) {
|
||||
const textHl = text.replace(/\{}/g, '\x1b[31m{}\x1b[0m');
|
||||
console.error(`[${lang}] \x1b[31mERROR:{}\x1b[0m ${name}: ${textHl}`);
|
||||
errors++;
|
||||
}
|
||||
if (enText.indexOf('{}') >= 0 && text.indexOf('{}') < 0) {
|
||||
const enTextHl = enText.replace(/\{}/g, '\x1b[31m{}\x1b[0m');
|
||||
console.error(`[${lang}] \x1b[31mERROR:NO{}\x1b[0m ${name}: ${text} <--> ${enTextHl}`);
|
||||
errors++;
|
||||
}
|
||||
text = text.join('\n');
|
||||
enText = enText.join('\n');
|
||||
}
|
||||
if (!enText) {
|
||||
console.warn(`[${lang}] SKIP ${name}`);
|
||||
delete languageTranslations[name];
|
||||
continue;
|
||||
}
|
||||
const textMatches = text.match(/"/g);
|
||||
const textMatchesCount = textMatches && textMatches.length || 0;
|
||||
const enTextMatches = enText.match(/"/g);
|
||||
const enTextMatchesCount = enTextMatches && enTextMatches.length || 0;
|
||||
if (enTextMatchesCount !== textMatchesCount) {
|
||||
const textHl = text.replace(/"/g, '\x1b[33m"\x1b[0m');
|
||||
console.warn(`[${lang}] \x1b[33mWARN:"\x1b[0m ${name}: ${textHl}`);
|
||||
}
|
||||
if (/[<>&]/.test(text)) {
|
||||
const textHl = text.replace(/([<>&])/g, '\x1b[31m$1\x1b[0m');
|
||||
console.error(`[${lang}] \x1b[31mERROR:<>\x1b[0m ${name}: ${textHl}`);
|
||||
errors++;
|
||||
}
|
||||
if (text.indexOf('{}') >= 0 && enText.indexOf('{}') < 0) {
|
||||
const textHl = text.replace(/\{}/g, '\x1b[31m{}\x1b[0m');
|
||||
console.error(`[${lang}] \x1b[31mERROR:{}\x1b[0m ${name}: ${textHl}`);
|
||||
errors++;
|
||||
}
|
||||
if (enText.indexOf('{}') >= 0 && text.indexOf('{}') < 0) {
|
||||
const enTextHl = enText.replace(/\{}/g, '\x1b[31m{}\x1b[0m');
|
||||
console.error(`[${lang}] \x1b[31mERROR:NO{}\x1b[0m ${name}: ${text} <--> ${enTextHl}`);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
const languageJson = JSON.stringify(languageTranslations, null, 2);
|
||||
const sign = crypto.createSign('RSA-SHA256');
|
||||
sign.write(Buffer.from(languageJson));
|
||||
sign.end();
|
||||
const signature = sign.sign(privateKey).toString('base64');
|
||||
const languageJson = JSON.stringify(languageTranslations, null, 2);
|
||||
|
||||
const langInfo = languages.data.filter(x => x.code === lang)[0];
|
||||
const region = (defaultCountries[langInfo.region] || langInfo.region).toLowerCase();
|
||||
const langName = langInfo.locale === region ? langInfo.local_name.replace(/\s*\(.*\)/, '') : langInfo.local_name;
|
||||
const langNameEn = langInfo.locale === region ? langInfo.english_name.replace(/\s*\(.*\)/, '') : langInfo.english_name;
|
||||
meta[lang] = { name: langName, nameEn: langNameEn, count: langPhraseCount };
|
||||
const data = Buffer.from(languageJson);
|
||||
const signature = await sign(data).catch(e => {
|
||||
console.log('Sign error', e);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
if (fs.existsSync(`docs/translations/${lang}`)) {
|
||||
const manifest = JSON.parse(fs.readFileSync(`docs/translations/${lang}/manifest.json`, 'utf8'));
|
||||
if (manifest.resources.loc !== signature) {
|
||||
const parts = manifest.version.split('.');
|
||||
manifest.version = parts[0] + '.' + (+parts[1] + 1) + '.0';
|
||||
manifest.resources.loc = signature;
|
||||
fs.writeFileSync(`docs/translations/${lang}/manifest.json`, JSON.stringify(manifest, null, 2));
|
||||
const langInfo = languages.data.filter(x => x.code === lang)[0];
|
||||
const region = (defaultCountries[langInfo.region] || langInfo.region).toLowerCase();
|
||||
const langName = langInfo.locale === region ? langInfo.local_name.replace(/\s*\(.*\)/, '') : langInfo.local_name;
|
||||
const langNameEn = langInfo.locale === region ? langInfo.english_name.replace(/\s*\(.*\)/, '') : langInfo.english_name;
|
||||
meta[lang] = {name: langName, nameEn: langNameEn, count: langPhraseCount};
|
||||
|
||||
if (fs.existsSync(`docs/translations/${lang}`)) {
|
||||
const manifest = JSON.parse(fs.readFileSync(`docs/translations/${lang}/manifest.json`, 'utf8'));
|
||||
if (manifest.resources.loc !== signature) {
|
||||
const parts = manifest.version.split('.');
|
||||
manifest.version = parts[0] + '.' + (+parts[1] + 1) + '.0';
|
||||
manifest.resources.loc = signature;
|
||||
fs.writeFileSync(`docs/translations/${lang}/manifest.json`, JSON.stringify(manifest, null, 2));
|
||||
fs.writeFileSync(`docs/translations/${lang}/${lang}.json`, languageJson);
|
||||
}
|
||||
meta[lang].version = manifest.version;
|
||||
} else {
|
||||
fs.mkdirSync(`docs/translations/${lang}`);
|
||||
fs.writeFileSync(`docs/translations/${lang}/manifest.json`, pluginManifest
|
||||
.replace(/{lang}/g, lang)
|
||||
.replace(/{name_ascii}/g, langNameEn.replace(/\W+/g, '-').toLowerCase())
|
||||
.replace(/{name_en}/g, langNameEn)
|
||||
.replace(/{name}/g, langName)
|
||||
.replace(/{signature}/g, signature)
|
||||
.replace(/{key}/g, publicKey)
|
||||
);
|
||||
fs.writeFileSync(`docs/translations/${lang}/${lang}.json`, languageJson);
|
||||
fs.writeFileSync(`docs/translations/${lang}/index.html`, pluginIndexPage
|
||||
.replace(/{lang}/g, lang)
|
||||
.replace(/{name}/g, langName)
|
||||
);
|
||||
meta[lang].version = '1.0.0';
|
||||
}
|
||||
meta[lang].version = manifest.version;
|
||||
} else {
|
||||
fs.mkdirSync(`docs/translations/${lang}`);
|
||||
fs.writeFileSync(`docs/translations/${lang}/manifest.json`, pluginManifest
|
||||
.replace(/{lang}/g, lang)
|
||||
.replace(/{name_ascii}/g, langNameEn.replace(/\W+/g, '-').toLowerCase())
|
||||
.replace(/{name_en}/g, langNameEn)
|
||||
.replace(/{name}/g, langName)
|
||||
.replace(/{signature}/g, signature)
|
||||
.replace(/{key}/g, publicKey)
|
||||
);
|
||||
fs.writeFileSync(`docs/translations/${lang}/${lang}.json`, languageJson);
|
||||
fs.writeFileSync(`docs/translations/${lang}/index.html`, pluginIndexPage
|
||||
.replace(/{lang}/g, lang)
|
||||
.replace(/{name}/g, langName)
|
||||
);
|
||||
meta[lang].version = '1.0.0';
|
||||
skipCount++;
|
||||
}
|
||||
} else {
|
||||
skipCount++;
|
||||
}
|
||||
});
|
||||
console.log(`Done: ${langCount} written, ${skipCount} skipped, ${errors} errors`);
|
||||
if (errors) {
|
||||
console.error('There were errors, please check the output.');
|
||||
process.exit(1);
|
||||
console.log(`Done: ${langCount} written, ${skipCount} skipped, ${errors} errors`);
|
||||
if (errors) {
|
||||
console.error('There were errors, please check the output.');
|
||||
process.exit(1);
|
||||
}
|
||||
fs.writeFileSync('docs/translations/meta.json', JSON.stringify(meta, null, 2));
|
||||
}
|
||||
fs.writeFileSync('docs/translations/meta.json', JSON.stringify(meta, null, 2));
|
||||
};
|
||||
|
||||
if (require.main === module) {
|
||||
module.exports();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
const fs = require('fs');
|
||||
const signer = require('pkcs15-smartcard-sign');
|
||||
const keychain = require('keychain');
|
||||
|
||||
const verifyKey = fs.readFileSync('keys/public-key.pem');
|
||||
|
||||
function getPin() {
|
||||
if (getPin.pin) {
|
||||
return Promise.resolve(getPin.pin);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
keychain.getPassword({ account: 'keeweb', service: 'keeweb.pin', type: 'generic' }, (err, pass) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
getPin.pin = pass;
|
||||
resolve(pass);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function sign(data) {
|
||||
return getPin().then(pin => signer.sign({ data, verifyKey, pin }).then(data => data.toString('base64')));
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
(async function() {
|
||||
await require('./download-translations')();
|
||||
require('./update-plugins');
|
||||
})();
|
|
@ -1,14 +1,13 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const sign = require('./sign');
|
||||
|
||||
console.log('Welcome to plugins updater');
|
||||
|
||||
console.log('Loading...');
|
||||
|
||||
const data = JSON.parse(fs.readFileSync('docs/plugins.json', 'utf8'));
|
||||
const privateKey = fs.readFileSync('keys/private-key.pem', 'binary');
|
||||
|
||||
data.signature = '';
|
||||
data.date = '';
|
||||
|
@ -54,14 +53,13 @@ data.date = new Date().toISOString();
|
|||
|
||||
console.log('Signing...');
|
||||
|
||||
const dataToSign = JSON.stringify(data, null, 2);
|
||||
const dataToSign = Buffer.from(JSON.stringify(data, null, 2));
|
||||
|
||||
const sign = crypto.createSign('RSA-SHA256');
|
||||
sign.write(new Buffer(dataToSign));
|
||||
sign.end();
|
||||
|
||||
data.signature = sign.sign(privateKey).toString('base64');
|
||||
|
||||
fs.writeFileSync('docs/plugins.json', JSON.stringify(data, null, 2));
|
||||
|
||||
console.log('Done');
|
||||
sign(dataToSign).then(signature => {
|
||||
data.signature = signature.toString('base64');
|
||||
fs.writeFileSync('docs/plugins.json', JSON.stringify(data, null, 2));
|
||||
console.log('Done');
|
||||
}).catch(err => {
|
||||
console.error('Sign error', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue