Skip to content

Commit a0f6d86

Browse files
committed
--save-key option
1 parent 237db45 commit a0f6d86

File tree

1 file changed

+108
-49
lines changed

1 file changed

+108
-49
lines changed

main.js

Lines changed: 108 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -388,10 +388,14 @@ function readPassphrase(passphrase, minEntropy, callback) {
388388
}
389389
}
390390

391-
function publicKeyFromId(id) {
391+
function keyFromId(id) {
392392
return new Uint8Array(Base58.decode(id).slice(0, 32));
393393
}
394394

395+
function keyPairFromSecret(secret) {
396+
return nacl.box.keyPair.fromSecretKey(keyFromId(secret));
397+
}
398+
395399
function validateKey(key) {
396400
if (!key || !(key.length >= 40 && key.length <= 50)
397401
|| !/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/
@@ -435,7 +439,7 @@ function printId(id) {
435439
}
436440
}
437441

438-
function saveId(email, id) {
442+
function saveId(email, id, keyPair) {
439443
var profileDirectory = path.resolve(home(), '.mlck');
440444

441445
try {
@@ -446,11 +450,16 @@ function saveId(email, id) {
446450
}
447451
}
448452

449-
var profile = {
450-
version: '0.1',
451-
email: email,
452-
id: id
453-
};
453+
var profile = { version: '0.1' };
454+
455+
if (keyPair) {
456+
// Store only the secret key. If it's compromised, you have to get a new
457+
// one. No other information is leaked.
458+
profile.secret = miniLockId(keyPair.secretKey);
459+
} else {
460+
profile.email = email;
461+
profile.id = id;
462+
}
454463

455464
fs.writeFileSync(path.resolve(profileDirectory, 'profile.json'),
456465
JSON.stringify(profile));
@@ -493,7 +502,7 @@ function makeHeader(ids, senderInfo, fileInfo) {
493502
debug("Adding recipient " + id);
494503

495504
var nonce = nacl.randomBytes(24);
496-
var publicKey = publicKeyFromId(id);
505+
var publicKey = keyFromId(id);
497506

498507
debug("Using nonce " + hex(nonce));
499508

@@ -544,7 +553,7 @@ function extractDecryptInfo(header, secretKey) {
544553

545554
decryptInfo.fileInfo = nacl.util.decodeBase64(decryptInfo.fileInfo);
546555
decryptInfo.fileInfo = nacl.box.open(decryptInfo.fileInfo, nonce,
547-
publicKeyFromId(decryptInfo.senderID), secretKey);
556+
keyFromId(decryptInfo.senderID), secretKey);
548557

549558
decryptInfo.fileInfo = JSON.parse(
550559
nacl.util.encodeUTF8(decryptInfo.fileInfo)
@@ -618,19 +627,33 @@ function decryptChunk(chunk, decryptor, output, hash) {
618627
}
619628

620629
function encryptFile(ids, email, passphrase, file, outputFile, includeSelf,
621-
anonymous, checkId, callback) {
630+
anonymous, checkId, keyPair, callback) {
622631
debug("Begin file encryption");
623632

624-
if (anonymous) {
625-
// Generate a random passphrase.
626-
email = 'Anonymous';
627-
passphrase = new Buffer(nacl.randomBytes(32)).toString('base64');
628-
}
633+
var keyPairFunc = null;
629634

630-
debug("Generating key pair with email " + email
631-
+ " and passphrase " + passphrase);
635+
if (anonymous || !keyPair) {
636+
if (anonymous) {
637+
// Generate a random passphrase.
638+
email = 'Anonymous';
639+
passphrase = new Buffer(nacl.randomBytes(32)).toString('base64');
640+
}
632641

633-
getKeyPair(passphrase, email, function (keyPair) {
642+
debug("Generating key pair with email " + email
643+
+ " and passphrase " + passphrase);
644+
645+
keyPairFunc = function (callback) {
646+
getKeyPair(passphrase, email, callback);
647+
};
648+
} else {
649+
keyPairFunc = function (callback) {
650+
async(function () {
651+
callback(keyPair);
652+
});
653+
};
654+
}
655+
656+
keyPairFunc(function (keyPair) {
634657
debug("Our public key is " + hex(keyPair.publicKey));
635658
debug("Our secret key is " + hex(keyPair.secretKey));
636659

@@ -821,13 +844,28 @@ function encryptFile(ids, email, passphrase, file, outputFile, includeSelf,
821844
});
822845
}
823846

824-
function decryptFile(email, passphrase, file, outputFile, checkId, callback) {
847+
function decryptFile(email, passphrase, file, outputFile, checkId, keyPair,
848+
callback) {
825849
debug("Begin file decryption");
826850

827-
debug("Generating key pair with email " + email
828-
+ " and passphrase " + passphrase);
851+
var keyPairFunc = null;
829852

830-
getKeyPair(passphrase, email, function (keyPair) {
853+
if (!keyPair) {
854+
debug("Generating key pair with email " + email
855+
+ " and passphrase " + passphrase);
856+
857+
keyPairFunc = function (callback) {
858+
getKeyPair(passphrase, email, callback);
859+
};
860+
} else {
861+
keyPairFunc = function (callback) {
862+
async(function () {
863+
callback(keyPair);
864+
});
865+
};
866+
}
867+
868+
keyPairFunc(function (keyPair) {
831869
debug("Our public key is " + hex(keyPair.publicKey));
832870
debug("Our secret key is " + hex(keyPair.secretKey));
833871

@@ -1023,6 +1061,7 @@ function handleIdCommand() {
10231061
var defaultOptions = {
10241062
'passphrase': null,
10251063
'save': false,
1064+
'save-key': false,
10261065
};
10271066

10281067
var shortcuts = {
@@ -1039,12 +1078,17 @@ function handleIdCommand() {
10391078
var passphrase = options.passphrase;
10401079

10411080
var save = options.save;
1081+
var saveKey = options['save-key'];
10421082

10431083
if (email === undefined) {
10441084
loadProfile();
10451085

1046-
if (profile) {
1047-
printId(profile.id);
1086+
if (profile && (profile.id || profile.secret)) {
1087+
if (profile.id) {
1088+
printId(profile.id);
1089+
} else {
1090+
printId(miniLockId(keyPairFromSecret(profile.secret).publicKey));
1091+
}
10481092
} else {
10491093
console.error('No profile data available.');
10501094
}
@@ -1060,13 +1104,15 @@ function handleIdCommand() {
10601104

10611105
debug("Using passphrase " + passphrase);
10621106

1063-
generateId(email, passphrase, function (error, id) {
1107+
generateId(email, passphrase, function (error, id, keyPair) {
10641108
if (error) {
10651109
logError(error);
10661110
die();
10671111
}
10681112

1069-
if (save) {
1113+
if (saveKey) {
1114+
saveId(email, id, keyPair);
1115+
} else if (save) {
10701116
saveId(email, id);
10711117
}
10721118

@@ -1118,32 +1164,40 @@ function handleEncryptCommand() {
11181164

11191165
loadProfile();
11201166

1121-
if (typeof email !== 'string' && profile) {
1122-
email = profile.email;
1123-
}
1167+
var keyPair = !anonymous && typeof email !== 'string'
1168+
&& profile && profile.secret && keyPairFromSecret(profile.secret);
11241169

1125-
if (!anonymous && typeof email !== 'string') {
1126-
die('Email required.');
1127-
}
1170+
if (!keyPair) {
1171+
if (typeof email !== 'string' && profile) {
1172+
email = profile.email;
1173+
}
1174+
1175+
if (!anonymous && typeof email !== 'string') {
1176+
die('Email required.');
1177+
}
11281178

1129-
if (!anonymous && typeof passphrase !== 'string' && !process.stdin.isTTY) {
1130-
die('No passphrase given; no terminal available.');
1179+
if (!anonymous && typeof passphrase !== 'string' && !process.stdin.isTTY) {
1180+
die('No passphrase given; no terminal available.');
1181+
}
11311182
}
11321183

1133-
var checkId = !anonymous && profile && email === profile.email && profile.id;
1184+
var checkId = !anonymous && !keyPair && profile && email === profile.email
1185+
&& profile.id;
11341186

1135-
readPassphrase(anonymous ? '' : passphrase, 0, function (error, passphrase) {
1187+
readPassphrase(anonymous || keyPair ? '' : passphrase, 0,
1188+
function (error, passphrase) {
11361189
if (error) {
11371190
logError(error);
11381191
die();
11391192
}
11401193

1141-
if (!anonymous) {
1194+
if (!anonymous && !keyPair) {
11421195
debug("Using passphrase " + passphrase);
11431196
}
11441197

11451198
encryptFile(ids, email, passphrase, file, outputFile, includeSelf,
1146-
anonymous, checkId, function (error, keyPair, length, filename) {
1199+
anonymous, checkId, keyPair,
1200+
function (error, keyPair, length, filename) {
11471201
if (error) {
11481202
if (error === ERR_ID_CHECK_FAILED) {
11491203
console.error('Incorrect passphrase for ' + email);
@@ -1196,29 +1250,34 @@ function handleDecryptCommand() {
11961250

11971251
loadProfile();
11981252

1199-
if (typeof email !== 'string' && profile) {
1200-
email = profile.email;
1201-
}
1253+
var keyPair = typeof email !== 'string'
1254+
&& profile && profile.secret && keyPairFromSecret(profile.secret);
12021255

1203-
if (typeof email !== 'string') {
1204-
die('Email required.');
1205-
}
1256+
if (!keyPair) {
1257+
if (typeof email !== 'string' && profile) {
1258+
email = profile.email;
1259+
}
12061260

1207-
if (typeof passphrase !== 'string' && !process.stdin.isTTY) {
1208-
die('No passphrase given; no terminal available.');
1261+
if (typeof email !== 'string') {
1262+
die('Email required.');
1263+
}
1264+
1265+
if (typeof passphrase !== 'string' && !process.stdin.isTTY) {
1266+
die('No passphrase given; no terminal available.');
1267+
}
12091268
}
12101269

1211-
var checkId = profile && email === profile.email && profile.id;
1270+
var checkId = !keyPair && profile && email === profile.email && profile.id;
12121271

1213-
readPassphrase(passphrase, 0, function (error, passphrase) {
1272+
readPassphrase(keyPair ? '' : passphrase, 0, function (error, passphrase) {
12141273
if (error) {
12151274
logError(error);
12161275
die();
12171276
}
12181277

12191278
debug("Using passphrase " + passphrase);
12201279

1221-
decryptFile(email, passphrase, file, outputFile, checkId,
1280+
decryptFile(email, passphrase, file, outputFile, checkId, keyPair,
12221281
function (error, keyPair, length, filename, senderId,
12231282
originalFilename) {
12241283
if (error) {

0 commit comments

Comments
 (0)