Compare commits
7 Commits
de7bd68872
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d9705577c | ||
|
|
04d7675c20 | ||
|
|
a05e1ba48e | ||
|
|
19dd5a2a9d | ||
|
|
385a4cedbb | ||
|
|
4d16737b23 | ||
|
|
0a17e78fdc |
@@ -28,48 +28,59 @@ module.exports.handler = (argv) => {
|
||||
persistence().LoadDB()
|
||||
.then(db => {
|
||||
// TODO validate lock definition has all the parameters we need
|
||||
// TODO: Verify challenge comes from a service we know...
|
||||
return db.ExtendedPublicKey.findOne({
|
||||
where: {
|
||||
xpub: challenge.xpub
|
||||
},
|
||||
include: [
|
||||
{
|
||||
association: db.ExtendedPublicKey.Key // TODO: update this to wallet
|
||||
// TODO: validate serviceExtendedPublicKey
|
||||
const verification = cryptoUtil.verifyChallenge(
|
||||
registerationMessage.serviceExtendedPublicKey,
|
||||
challenge
|
||||
);
|
||||
if(verification) {
|
||||
return db.ExtendedPublicKey.findOne({
|
||||
where: {
|
||||
xpub: challenge.xpub
|
||||
},
|
||||
include: [
|
||||
{
|
||||
association: db.ExtendedPublicKey.Key // TODO: update this to wallet
|
||||
}
|
||||
]
|
||||
}).then(extendedPublicKey => {
|
||||
if (extendedPublicKey) {
|
||||
const encryptedKey = extendedPublicKey.key.encryptedPrivateKey;
|
||||
const iv = extendedPublicKey.key.iv;
|
||||
const password = "vanished";
|
||||
const walletXpriv = cryptoUtil.decrypt(encryptedKey, password, iv);
|
||||
|
||||
var challengeDerivationPath = `${extendedPublicKey.derivationPath}/${challenge.derivationPath.split("c/")[1]}`;
|
||||
|
||||
const signature = cryptoUtil.signMessage(walletXpriv, challengeDerivationPath, challenge.message)
|
||||
|
||||
|
||||
// TODO: save lock
|
||||
return db.Lock.create({
|
||||
userIdentifier: registerationMessage.userIdentifier,
|
||||
url: registerationMessage.url,
|
||||
serviceExtendedPublicKey: registerationMessage.serviceExtendedPublicKey,
|
||||
signature: signature.toString('hex'),
|
||||
message: challenge.message,
|
||||
extendedPublicKeyId: extendedPublicKey.id
|
||||
})
|
||||
} else {
|
||||
console.error("Sorry we can't create a lock with xpub: ", challenge.xpub);
|
||||
return null;
|
||||
}
|
||||
}).then(lock => {
|
||||
if(lock) {
|
||||
console.log("Lock: ", lock.id);
|
||||
console.log("Signature: ", lock.signature);
|
||||
} else {
|
||||
console.error("Failed to create the lock.");
|
||||
}
|
||||
]
|
||||
}).then(extendedPublicKey => {
|
||||
if (extendedPublicKey) {
|
||||
const encryptedKey = extendedPublicKey.key.encryptedPrivateKey;
|
||||
const password = "vanished";
|
||||
const walletXpriv = cryptoUtil.decrypt(encryptedKey, password);
|
||||
|
||||
var challengeDerivationPath = `${extendedPublicKey.derivationPath}/${challenge.derivationPath.split("c/")[1]}`;
|
||||
|
||||
const signature = cryptoUtil.signMessage(walletXpriv, challengeDerivationPath, challenge.message)
|
||||
|
||||
|
||||
// TODO: save lock
|
||||
return db.Lock.create({
|
||||
userIdentifier: registerationMessage.userIdentifier,
|
||||
url: registerationMessage.url,
|
||||
signature: signature.toString('hex'),
|
||||
message: challenge.message,
|
||||
extendedPublicKeyId: extendedPublicKey.id
|
||||
})
|
||||
} else {
|
||||
console.error("Sorry we can't create a lock with xpub: ", challenge.xpub);
|
||||
return null;
|
||||
}
|
||||
}).then(lock => {
|
||||
if(lock) {
|
||||
console.log("Lock: ", lock.id);
|
||||
console.log("Signature: ", lock.signature);
|
||||
} else {
|
||||
console.error("Failed to create the lock.");
|
||||
}
|
||||
|
||||
})
|
||||
})
|
||||
} else {
|
||||
console.error("Challenge not signed by service");
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
@@ -12,8 +12,8 @@ module.exports.builder = (yargs) => {
|
||||
describe: 'challenge that needs to be signed',
|
||||
type: 'string'
|
||||
})
|
||||
.option('domain-url', {
|
||||
describe: 'override domain url belong to the service making the challenge',
|
||||
.option('url', {
|
||||
describe: 'override url belong to the service making the challenge',
|
||||
type: 'string'
|
||||
})
|
||||
.option('user-identifier', {
|
||||
@@ -28,17 +28,17 @@ module.exports.handler = (argv) => {
|
||||
const challenge = JSON.parse(argv.challenge);
|
||||
// TODO get userIdentifier and domainUrl from challenge
|
||||
const loginRequest = JSON.parse(challenge.message);
|
||||
const domainUrl = argv.domainUrl || loginRequest.domainUrl;
|
||||
const url = argv.url || loginRequest.url;
|
||||
const userIdentifier = argv.userIdentifier || loginRequest.userIdentifier;
|
||||
persistence().LoadDB()
|
||||
.then(db => {
|
||||
console.log("Looking for");
|
||||
console.log("url: ", domainUrl);
|
||||
console.log("url: ", url);
|
||||
console.log("userIdentifier: ", userIdentifier);
|
||||
|
||||
return db.Lock.findOne({
|
||||
where: {
|
||||
url: domainUrl,
|
||||
url: url,
|
||||
userIdentifier: userIdentifier
|
||||
},
|
||||
include: [
|
||||
@@ -58,8 +58,9 @@ module.exports.handler = (argv) => {
|
||||
if(lock) {
|
||||
// Sign the message with the key that corresponds with this lock...
|
||||
const encryptedKey = lock.extendedPublicKey.key.encryptedPrivateKey;
|
||||
const iv = lock.extendedPublicKey.key.iv;
|
||||
const password = "vanished";
|
||||
const walletXpriv = cryptoUtil.decrypt(encryptedKey, password);
|
||||
const walletXpriv = cryptoUtil.decrypt(encryptedKey, password, iv);
|
||||
|
||||
var challengeDerivationPath = `${lock.extendedPublicKey.derivationPath}/${challenge.derivationPath.split("c/")[1]}`;
|
||||
|
||||
|
||||
@@ -19,20 +19,17 @@ module.exports.handler = (argv) => {
|
||||
|
||||
// TODO: validate user backed up mnemonic
|
||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
||||
console.log("Seed: ", seed.toString('hex'))
|
||||
const node = bip32.fromSeed(seed);
|
||||
console.log("xpriv: ", node.toBase58());
|
||||
|
||||
// TODO: Get user key password
|
||||
const password = "vanished";
|
||||
// TODO: Encrypt text securely...
|
||||
const encryptedPrivateKey = cryptoUtil.encrypt(node.toBase58(), password);
|
||||
|
||||
console.log("Encrypted Private Key: ", encryptedPrivateKey);
|
||||
|
||||
// Check if a key exist...
|
||||
db.Key.create({
|
||||
encryptedPrivateKey: encryptedPrivateKey,
|
||||
encryptedPrivateKey: encryptedPrivateKey.cipherText,
|
||||
iv: encryptedPrivateKey.iv,
|
||||
name: "FirstKey"
|
||||
}).then(key => {
|
||||
console.log("Successfully created: ", key.name)
|
||||
|
||||
@@ -22,19 +22,14 @@ module.exports.handler = (argv) => {
|
||||
// TODO: Load password from config...
|
||||
if(key) {
|
||||
const password = "vanished";
|
||||
|
||||
console.log("Encrypted: ", key.encryptedPrivateKey);
|
||||
|
||||
const decryptedPrivateKey = cryptoUtil.decrypt(key.encryptedPrivateKey, password);
|
||||
console.log("Decrypted: ", decryptedPrivateKey);
|
||||
const masterNode = bip32.fromBase58(decryptedPrivateKey);
|
||||
const iv = key.iv;
|
||||
|
||||
console.log("Node: ", masterNode.toBase58());
|
||||
const decryptedPrivateKey = cryptoUtil.decrypt(key.encryptedPrivateKey, password, iv);
|
||||
const masterNode = bip32.fromBase58(decryptedPrivateKey);
|
||||
|
||||
const derivationPath = `m/${cryptoUtil.randomDerivationPath(true)}`
|
||||
|
||||
const xpubNode = masterNode.derivePath(derivationPath);
|
||||
console.log("Xpub: ", xpubNode.neutered().toBase58());
|
||||
|
||||
// TODO save xpub and derivation path in db...
|
||||
return db.ExtendedPublicKey.create({
|
||||
|
||||
61
lib/commands/sign.js
Normal file
61
lib/commands/sign.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const registrationLogic = require('../core/logic/register');
|
||||
const authenticationLogic = require('../core/logic/authentication');
|
||||
|
||||
|
||||
module.exports.description = "Sign message/challenge."
|
||||
|
||||
module.exports.builder = (yargs) => {
|
||||
return yargs
|
||||
.usage(`Usage: $0 sign [options]`)
|
||||
.help(false)
|
||||
.version(false)
|
||||
.option('challenge', {
|
||||
describe: 'Challenge to sign',
|
||||
type: 'string'
|
||||
})
|
||||
.option('p', {
|
||||
describe: 'will post signature to response endpoint',
|
||||
type: 'boolean'
|
||||
})
|
||||
.demandOption(['challenge'])
|
||||
.argv;
|
||||
}
|
||||
|
||||
module.exports.handler = (argv) => {
|
||||
console.log("Sign challenge.");
|
||||
|
||||
const challengeArgument = argv["challenge"].js || argv["challenge"];
|
||||
|
||||
const challenge = JSON.parse(challengeArgument);
|
||||
// Parse Action
|
||||
try {
|
||||
const request = JSON.parse(challenge.message);
|
||||
|
||||
if(request.action != undefined) {
|
||||
switch(request.action) {
|
||||
case "register":
|
||||
// Ask user if they want to register with service
|
||||
registrationLogic.registration(challenge, request);
|
||||
break;
|
||||
case "authenticate":
|
||||
// Ask user if they want to authenticate with service
|
||||
authenticationLogic.authenticate(challenge, request, argv)
|
||||
break;
|
||||
default:
|
||||
// Tell user that action is unsupported
|
||||
// ask if they want to sign regardless if they want to continue any way.
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// No action provided... should just sign
|
||||
console.log("No action required.");
|
||||
}
|
||||
// TODO: Validate lock-definition is of the correct format...
|
||||
|
||||
} catch(e) {
|
||||
// Should just be signed...
|
||||
console.error("challenge.messsage isn't json Object")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,21 +1,24 @@
|
||||
const crypto = require('crypto');
|
||||
const algorithm = 'aes-256-ecb';
|
||||
const algorithm = 'aes256';
|
||||
const bip32 = require('bip32');
|
||||
|
||||
// NOTE I'm not a cryptographer... so don't be reusing any code from below as I don't have no idea what I'm doing
|
||||
|
||||
const iv = null; // If I knew what I was doing I would have a value assigned here...
|
||||
|
||||
module.exports.encrypt = (plainText, password) => {
|
||||
const iv = crypto.randomBytes(16).toString('hex').slice(0, 16); // TODO figure out this iv stuff...
|
||||
|
||||
const key = crypto.createHash('sha256').update(password, 'utf8').digest();
|
||||
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
||||
|
||||
var cipherText = cipher.update(plainText, 'utf8', 'hex');
|
||||
cipherText += cipher.final('hex');
|
||||
return cipherText;
|
||||
return {
|
||||
cipherText: cipherText,
|
||||
iv: iv
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.decrypt = (cipherText, password) => {
|
||||
module.exports.decrypt = (cipherText, password, iv) => {
|
||||
const key = crypto.createHash('sha256').update(password, 'utf8').digest();
|
||||
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
||||
|
||||
@@ -36,6 +39,17 @@ module.exports.randomDerivationPath = function(hardenedDerivation) {
|
||||
return randomNumbers.join('/')
|
||||
}
|
||||
|
||||
module.exports.verifyChallenge = function(serviceXpub, challenge) {
|
||||
const requestSignature = challenge.request.signature;
|
||||
const requestDerivationPath = challenge.request.signatureDerivationPath;
|
||||
return this.verifyMessage(
|
||||
serviceXpub,
|
||||
requestDerivationPath,
|
||||
JSON.stringify(this.unsignedChallenge(challenge)),
|
||||
requestSignature
|
||||
);
|
||||
}
|
||||
|
||||
module.exports.verifyMessage = function(base58Key, derivationPath, message, signature) {
|
||||
if(derivationPath.startsWith("a/")) {
|
||||
derivationPath = derivationPath.split("a/")[1];
|
||||
@@ -51,10 +65,20 @@ module.exports.verifyMessage = function(base58Key, derivationPath, message, sign
|
||||
return verificationNode.verify(hash, Buffer.from(signature, 'hex'));
|
||||
}
|
||||
|
||||
module.exports.unsignedChallenge = function(challenge) {
|
||||
return {
|
||||
// keys must be alphabetically ordered
|
||||
derivationPath: challenge.derivationPath,
|
||||
message: challenge.message,
|
||||
xpub: challenge.xpub
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.signMessage = function(base58Key, derivationPath, message) {
|
||||
let signingNode = bip32.fromBase58(base58Key).derivePath(derivationPath);
|
||||
|
||||
var hash = crypto.createHash('sha256').update(message, 'utf8').digest();
|
||||
|
||||
return signingNode.sign(hash);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
68
lib/core/logic/authentication.js
Normal file
68
lib/core/logic/authentication.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const persistence = require("../persistence/persistence");
|
||||
const cryptoUtil = require("../cryptoUtil");
|
||||
const postSignature = require("./post-signature");
|
||||
|
||||
module.exports.authenticate = function(challenge, authenticationRequest, argv) {
|
||||
persistence().LoadDB()
|
||||
.then(db => {
|
||||
const url = argv.url || authenticationRequest.url;
|
||||
const userIdentifier = argv.userIdentifier || authenticationRequest.userIdentifier;
|
||||
// TODO validate lock definition has all the parameters we need
|
||||
// TODO: validate serviceExtendedPublicKey
|
||||
console.log("Looking for");
|
||||
console.log("url: ", url);
|
||||
console.log("userIdentifier: ", userIdentifier);
|
||||
|
||||
return db.Lock.findOne({
|
||||
where: {
|
||||
url: url,
|
||||
userIdentifier: userIdentifier
|
||||
},
|
||||
include: [
|
||||
{
|
||||
association: db.Lock.ExtendedPublicKey,
|
||||
require: true,
|
||||
include: [
|
||||
{
|
||||
association: db.ExtendedPublicKey.Key, // TODO rename key to wallet
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
.then(lock => {
|
||||
if(lock) {
|
||||
// Sign the message with the key that corresponds with this lock...
|
||||
const verification = cryptoUtil.verifyChallenge(
|
||||
lock.serviceExtendedPublicKey,
|
||||
challenge
|
||||
);
|
||||
|
||||
if (verification) {
|
||||
const encryptedKey = lock.extendedPublicKey.key.encryptedPrivateKey;
|
||||
const iv = lock.extendedPublicKey.key.iv;
|
||||
const password = "vanished";
|
||||
const walletXpriv = cryptoUtil.decrypt(encryptedKey, password, iv);
|
||||
|
||||
var challengeDerivationPath = `${lock.extendedPublicKey.derivationPath}/${challenge.derivationPath.split("c/")[1]}`;
|
||||
|
||||
const signature = cryptoUtil.signMessage(walletXpriv, challengeDerivationPath, challenge.message).toString('hex');
|
||||
console.log("Signature: ", signature);
|
||||
|
||||
if(argv.p) {
|
||||
postSignature.postSignature(authenticationRequest.responseEndpoint, {
|
||||
id: challenge.id,
|
||||
signature: signature
|
||||
})
|
||||
}
|
||||
} else {
|
||||
console.error("Challenge not signed by registered service.");
|
||||
}
|
||||
|
||||
} else {
|
||||
console.error("Failed to find a lock with these parameters");
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
10
lib/core/logic/post-signature.js
Normal file
10
lib/core/logic/post-signature.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const needle = require('needle');
|
||||
module.exports.postSignature = function(endpoint, signature) {
|
||||
needle('post', endpoint, signature, { json: true })
|
||||
.then(response => {
|
||||
console.log("Response: ", response.statusMessage);
|
||||
console.log("Body: ", response.body);
|
||||
}).catch(error => {
|
||||
console.error("Failed to sign request: ", error);
|
||||
})
|
||||
}
|
||||
62
lib/core/logic/register.js
Normal file
62
lib/core/logic/register.js
Normal file
@@ -0,0 +1,62 @@
|
||||
const persistence = require("../persistence/persistence");
|
||||
const cryptoUtil = require("../cryptoUtil");
|
||||
|
||||
module.exports.registration = function(challenge, registerationRequest) {
|
||||
persistence().LoadDB()
|
||||
.then(db => {
|
||||
// TODO validate lock definition has all the parameters we need
|
||||
// TODO: validate serviceExtendedPublicKey
|
||||
const verification = cryptoUtil.verifyChallenge(
|
||||
registerationRequest.serviceExtendedPublicKey,
|
||||
challenge
|
||||
);
|
||||
if(verification) {
|
||||
return db.ExtendedPublicKey.findOne({
|
||||
where: {
|
||||
xpub: challenge.xpub
|
||||
},
|
||||
include: [
|
||||
{
|
||||
association: db.ExtendedPublicKey.Key // TODO: update this to wallet
|
||||
}
|
||||
]
|
||||
}).then(extendedPublicKey => {
|
||||
if (extendedPublicKey) {
|
||||
const encryptedKey = extendedPublicKey.key.encryptedPrivateKey;
|
||||
const iv = extendedPublicKey.key.iv;
|
||||
const password = "vanished";
|
||||
const walletXpriv = cryptoUtil.decrypt(encryptedKey, password, iv);
|
||||
|
||||
var challengeDerivationPath = `${extendedPublicKey.derivationPath}/${challenge.derivationPath.split("c/")[1]}`;
|
||||
|
||||
const signature = cryptoUtil.signMessage(walletXpriv, challengeDerivationPath, challenge.message)
|
||||
|
||||
|
||||
// TODO: save lock
|
||||
return db.Lock.create({
|
||||
userIdentifier: registerationRequest.userIdentifier,
|
||||
url: registerationRequest.url,
|
||||
serviceExtendedPublicKey: registerationRequest.serviceExtendedPublicKey,
|
||||
signature: signature.toString('hex'),
|
||||
message: challenge.message,
|
||||
extendedPublicKeyId: extendedPublicKey.id
|
||||
})
|
||||
} else {
|
||||
console.error("Sorry we can't create a lock with xpub: ", challenge.xpub);
|
||||
return null;
|
||||
}
|
||||
}).then(lock => {
|
||||
if(lock) {
|
||||
console.log("Lock: ", lock.id);
|
||||
console.log("Signature: ", lock.signature);
|
||||
} else {
|
||||
console.error("Failed to create the lock.");
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
console.error("Challenge not signed by service");
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
@@ -12,6 +12,10 @@ module.exports = function (sequelize, DataTypes, options) {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
iv: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
unique: true
|
||||
|
||||
@@ -16,6 +16,10 @@ module.exports = function (sequelize, DataTypes, options) {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
serviceExtendedPublicKey: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
signature: {
|
||||
// Derivation from the master key...
|
||||
// TODO: Add validation...
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"bip39": "^3.0.2",
|
||||
"config": "^3.2.4",
|
||||
"nanoid": "^2.1.7",
|
||||
"needle": "^2.4.0",
|
||||
"sequelize": "^5.21.2",
|
||||
"sqlite3": "^4.1.0",
|
||||
"yargs": "^15.0.2"
|
||||
|
||||
Reference in New Issue
Block a user