autho 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli/bin.js +42 -35
- package/src/cli/wizards/createSecret.js +19 -10
- package/src/cli/wizards/getEncryptionKey.js +30 -0
- package/src/cli/wizards/getSecret.js +31 -0
- package/src/sdk/cipher.js +29 -13
- package/src/sdk/{config.js → db.js} +5 -4
- package/src/sdk/secrets.js +8 -10
package/package.json
CHANGED
package/src/cli/bin.js
CHANGED
|
@@ -3,42 +3,38 @@
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { prompt, ask } from "./utils.js";
|
|
6
|
-
import
|
|
6
|
+
import DB from "../sdk/db.js";
|
|
7
7
|
import Cipher from "../sdk/cipher.js";
|
|
8
8
|
import createSecret from "./wizards/createSecret.js";
|
|
9
|
+
import getEncryptionKey from "./wizards/getEncryptionKey.js";
|
|
10
|
+
import getSecret from "./wizards/getSecret.js";
|
|
9
11
|
import Secrets from "../sdk/secrets.js";
|
|
10
12
|
import OTP from "../sdk/otp.js";
|
|
11
13
|
|
|
12
14
|
const program = new Command();
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
//type AppOptions = {
|
|
17
|
+
// masterPasswordHash?: string;
|
|
18
|
+
// masterPassword?: string;
|
|
19
|
+
// appFolder?: string;
|
|
20
|
+
// };
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
choices,
|
|
31
|
-
required: true,
|
|
32
|
-
});
|
|
33
|
-
const secrets = new Secrets(config);
|
|
34
|
-
const secret = await secrets.get(secretId);
|
|
35
|
-
|
|
36
|
-
if (!secret) {
|
|
37
|
-
throw new Error("Secret not found");
|
|
22
|
+
class App {
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
let masterPasswordHash =
|
|
25
|
+
options.masterPasswordHash || process.env.AUTHO_MASTER_PASSWORD_HASH;
|
|
26
|
+
const masterPassword = options.masterPassword || process.env.AUTHO_MASTER_PASSWORD;
|
|
27
|
+
if (!masterPasswordHash && !masterPassword) {
|
|
28
|
+
throw new Error("Master password or master password hash is required")
|
|
29
|
+
}
|
|
30
|
+
masterPasswordHash =
|
|
31
|
+
masterPasswordHash ||
|
|
32
|
+
Cipher.hash(masterPassword);
|
|
33
|
+
this.db = new DB({ encryptionKey: masterPasswordHash });
|
|
34
|
+
this.secrets = new Secrets(this);
|
|
38
35
|
}
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
};
|
|
37
|
+
}
|
|
42
38
|
|
|
43
39
|
program
|
|
44
40
|
.name("autho")
|
|
@@ -55,8 +51,8 @@ program
|
|
|
55
51
|
type: "password",
|
|
56
52
|
required: true,
|
|
57
53
|
});
|
|
58
|
-
|
|
59
|
-
const
|
|
54
|
+
|
|
55
|
+
const app = new App({ masterPassword });
|
|
60
56
|
|
|
61
57
|
let choices = [
|
|
62
58
|
{ value: "create", name: "Create new secret" },
|
|
@@ -70,15 +66,25 @@ program
|
|
|
70
66
|
choices,
|
|
71
67
|
required: true,
|
|
72
68
|
});
|
|
69
|
+
|
|
73
70
|
switch (action) {
|
|
74
71
|
case "create":
|
|
75
|
-
await createSecret(
|
|
72
|
+
await createSecret(app);
|
|
76
73
|
break;
|
|
77
74
|
|
|
78
75
|
case "read":
|
|
79
|
-
const readSecret = await getSecret(
|
|
80
|
-
|
|
76
|
+
const readSecret = await getSecret(app);
|
|
77
|
+
let encryptionKey = app.db.encryptionKey
|
|
78
|
+
if (readSecret.protected) {
|
|
79
|
+
encryptionKey = await getEncryptionKey()
|
|
80
|
+
}
|
|
81
81
|
|
|
82
|
+
readSecret.value = Cipher.decrypt(
|
|
83
|
+
readSecret.value,
|
|
84
|
+
readSecret.publicKey,
|
|
85
|
+
encryptionKey,
|
|
86
|
+
readSecret.signature
|
|
87
|
+
);
|
|
82
88
|
switch (readSecret.type) {
|
|
83
89
|
case "password":
|
|
84
90
|
console.log("Username:", readSecret.typeOptions.username);
|
|
@@ -99,12 +105,13 @@ program
|
|
|
99
105
|
|
|
100
106
|
break;
|
|
101
107
|
case "delete":
|
|
102
|
-
const deleteSecret = await getSecret(
|
|
103
|
-
|
|
104
|
-
await secrets.remove(deleteSecret.id)
|
|
108
|
+
const deleteSecret = await getSecret(app);
|
|
109
|
+
await app.secrets.remove(deleteSecret.id);
|
|
105
110
|
console.log("Removed");
|
|
106
111
|
process.exit(0);
|
|
107
|
-
|
|
112
|
+
default:
|
|
113
|
+
console.log("Unknown action:", action);
|
|
114
|
+
process.exit(1);
|
|
108
115
|
}
|
|
109
116
|
} catch (error) {
|
|
110
117
|
console.log(
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { prompt } from "../utils.js";
|
|
2
|
-
import
|
|
2
|
+
import getEncryptionKey from "./getEncryptionKey.js";
|
|
3
3
|
|
|
4
|
-
const wizard = async (
|
|
4
|
+
const wizard = async (app) => {
|
|
5
|
+
const secrets = app.secrets;
|
|
5
6
|
const info = await prompt([
|
|
6
7
|
{
|
|
7
8
|
name: "name",
|
|
@@ -16,10 +17,22 @@ const wizard = async (config, masterPasswordHash) => {
|
|
|
16
17
|
default: "password",
|
|
17
18
|
choices: ["password", "otp", "note"],
|
|
18
19
|
required: true,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: "protected",
|
|
23
|
+
message: "protected:",
|
|
24
|
+
type: "confirm",
|
|
25
|
+
default: false,
|
|
26
|
+
required: true,
|
|
19
27
|
}
|
|
20
28
|
]);
|
|
21
29
|
|
|
22
30
|
let newSecret = {};
|
|
31
|
+
let encryptionKey = app.db.encryptionKey
|
|
32
|
+
|
|
33
|
+
if (info.protected) {
|
|
34
|
+
encryptionKey = await getEncryptionKey(true)
|
|
35
|
+
}
|
|
23
36
|
|
|
24
37
|
switch (info.type) {
|
|
25
38
|
case "password":
|
|
@@ -38,8 +51,7 @@ const wizard = async (config, masterPasswordHash) => {
|
|
|
38
51
|
},
|
|
39
52
|
]);
|
|
40
53
|
newSecret = {
|
|
41
|
-
|
|
42
|
-
type: info.type,
|
|
54
|
+
...info,
|
|
43
55
|
value: password.value,
|
|
44
56
|
typeOptions: {
|
|
45
57
|
username: password.username,
|
|
@@ -56,8 +68,7 @@ const wizard = async (config, masterPasswordHash) => {
|
|
|
56
68
|
},
|
|
57
69
|
]);
|
|
58
70
|
newSecret = {
|
|
59
|
-
|
|
60
|
-
type: info.type,
|
|
71
|
+
...info,
|
|
61
72
|
value: note.value,
|
|
62
73
|
typeOptions: {
|
|
63
74
|
|
|
@@ -80,8 +91,7 @@ const wizard = async (config, masterPasswordHash) => {
|
|
|
80
91
|
},
|
|
81
92
|
]);
|
|
82
93
|
newSecret = {
|
|
83
|
-
|
|
84
|
-
type: info.type,
|
|
94
|
+
...info,
|
|
85
95
|
value: otp.value,
|
|
86
96
|
typeOptions: {
|
|
87
97
|
username: otp.username,
|
|
@@ -90,8 +100,7 @@ const wizard = async (config, masterPasswordHash) => {
|
|
|
90
100
|
break;
|
|
91
101
|
}
|
|
92
102
|
|
|
93
|
-
|
|
94
|
-
await secrets.add(newSecret, masterPasswordHash);
|
|
103
|
+
await secrets.add(newSecret, encryptionKey);
|
|
95
104
|
};
|
|
96
105
|
|
|
97
106
|
export default wizard;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import Cipher from "../../sdk/cipher.js";
|
|
2
|
+
import { prompt } from "../utils.js";
|
|
3
|
+
|
|
4
|
+
const wizard = async (confirm = false) => {
|
|
5
|
+
const questions = [{
|
|
6
|
+
name: "password",
|
|
7
|
+
message: "password:",
|
|
8
|
+
type: "password",
|
|
9
|
+
required: true,
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
if (confirm) {
|
|
13
|
+
questions.push({
|
|
14
|
+
name: "confirmPassword",
|
|
15
|
+
message: "confirm password:",
|
|
16
|
+
type: "password",
|
|
17
|
+
required: true,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const input = await prompt(questions);
|
|
21
|
+
|
|
22
|
+
if (input.confirmPassword && input.password !== input.confirmPassword) {
|
|
23
|
+
throw new Error("Passwords do not match");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return Cipher.hash(input.password);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export default wizard;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { prompt } from "../utils.js";
|
|
2
|
+
|
|
3
|
+
const wizard = async (app) => {
|
|
4
|
+
const existingSecrets = app.db.get("secrets", []);
|
|
5
|
+
|
|
6
|
+
if (existingSecrets.length === 0) {
|
|
7
|
+
throw new Error("No secrets found");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const choices = existingSecrets.map((secret) => ({
|
|
11
|
+
value: secret.id,
|
|
12
|
+
name: `${secret.name} (${secret.typeOptions.username || secret.id})`,
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const { id: secretId } = await prompt({
|
|
16
|
+
name: "id",
|
|
17
|
+
message: "Secrets:",
|
|
18
|
+
type: "list",
|
|
19
|
+
choices,
|
|
20
|
+
required: true,
|
|
21
|
+
});
|
|
22
|
+
const secret = await app.secrets.get(secretId);
|
|
23
|
+
|
|
24
|
+
if (!secret) {
|
|
25
|
+
throw new Error("Secret not found");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return secret;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default wizard;
|
package/src/sdk/cipher.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
2
|
|
|
3
|
-
const algorithm = "aes-256-ctr";
|
|
4
|
-
const IV_LENGTH = 16;
|
|
5
|
-
|
|
6
3
|
export default class Cipher {
|
|
7
|
-
constructor() { }
|
|
8
4
|
|
|
9
|
-
static hash(text) {
|
|
10
|
-
const hash = crypto.createHash(
|
|
5
|
+
static hash(text, algorithm = "sha256") {
|
|
6
|
+
const hash = crypto.createHash(algorithm);
|
|
11
7
|
hash.update(text);
|
|
12
8
|
|
|
13
9
|
return hash.digest("hex");
|
|
14
10
|
}
|
|
15
11
|
|
|
16
|
-
static random(size =
|
|
12
|
+
static random(size = 16) {
|
|
17
13
|
const rnd = crypto.randomBytes(size);
|
|
18
14
|
|
|
19
15
|
return rnd;
|
|
@@ -25,30 +21,50 @@ export default class Cipher {
|
|
|
25
21
|
return rnd;
|
|
26
22
|
}
|
|
27
23
|
|
|
28
|
-
static
|
|
24
|
+
static sign(text) {
|
|
25
|
+
const hash = Cipher.hash(text)
|
|
26
|
+
const signature = `${hash.substring(0, 10)}:${hash.substring(hash.length - 10)}`;
|
|
27
|
+
|
|
28
|
+
return signature;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static verify(text, signature) {
|
|
32
|
+
const expectedSignature = Cipher.sign(text)
|
|
33
|
+
|
|
34
|
+
return expectedSignature === signature;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static encrypt(text, encryptionKey, algorithm = "aes-256-ctr") {
|
|
29
38
|
const publicKey = Cipher.randomString();
|
|
30
39
|
let cipher = crypto.createCipheriv(
|
|
31
40
|
algorithm,
|
|
32
|
-
Buffer.from(
|
|
41
|
+
Buffer.from(encryptionKey, "hex"),
|
|
33
42
|
Buffer.from(publicKey, "hex"),
|
|
34
43
|
);
|
|
35
44
|
let encrypted = cipher.update(text);
|
|
36
45
|
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
|
37
46
|
encrypted = encrypted.toString("hex");
|
|
38
47
|
|
|
39
|
-
return { publicKey, encrypted };
|
|
48
|
+
return { publicKey, encrypted, algorithm, signature: Cipher.sign(text) };
|
|
40
49
|
}
|
|
41
50
|
|
|
42
|
-
static decrypt(encryptedText, publicKey,
|
|
51
|
+
static decrypt(encryptedText, publicKey, encryptionKey, signature, algorithm = "aes-256-ctr") {
|
|
43
52
|
encryptedText = Buffer.from(encryptedText, "hex");
|
|
53
|
+
|
|
44
54
|
let decipher = crypto.createDecipheriv(
|
|
45
55
|
algorithm,
|
|
46
|
-
Buffer.from(
|
|
56
|
+
Buffer.from(encryptionKey, "hex"),
|
|
47
57
|
Buffer.from(publicKey, "hex")
|
|
48
58
|
);
|
|
59
|
+
|
|
49
60
|
let decrypted = decipher.update(encryptedText);
|
|
50
61
|
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
62
|
+
decrypted = decrypted.toString();
|
|
63
|
+
|
|
64
|
+
if (!Cipher.verify(decrypted, signature)) {
|
|
65
|
+
throw new Error("Invalid signature")
|
|
66
|
+
}
|
|
51
67
|
|
|
52
|
-
return decrypted
|
|
68
|
+
return decrypted;
|
|
53
69
|
}
|
|
54
70
|
}
|
|
@@ -2,20 +2,21 @@ import Conf from "conf";
|
|
|
2
2
|
|
|
3
3
|
const { AUTHO_ENCRYPTION_KEY = "", AUTHO_NAME = 'default' } = process.env;
|
|
4
4
|
|
|
5
|
-
export default class
|
|
5
|
+
export default class DB {
|
|
6
6
|
constructor({
|
|
7
7
|
encryptionKey = AUTHO_ENCRYPTION_KEY,
|
|
8
8
|
configName = AUTHO_NAME
|
|
9
9
|
}) {
|
|
10
|
-
this.
|
|
10
|
+
this.encryptionKey = encryptionKey;
|
|
11
|
+
this.client = new Conf({ projectName: "autho", encryptionKey, configName });
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
get(key, defaultValue) {
|
|
14
|
-
return this.
|
|
15
|
+
return this.client.get(key, defaultValue);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
set(key, value) {
|
|
18
|
-
this.
|
|
19
|
+
this.client.set(key, value);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
}
|
package/src/sdk/secrets.js
CHANGED
|
@@ -1,34 +1,32 @@
|
|
|
1
1
|
import { createSecretSchema } from "../models/Secret.js";
|
|
2
|
-
import Cipher from "
|
|
2
|
+
import Cipher from "../sdk/cipher.js";
|
|
3
3
|
|
|
4
4
|
export default class Secrets {
|
|
5
|
-
constructor(
|
|
6
|
-
this.
|
|
5
|
+
constructor(app) {
|
|
6
|
+
this.db = app.db;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
get secrets() {
|
|
10
|
-
return this.
|
|
10
|
+
return this.db.get("secrets", []);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
set secrets(value) {
|
|
14
|
-
this.
|
|
14
|
+
this.db.set("secrets", value);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
async get(id) {
|
|
18
18
|
return this.secrets.find((secret) => secret.id == id);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
async add(secret,
|
|
21
|
+
async add(secret, encryptionKey) {
|
|
22
22
|
const { value, error } = createSecretSchema.validate(secret);
|
|
23
23
|
if (error) {
|
|
24
24
|
throw new Error(error);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const {
|
|
28
|
-
value.value = encrypted;
|
|
29
|
-
value.publicKey = publicKey;
|
|
27
|
+
const { encrypted, ...encryption } = Cipher.encrypt(value.value, encryptionKey);
|
|
30
28
|
|
|
31
|
-
this.secrets = [...this.secrets, value];
|
|
29
|
+
this.secrets = [...this.secrets, { ...value, ...encryption, value: encrypted }];
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
async remove(id) {
|