Skip to content

Commit

Permalink
Hardware Key support added. closes 0perationPrivacy#88
Browse files Browse the repository at this point in the history
  • Loading branch information
0perationPrivacy committed Feb 12, 2022
1 parent 365409e commit 7cb04f7
Show file tree
Hide file tree
Showing 56 changed files with 1,838 additions and 278 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
> Note: Every update deletes the MMS media files/folder
---
## v0.85 (Feb 12, 2022)
### Feature
- Hardware Key support added. You can add multiple security keys that support U2F (e.g., Yubikey, OnlyKey, Titan, etc).

## v0.84 (Jan 16, 2022)
### Bug
- fixed issue #107, #110, email settings save issue, custom APPDIRECTORY caching issue.
Expand Down
318 changes: 255 additions & 63 deletions app/controller/hardwarekey.controller.js
Original file line number Diff line number Diff line change
@@ -1,78 +1,270 @@
const U2F = require("u2f");
const base64url = require('base64url');
var baseUrl = process.env.BASE_URL.trim()
const APP_ID = baseUrl.substr(0, baseUrl.length - 1);
var Hardwarekey = require('../model/hardwarekey.model');
var User = require('../model/user.model');
var Handel = require('../model/handel.model');

var sessData = {};
exports.registerSession = async (req, res) => {
var session = U2F.request(APP_ID);
console.log('====================================registerSession=====================')
console.log(session)
console.log('====================================/registerSession=====================')
res.send(session);
console.log(req.user);
var payload = req.body;
var userexists = await userExists(payload.title, req.user.id);
var getuser = await Hardwarekey.findOne({title: payload.title, user: req.user.id, id: sessData.id});
// console.log(getuser)
if(userexists && getuser && getuser.registrationComplete){
res.status(400).send({'status': 'false', 'message': 'Title already exists!'});
}else{
await deleteUser(payload.title, req.user.id);
payload.id = base64url.encode(await generateRandomBuffer(32));
payload.credentials = [];
var user = await addUser(payload.title, payload, req.user.id);
console.log(user);
sessData = req.session;
sessData.title = payload.title;
sessData.user = req.user.id;
sessData.id = payload.id;
res.send({'status': 'startFIDOEnrolment'});
}
}
exports.register = async (req, res) => {
console.log('register key')
console.log(req.body.result)
/* var hardwarekey = Hardwarekey.findOne({registeredKeys: req.body.result.registeredKeys})
if(!hardwarekey) { */
var registration = U2F.checkRegistration(req.body.result, req.body.registerResponse);
console.log('====================================registration response=====================')
console.log(registration)
console.log('====================================/registration response=====================')
if(!registration.successful) {
return res.status(400).send({ message: "The hardware key has not been registered" });
}

await User.updateOne({_id:{$eq:req.user.id}},{hardwarekey:'true'})
var keyFound = await Hardwarekey.findOne({user: {$eq: req.user.id}})
if(keyFound){
keyFound.keyhandle = registration.keyHandle;
keyFound.publickey = registration.publicKey;
keyFound.title = req.body.title,
keyFound.registeredKeys = req.body.result.registeredKeys
await keyFound.save()
}else{
var data = {
keyhandle: registration.keyHandle,
publickey: registration.publicKey,
user: req.user.id,
title:req.body.title,
registeredKeys: req.body.result.registeredKeys
}
await Hardwarekey.create(data)
}
res.send({ message: "The hardware key has been registered"});
/* }else{
res.send({ message: "The hardware key has not been registered (unique)"})
} */
if(!sessData.title){
res.status(400).send({'status': 'failed', 'message': 'Access denied!'});
return;
}
let user = await getUser(sessData.title, sessData.user);
var userData = await User.findOne({_id: sessData.user});
sessData.challenge = base64url.encode(await generateRandomBuffer(32));
var publicKey = {
challenge: sessData.challenge,
'rp': {
'name': 'Operation Privacy'
},
'user': {
'id': user.id,
'name': userData.email,
'displayName': userData.name
},
'pubKeyCredParams': [
{ 'type': 'public-key', 'alg': -7 },
{ 'type': 'public-key', 'alg': -257 },
],
'attestation': 'direct'
};
if(req.body.options) {
var options = req.body.options
if(!publicKey.authenticatorSelection)
publicKey.authenticatorSelection = {};

if(options.attestation)
publicKey.attestation = options.attestation;

if(options.rpId)
publicKey.rp.id = options.rpId;

if(options.uv)
publicKey.authenticatorSelection.userVerification = 'required';
}

if(sessData.rk) {
if(!publicKey.authenticatorSelection)
publicKey.authenticatorSelection = {};

publicKey.authenticatorSelection.requireResidentKey = true;
}
var hardwarekey = await Hardwarekey.find({user: req.user.id, registrationComplete:true});
res.send({publicKey:publicKey, hardwarekey:hardwarekey});
}

exports.verify = async (req, res) => {
var payload = req.body;

if(!sessData.title){
return res.status(400).send({'status': 'false', message:'Access denied!', 'errorMessage': 'Access denied!'});
}
// var checkKey = await Hardwarekey.findOne({user: sessData.user, aaguid: payload.aaguid});
// if(checkKey){
// var handel = await Handel.deleteOne({user: sessData.user, username : sessData.title});
// console.log(handel);
// sessData = {};
// return res.status(400).json({status:'false',message:'Harware key already exits!'});
// }
let user = await getUser(sessData.title, sessData.user);
var cr = user.credentials;
cr.push(payload.id);
var updateData = {
registrationComplete: true,
credentials: cr,
aaguid: payload.aaguid
};
let updateuser = await updateUser(sessData.title, sessData.user, updateData);
if(updateuser.registrationComplete == true){
await User.updateOne({_id: sessData.user}, {hardwarekey: 'true'});
}
console.log(updateuser)
sessData = {};
res.send({'status': 'ok'});
};

exports.loginSession = async (req, res) => {
var hardwarekey = await Hardwarekey.findOne({user: {$eq: req.body.user}})
/*if(hardwarekey){
var keyHandle = hardwarekey.keyhandle
var payload = req.body
var userexit = await userExists(payload.title, payload.user)
if(!userexit){
res.status(400).send({status: 'error', message: 'Wrong username or password!'});
return;
}else{
var keyHandle = 'demo'
}*/
var keyHandle = 'demo'
var loginSession = U2F.request(APP_ID, keyHandle)
console.log('====================================login session=====================')
console.log(loginSession)
console.log('====================================/login session=====================')
res.send(loginSession)
sessData.title = payload.title;
sessData.user = payload.user;
}
sessData.challenge = base64url.encode(await generateRandomBuffer(32));
var publicKey = {
'challenge': sessData.challenge,
'status': 'ok'
}
let user = await getUser(sessData.title, sessData.user);
console.log(user);
publicKey.allowCredentials = user.credentials.map((credId) => {
return { 'type': 'public-key', 'id': credId }
})

if(sessData.rk) {
delete publicKey.allowCredentials
}

if(sessData.uv) {
publicKey.userVerification = 'required';
}
res.send(publicKey);
}

function preformatGetAssertReq (getAssert) {
getAssert.challenge = base64url.decode(getAssert.challenge)
if (getAssert.allowCredentials) {
for (let allowCred of getAssert.allowCredentials) {
allowCred.id = base64url.decode(allowCred.id)
}
}
return getAssert
}

exports.login = async (req, res) => {
var hardwarekey = await Hardwarekey.findOne({user: {$eq: req.body.user}})
console.log('====================================/database=====================')
console.log(hardwarekey)
console.log('====================================/database=====================')
var success = U2F.checkSignature(req.body.result, req.body.loginResponse, hardwarekey.publickey);
if(!success.successful) {
return res.status(400).send({ message: "Unauthorized access" });
}
console.log('====================================login success=====================')
console.log(success)
console.log('====================================/login success=====================')
res.send(success);
}
var payload = req.body
var userwhere = sessData.user
var checkHandel = await getUserByUserHandle(payload.response.userHandle, userwhere);
if(!sessData.title && !checkHandel){
res.status(400).send({'status': 'false', message: 'Something is wrong!'});
}else{
sessData = {};
res.send({'status': 'true'});
}
}

exports.getKey = async (req, res) => {
var harewarekeys = await Hardwarekey.find({user:req.user.id, registrationComplete: true});
res.send({status:'true', message:'hardware key list!', data:harewarekeys});
}
exports.delete = async (req, res) => {
var harewarekey = await Hardwarekey.findOne({_id: req.body.id});
if(harewarekey){
await Handel.deleteOne({username: harewarekey.title, user: harewarekey.user});
await harewarekey.delete()
}
var harewarekeys = await Hardwarekey.findOne({user:req.user.id, registrationComplete: true});
if(!harewarekeys){
await User.updateOne({_id: req.user.id}, {hardwarekey: 'false'});
}
res.send({status:'true', message:'hardware key deleted!', data:[]});
}

async function getUserByUserHandle(userHandle, userwhere) {
try {
var user = await Handel.findOne({id:userHandle});
if(user){
userwhere.title = user.username;
let userJSON = Hardwarekey.findOne(userwhere);
if(!userJSON)
throw new Error(`Username "${user.username}" does not exist!`);

return userJSON;
}else{
return false;
}
} catch(e) {
return {}
}
};

async function generateRandomBuffer(length) {
if(!length)
length = 32;

//var randomBuff = new Uint32Array(length);
let randomBuff = new Uint8Array(length);
var getRandomValues = require('get-random-values');
getRandomValues(randomBuff);
//var crypto = require('crypto');
//console.log(crypto.getRandomValues(a));
return randomBuff
};

async function addUser(username, struct, user){
var handel = await Handel.create({id:struct.id, username:username, user:user});
sessData.handelId = handel._id
struct.user = user
await Hardwarekey.create(struct);
return true;
}

async function deleteUser(title, user){
await Hardwarekey.deleteOne({title: title, user: user});
return true;
}

async function userExists(title, user) {
var user = await Hardwarekey.findOne({title: title, user: user});
if(!user){
return false;
}
return true;
};

async function getUser(title, user){
var user = await Hardwarekey.findOne({title: title, user: user});
if(user){
return user;
}else{
return false;
}
};

async function updateUser(title, user, struct){
console.log("=====================================================")
console.log("session title => "+title)
console.log("session user => "+user)
console.log(struct)
var user = await Hardwarekey.findOne({title: title, user: user});
if(user){
user.registrationComplete = struct.registrationComplete;
user.credentials = struct.credentials;
user.aaguid = struct.aaguid;
await user.save();
// var user2 = await Hardwarekey.updateOne({ title: title, user: user}, struct);
return user;
}else{
return false;
}
};

async function generateRandomBuffer(length) {
if(!length)
length = 32;

//var randomBuff = new Uint32Array(length);
let randomBuff = new Uint8Array(length);
var getRandomValues = require('get-random-values');
getRandomValues(randomBuff);
//var crypto = require('crypto');
//console.log(crypto.getRandomValues(a));
return randomBuff
};
2 changes: 1 addition & 1 deletion app/controller/setting.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ exports.smsStatus = async (req, res) => {
if(settingCheck){
if(settingCheck.type === 'twilio'){
const client = twilio(settingCheck.twilio_sid, settingCheck.twilio_token);
client.messages(sid).remove(); //remove Twilio sms from server right after sent with any status reply state
await client.messages(sid).remove(); //remove Twilio sms from server right after sent with any status reply state
}
}
}
Expand Down
28 changes: 20 additions & 8 deletions app/controller/user.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const moment = require('moment')

var User = require('../model/user.model');
const nodemailer = require('nodemailer');

var Hardwarekey = require('../model/hardwarekey.model');
var Contact = require('../model/contact.model');
var Email = require('../model/email.model');
var Message = require('../model/message.model');
Expand Down Expand Up @@ -40,15 +40,27 @@ exports.login = async (req, res) => {
var token = jwt.sign(obj, process.env.COOKIE_KEY);
user.token = token;
user.save();
//if(user.mfa && user.mfa === 'true'){
/* if(user.hardwarekey && user.hardwarekey === 'true'){
res.send({status:'hardwarekey', message:'user data!', data:user, token:token});
} */if(user.mfa && user.mfa === 'true'){
res.send({status:'mfa', message:'user data!', data:user, token:token});
var status = 'true';
var harwarekey, mfa;
if(user.hardwarekey && user.hardwarekey === 'true'){
var mfa = false
if(user.mfa && user.mfa === 'true') {
mfa = true;
}
status = 'hardwarekey';
// harwarekey = false
harwarekey = await Hardwarekey.find({ user:user._id, registrationComplete: true });
// res.send({status:'hardwarekey', message:'user data!', data:user, token:token, harwarekey:harwarekey, mfa:mfa});
} else
if(user.mfa && user.mfa === 'true'){
status = 'mfa';
harwarekey = false
mfa=true
}else{
res.send({status:'true', message:'user data!', data:user, token:token});
harwarekey = false;
mfa = false;
}

res.send({status:status, message:'user data!', data:user, token:token, harwarekey:harwarekey, mfa:mfa});
return;
}else{
res.status(401).json({status:'false',message:'Unauthorized Access!'});
Expand Down
Loading

0 comments on commit 7cb04f7

Please sign in to comment.