diff --git a/README.md b/README.md new file mode 100644 index 0000000..de9b508 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# darkID +Blockchain based anonymous distributed ID system + +### Main concept +The objective is to guarantee a decentralized login system, but making sure that registered users are real ones and there are no bots generating large amounts of accounts. Only the verified (by email or phone) users can generate an anonymous ID (the Public-Key blind signed). + + +![screenshot](https://raw.githubusercontent.com/arnaucode/darkID/master/documentation/screenshot01.png "screenshot") + +![screenshot](https://raw.githubusercontent.com/arnaucode/darkID/master/documentation/screenshot02.png "screenshot") + +## How it works? + + +#### Network infrastructure + +![network](https://raw.githubusercontent.com/arnaucode/darkID/master/documentation/darkID-network.png "network") + + +#### Step by step process +1. Once all the nodes of the network are running, a new user can connect to the server-ID-signer. +2. The user registers a non anonymous user (using email, phone, password, etc), and performs the login with that user +3. The user, locally, generates a RSA key pair (private key & public key) +4. The user blinds his Public-Key with the server-ID-signer Public-Key +5. The user's Public-Key blinded, is sent to the server-ID-signer +6. The server-ID-signer Blind Signs the Public-Key blinded from the user, and returns it to the user +7. The user unblinds the Public-Key signed by the server-ID-signer, and now has the Public-Key Blind Signed by the server-ID-signer +8. The user sends the Public-Key blind signed to the p2p network +9. The peers verify that the Public-Key Blind Signed is correctly signed by the server-ID-signer, if it is, they add the Public-Key to the Ethereum Blockchain, inside a new block +10. Then, when the user wants to login into a platform, just needs to put his Public-Key +11. The platform goes to the Ethereum Blockchain, to check if this Public-Key is registered in the blockchain +12. The platform sends a message encrypted with the user Public-Key, and the user returns the message decrypted with the Private-Key, to verify that is the owner of that Public-Key + + +##### RSA encryption system +https://en.wikipedia.org/wiki/RSA_cryptosystem +- Public parameters: (e, n) +- Private parameters: (d, p, q, phi, sigma) +- Public-Key = (e, n) +- Private-Key = (d, n) +- Encryption: +![rsa](https://wikimedia.org/api/rest_v1/media/math/render/svg/fbfc70524a1ad983e6f3aac51226b9ca92fefb10 "rsa") +- Decryption: +![rsa](https://wikimedia.org/api/rest_v1/media/math/render/svg/10227461ee5f4784484f082d744ba5b8c468668c "rsa") + + +##### Blind signature process +https://en.wikipedia.org/wiki/Blind_signature +- m is the message (in our case, is the Public-Key of the user to be blinded) +![rsa](https://wikimedia.org/api/rest_v1/media/math/render/svg/a59b57fa153c8b327605672caadb0ecf59e5795a "rsa") + +- server-ID-signer blind signs m' +![rsa](https://wikimedia.org/api/rest_v1/media/math/render/svg/e726b003ff1649f9254032cffae42d80577da787 "rsa") + +- user can unblind m, to get m signed +![rsa](https://wikimedia.org/api/rest_v1/media/math/render/svg/e96fad0e1d46ec4c55986d1c8fc84e8c44259ecc "rsa") + +- This works because RSA keys satisfy this equation +![rsa](https://wikimedia.org/api/rest_v1/media/math/render/svg/d6bd21fb4e25c311df07b50c313a248d978c3212 "rsa") and this ![rsa](https://wikimedia.org/api/rest_v1/media/math/render/svg/c13170a26e031125b417f22644fb64384c04eea7 "rsa") diff --git a/clientApp/.gitignore b/clientApp/.gitignore new file mode 100644 index 0000000..34afe36 --- /dev/null +++ b/clientApp/.gitignore @@ -0,0 +1 @@ +keys.json diff --git a/clientApp/GUI/.bowerrc b/clientApp/GUI/.bowerrc new file mode 100644 index 0000000..baa91a3 --- /dev/null +++ b/clientApp/GUI/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "bower_components" +} \ No newline at end of file diff --git a/clientApp/GUI/.gitignore b/clientApp/GUI/.gitignore new file mode 100644 index 0000000..7bf6eb1 --- /dev/null +++ b/clientApp/GUI/.gitignore @@ -0,0 +1,2 @@ +bower_components +node_modules diff --git a/clientApp/GUI/app.js b/clientApp/GUI/app.js new file mode 100644 index 0000000..f7c57b5 --- /dev/null +++ b/clientApp/GUI/app.js @@ -0,0 +1,84 @@ +'use strict'; + +//var urlapi = "http://127.0.0.1:3130/"; +var clientapi = "http://127.0.0.1:4100/"; + +// Declare app level module which depends on views, and components +angular.module('app', [ + 'ngRoute', + 'ngMessages', + 'angularBootstrapMaterial', + 'ui.bootstrap', + 'toastr', + 'app.navbar', + 'app.main', + 'app.signup', + 'app.login' +]). +config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) { + $locationProvider.hashPrefix('!'); + + if ((localStorage.getItem('darkID_token'))) { + console.log(window.location.hash); + if ((window.location.hash === '#!/login') || (window.location.hash === '#!/signup')) { + window.location = '#!/main'; + } + + $routeProvider.otherwise({ + redirectTo: '/main' + }); + } else { + if ((window.location !== '#!/login') || (window.location !== '#!/signup')) { + console.log('app, user no logged'); + + localStorage.removeItem('darkID_token'); + localStorage.removeItem('darkID_userdata'); + window.location = '#!/login'; + $routeProvider.otherwise({ + redirectTo: '/login' + }); + } + } + }]) + .config(function(toastrConfig) { + angular.extend(toastrConfig, { + autoDismiss: false, + containerId: 'toast-container', + maxOpened: 0, + newestOnTop: true, + positionClass: 'toast-bottom-right', + preventDuplicates: false, + preventOpenDuplicates: false, + target: 'body' + }); + }) + .factory('httpInterceptor', function httpInterceptor() { + return { + request: function(config) { + return config; + }, + + requestError: function(config) { + return config; + }, + + response: function(res) { + return res; + }, + + responseError: function(res) { + return res; + } + }; + }) + .factory('api', function($http) { + return { + init: function() { + /*$http.defaults.headers.common['X-Access-Token'] = localStorage.getItem('block_webapp_token'); + $http.defaults.headers.post['X-Access-Token'] = localStorage.getItem('block_webapp_token');*/ + } + }; + }) + .run(function(api) { + api.init(); + }); diff --git a/clientApp/GUI/bower.json b/clientApp/GUI/bower.json new file mode 100644 index 0000000..ce65efb --- /dev/null +++ b/clientApp/GUI/bower.json @@ -0,0 +1,18 @@ +{ + "name": "darkID-clientApp", + "description": "", + "version": "0.0.0", + "homepage": "", + "license": "MIT", + "private": true, + "dependencies": { + "angular": "^1.6.2", + "angular-route": "^1.6.1", + "angular-messages": "^1.6.5", + "angular-bootstrap-material": "abm#^0.1.4", + "angular-bootstrap": "^2.5.0", + "components-font-awesome": "^4.7.0", + "angular-toastr": "^2.1.1", + "cssMaterialColors": "*" + } +} diff --git a/clientApp/GUI/css/bootstrapMaterial-dark-overwrite.css b/clientApp/GUI/css/bootstrapMaterial-dark-overwrite.css new file mode 100644 index 0000000..259f10a --- /dev/null +++ b/clientApp/GUI/css/bootstrapMaterial-dark-overwrite.css @@ -0,0 +1,14 @@ +body { + /*background: #15191e!important;*/ + background: #000000!important; + color: #ffffff!important; +} +.card { + /*background: #1f262d!important;*/ + /*background: #15191e!important; + color: #ffffff!important;*/ + /*border: 1px solid #ffffff!important;*/ + + background: #000000!important; + color: #ffffff!important; +} diff --git a/clientApp/GUI/css/own.css b/clientApp/GUI/css/own.css new file mode 100644 index 0000000..2a2678b --- /dev/null +++ b/clientApp/GUI/css/own.css @@ -0,0 +1,5 @@ +.o_nav { + background: #000000!important; + color: #ffffff!important; + border-bottom: 2px solid #4DD0E1!important; +} diff --git a/clientApp/GUI/img/darkID-logo-black.png b/clientApp/GUI/img/darkID-logo-black.png new file mode 100644 index 0000000..dc84861 Binary files /dev/null and b/clientApp/GUI/img/darkID-logo-black.png differ diff --git a/clientApp/GUI/img/darkID-logo-white.png b/clientApp/GUI/img/darkID-logo-white.png new file mode 100644 index 0000000..ddadf84 Binary files /dev/null and b/clientApp/GUI/img/darkID-logo-white.png differ diff --git a/clientApp/GUI/img/darkID-logo.xcf b/clientApp/GUI/img/darkID-logo.xcf new file mode 100644 index 0000000..56a372d Binary files /dev/null and b/clientApp/GUI/img/darkID-logo.xcf differ diff --git a/clientApp/GUI/img/darkID-logo01.png b/clientApp/GUI/img/darkID-logo01.png new file mode 100644 index 0000000..8165571 Binary files /dev/null and b/clientApp/GUI/img/darkID-logo01.png differ diff --git a/clientApp/GUI/img/darkID-logo02.png b/clientApp/GUI/img/darkID-logo02.png new file mode 100644 index 0000000..04fb61b Binary files /dev/null and b/clientApp/GUI/img/darkID-logo02.png differ diff --git a/clientApp/GUI/index.html b/clientApp/GUI/index.html new file mode 100644 index 0000000..c544034 --- /dev/null +++ b/clientApp/GUI/index.html @@ -0,0 +1,69 @@ + + + + + + darkID + + + + + + + + + + + + + +
+




+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/clientApp/GUI/main.js b/clientApp/GUI/main.js new file mode 100644 index 0000000..c2b5dde --- /dev/null +++ b/clientApp/GUI/main.js @@ -0,0 +1,72 @@ +const electron = require('electron') +// Module to control application life. +const app = electron.app +// Module to create native browser window. +const BrowserWindow = electron.BrowserWindow + +const Tray = electron.Tray +const Menu = electron.Menu + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow + + +function createWindow () { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 850, + height: 600, + icon: 'img/darkID-logo-white.png' + }) + tray = new Tray('img/darkID-logo-white.png') + const contextMenu = Menu.buildFromTemplate([ + {label: 'Obre la finestra', type: 'radio'}, + {label: 'javascript madness', type: 'radio'}, + {label: 'Tanca', type: 'radio'} + //{label: 'Tanca', type: 'radio', checked: true} + ]) + tray.setToolTip('Panopticon, projectNSA') + tray.setContextMenu(contextMenu) + + mainWindow.setMenu(null); + + // and load the index.html of the app. + mainWindow.loadURL(`file://${__dirname}/index.html`) + + // Open the DevTools. + //mainWindow.webContents.openDevTools() + + // Emitted when the window is closed. + mainWindow.on('closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', function () { + // On OS X it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', function () { + // On OS X it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow() + } +}) + +// In this file you can include the rest of your app's specific main process +// code. You can also put them in separate files and require them here. diff --git a/clientApp/GUI/package.json b/clientApp/GUI/package.json new file mode 100644 index 0000000..ffd4e36 --- /dev/null +++ b/clientApp/GUI/package.json @@ -0,0 +1,15 @@ +{ + "name": "darkID-clientApp", + "version": "1.0.0", + "description": "frontend desktop app for darkID", + "main": "main.js", + "scripts": { + "postinstall": "bower install", + "prestart": "npm install", + "start": "electron ." + }, + "devDependencies": { + "electron-prebuilt": "^1.2.0" + }, + "license": "MIT" +} diff --git a/clientApp/GUI/views/login/login.html b/clientApp/GUI/views/login/login.html new file mode 100755 index 0000000..eaf58f6 --- /dev/null +++ b/clientApp/GUI/views/login/login.html @@ -0,0 +1,32 @@ +
+
+
+ +
+
+
+ +
+

+ darkID +

+ + +
+
+ Signup +
+
+
Login
+
+
+
+
+
+ + +
+ +
+
+
diff --git a/clientApp/GUI/views/login/login.js b/clientApp/GUI/views/login/login.js new file mode 100755 index 0000000..c5e7bdb --- /dev/null +++ b/clientApp/GUI/views/login/login.js @@ -0,0 +1,62 @@ +'use strict'; + +angular.module('app.login', ['ngRoute']) + + .config(['$routeProvider', function($routeProvider) { + $routeProvider.when('/login', { + templateUrl: 'views/login/login.html', + controller: 'LoginCtrl' + }); + }]) + + .controller('LoginCtrl', function($scope, $rootScope, $http, $routeParams, toastr) { + $rootScope.server = "" + $scope.user = {}; + //set server in goclient + $http.get(clientapi + 'getserver') + .then(function(data) { + console.log("data: "); + console.log(data.data); + $rootScope.server = data.data; + localStorage.setItem("darkID_server", JSON.stringify($rootScope.server)); + console.log("server", $rootScope.server); + }, function(data) { + console.log('data error'); + }); + + $scope.login = function() { + + console.log('Doing login', $scope.user); + console.log($rootScope.server + "login"); + + + + // + $http({ + url: $rootScope.server + 'login', + method: "POST", + headers: { + "Content-Type": undefined + }, + data: $scope.user + }) + .then(function(data) { + console.log("data: "); + console.log(data.data); + if (data.data.token) { + localStorage.setItem("darkID_token", data.data.token); + localStorage.setItem("darkID_user", JSON.stringify(data.data)); + window.location.reload(); + } else { + console.log("login failed"); + toastr.error('Login failed'); + } + + + }, + function(data) { + console.log(data); + }); + + }; + }); diff --git a/clientApp/GUI/views/main/main.html b/clientApp/GUI/views/main/main.html new file mode 100755 index 0000000..4161be9 --- /dev/null +++ b/clientApp/GUI/views/main/main.html @@ -0,0 +1,53 @@ +
+
+
+ +
+
+
+
+
+
+
Use ID
+
+
+
Create new ID
+
+
+
+
+
+

+ My IDs +

+
+
+ Public Key: {{id.pubK}} + +
Date of creation: {{id.date}} + +
+

+
+
+
+ Not verified + Verified +
+
+ Not signed + Signed +
+
Send to serverIDsigner
+
Verify
+
+
+
+
+
+
+ +
+ +
+
diff --git a/clientApp/GUI/views/main/main.js b/clientApp/GUI/views/main/main.js new file mode 100755 index 0000000..03e98ab --- /dev/null +++ b/clientApp/GUI/views/main/main.js @@ -0,0 +1,61 @@ +'use strict'; + +angular.module('app.main', ['ngRoute']) + + .config(['$routeProvider', function($routeProvider) { + $routeProvider.when('/main', { + templateUrl: 'views/main/main.html', + controller: 'MainCtrl' + }); + }]) + + .controller('MainCtrl', function($scope, $rootScope, $http) { + + $rootScope.server = JSON.parse(localStorage.getItem("darkID_server")); + + $scope.ids = []; + $http.get(clientapi + 'ids') + .then(function(data) { + console.log('data success'); + console.log(data); + $scope.ids = data.data; + + }, function(data) { + console.log('data error'); + }); + + $scope.newID = function() { + $http.get(clientapi + 'newid') + .then(function(data) { + console.log('data success'); + console.log(data); + $scope.ids = data.data; + + }, function(data) { + console.log('data error'); + }); + }; + + $scope.blindAndSendToSign = function(pubK) { + $http.get(clientapi + 'blindandsendtosign/' + pubK) + .then(function(data) { + console.log('data success'); + console.log(data); + $scope.ids = data.data; + + }, function(data) { + console.log('data error'); + }); + }; + $scope.verify = function(pubK) { + $http.get(clientapi + 'verify/' + pubK) + .then(function(data) { + console.log('data success'); + console.log(data); + $scope.ids = data.data; + + }, function(data) { + console.log('data error'); + }); + }; + }); diff --git a/clientApp/GUI/views/navbar.html b/clientApp/GUI/views/navbar.html new file mode 100755 index 0000000..592eefa --- /dev/null +++ b/clientApp/GUI/views/navbar.html @@ -0,0 +1,35 @@ +
+ +
diff --git a/clientApp/GUI/views/navbar.js b/clientApp/GUI/views/navbar.js new file mode 100755 index 0000000..6b9d78a --- /dev/null +++ b/clientApp/GUI/views/navbar.js @@ -0,0 +1,24 @@ +'use strict'; + +angular.module('app.navbar', ['ngRoute']) + + .config(['$routeProvider', function($routeProvider) { + $routeProvider.when('/navbar', { + templateUrl: 'views/navbar.html', + controller: 'NavbarCtrl' + }); + }]) + + .controller('NavbarCtrl', function($scope, $rootScope, $http, $routeParams, $location) { + $rootScope.server = JSON.parse(localStorage.getItem("darkID_server")); + + $scope.user = JSON.parse(localStorage.getItem("darkID_user")); + + $scope.logout = function() { + localStorage.removeItem("darkID_token"); + localStorage.removeItem("darkID_user"); + localStorage.removeItem("darkID_server"); + window.location.reload(); + }; + + }); diff --git a/clientApp/GUI/views/signup/signup.html b/clientApp/GUI/views/signup/signup.html new file mode 100755 index 0000000..bdbd1bd --- /dev/null +++ b/clientApp/GUI/views/signup/signup.html @@ -0,0 +1,33 @@ +
+
+
+ +
+
+
+ +
+

+ darkID +

+ + + +
+
+ Cancel +
+
+
Signup
+
+
+
+
+
+ + +
+ +
+
+
diff --git a/clientApp/GUI/views/signup/signup.js b/clientApp/GUI/views/signup/signup.js new file mode 100755 index 0000000..bfab98d --- /dev/null +++ b/clientApp/GUI/views/signup/signup.js @@ -0,0 +1,19 @@ +'use strict'; + +angular.module('app.signup', ['ngRoute']) + +.config(['$routeProvider', function($routeProvider) { + $routeProvider.when('/signup', { + templateUrl: 'views/signup/signup.html', + controller: 'SignupCtrl' + }); +}]) + +.controller('SignupCtrl', function($scope, $http, $routeParams) { + $scope.user = {}; + $scope.doSignup = function() { + console.log('Doing signup', $scope.user); + + + }; +}); diff --git a/clientApp/README.md b/clientApp/README.md new file mode 100644 index 0000000..3fc8892 --- /dev/null +++ b/clientApp/README.md @@ -0,0 +1,5 @@ +# serverIDsign + +- The server where the user creates a non anonymous account +- Also is the server that blind signs the Anonymous ID of the users +- Have the webapp (frontend) to interact through a GUI interface diff --git a/clientApp/clientAppRESTFunctions.go b/clientApp/clientAppRESTFunctions.go new file mode 100644 index 0000000..8d14927 --- /dev/null +++ b/clientApp/clientAppRESTFunctions.go @@ -0,0 +1,173 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "time" + + ownrsa "./ownrsa" + "github.com/fatih/color" + "github.com/gorilla/mux" +) + +//TODO use rsa library instead own rsa functions + +func Index(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "serverIDsigner") +} + +func GetServer(w http.ResponseWriter, r *http.Request) { + color.Green(config.Server) + fmt.Fprintln(w, config.Server) +} +func IDs(w http.ResponseWriter, r *http.Request) { + //read the keys stored in /keys directory + keys := readKeys("keys.json") + saveKeys(keys, "keys.json") + + jResp, err := json.Marshal(keys) + check(err) + fmt.Fprintln(w, string(jResp)) +} +func NewID(w http.ResponseWriter, r *http.Request) { + //generate RSA keys pair + newKey := ownrsa.GenerateKeyPair() + + key := ownrsa.PackKey(newKey) + key.Date = time.Now() + fmt.Println(key) + + keys := readKeys("keys.json") + keys = append(keys, key) + saveKeys(keys, "keys.json") + + jResp, err := json.Marshal(keys) + check(err) + fmt.Fprintln(w, string(jResp)) +} + +type AskBlindSign struct { + M string `json:"m"` +} + +func BlindAndSendToSign(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + packPubK := vars["pubK"] + color.Green(packPubK) + + //read the keys stored in /keys directory + keys := readKeys("keys.json") + + var key ownrsa.RSA + //search for complete key + for _, k := range keys { + if k.PubK == packPubK { + key = ownrsa.UnpackKey(k) + } + } + //blind the key.PubK + var m []int + //convert packPubK to []bytes + mBytes := []byte(packPubK) + for _, byte := range mBytes { + m = append(m, int(byte)) + } + rVal := 101 + blinded := ownrsa.Blind(m, rVal, key.PubK, key.PrivK) + fmt.Println(blinded) + + //convert blinded to string + var askBlindSign AskBlindSign + askBlindSign.M = ownrsa.ArrayIntToString(blinded, "_") + + //send to the serverIDsigner the key.PubK blinded + color.Green(askBlindSign.M) + body := new(bytes.Buffer) + json.NewEncoder(body).Encode(askBlindSign) + res, err := http.Post(config.Server+"blindsign", "application/json", body) + check(err) + fmt.Println(res) + + decoder := json.NewDecoder(res.Body) + //var sigmaString string + err = decoder.Decode(&askBlindSign) + if err != nil { + panic(err) + } + defer r.Body.Close() + + fmt.Println("sigmaString") + fmt.Println(askBlindSign) + sigma := ownrsa.StringToArrayInt(askBlindSign.M, "_") + fmt.Println(sigma) + + //get the serverIDsigner pubK + serverPubK := getServerPubK(config.Server) + + //unblind the response + mSigned := ownrsa.Unblind(sigma, rVal, serverPubK) + fmt.Print("mSigned: ") + fmt.Println(mSigned) + + verified := ownrsa.Verify(m, mSigned, serverPubK) + fmt.Println(verified) + + var iKey int + for i, k := range keys { + if k.PubK == packPubK { + iKey = i + //save to k the key updated + k.PubKSigned = ownrsa.ArrayIntToString(mSigned, "_") + k.Verified = verified + } + fmt.Println(k) + } + keys[iKey].PubKSigned = ownrsa.ArrayIntToString(mSigned, "_") + keys[iKey].Verified = verified + fmt.Println(keys) + saveKeys(keys, "keys.json") + + jResp, err := json.Marshal(keys) + check(err) + fmt.Fprintln(w, string(jResp)) +} + +func Verify(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + packPubK := vars["pubK"] + color.Green(packPubK) + + //read the keys stored in /keys directory + keys := readKeys("keys.json") + + var key ownrsa.PackRSA + //search for complete key + for _, k := range keys { + if k.PubK == packPubK { + key = k + } + } + + //get the serverIDsigner pubK + serverPubK := getServerPubK(config.Server) + m := ownrsa.StringToArrayInt(key.PubK, "_") + mSigned := ownrsa.StringToArrayInt(key.PubKSigned, "_") + + verified := ownrsa.Verify(m, mSigned, serverPubK) + fmt.Println(verified) + + for _, k := range keys { + if k.PubK == packPubK { + //save to k the key updated + k.PubKSigned = ownrsa.ArrayIntToString(mSigned, "_") + k.Verified = verified + } + } + saveKeys(keys, "keys.json") + + jResp, err := json.Marshal(keys) + check(err) + fmt.Fprintln(w, string(jResp)) +} diff --git a/clientApp/config.json b/clientApp/config.json new file mode 100755 index 0000000..d385f7a --- /dev/null +++ b/clientApp/config.json @@ -0,0 +1,5 @@ +{ + "port": "4100", + "keysDirectory": "keys", + "server": "http://127.0.0.1:3130/" +} diff --git a/clientApp/errors.go b/clientApp/errors.go new file mode 100755 index 0000000..b3cf6b2 --- /dev/null +++ b/clientApp/errors.go @@ -0,0 +1,15 @@ +package main + +import ( + "log" + "runtime" +) + +func check(err error) { + if err != nil { + _, fn, line, _ := runtime.Caller(1) + log.Println(line) + log.Println(fn) + log.Println(err) + } +} diff --git a/clientApp/keys.go b/clientApp/keys.go new file mode 100644 index 0000000..5d6b1f4 --- /dev/null +++ b/clientApp/keys.go @@ -0,0 +1,47 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + ownrsa "./ownrsa" + "github.com/fatih/color" +) + +func readKeys(path string) []ownrsa.PackRSA { + var keys []ownrsa.PackRSA + + file, err := ioutil.ReadFile(path) + check(err) + content := string(file) + json.Unmarshal([]byte(content), &keys) + + return keys +} + +func saveKeys(keys []ownrsa.PackRSA, path string) { + jsonKeys, err := json.Marshal(keys) + check(err) + err = ioutil.WriteFile(path, jsonKeys, 0644) + check(err) +} + +func getServerPubK(url string) ownrsa.RSAPublicKey { + r, err := http.Get(url + "/") + check(err) + fmt.Println(r) + + decoder := json.NewDecoder(r.Body) + //var sigmaString string + var pubK ownrsa.RSAPublicKey + err = decoder.Decode(&pubK) + if err != nil { + panic(err) + } + defer r.Body.Close() + color.Blue("received server pubK:") + fmt.Println(pubK) + return pubK +} diff --git a/clientApp/log.go b/clientApp/log.go new file mode 100755 index 0000000..e8f391a --- /dev/null +++ b/clientApp/log.go @@ -0,0 +1,24 @@ +package main + +import ( + "io" + "log" + "os" + "strings" + "time" +) + +func savelog() { + timeS := time.Now().String() + _ = os.Mkdir("logs", os.ModePerm) + //next 3 lines are to avoid windows filesystem errors + timeS = strings.Replace(timeS, " ", "_", -1) + timeS = strings.Replace(timeS, ".", "-", -1) + timeS = strings.Replace(timeS, ":", "-", -1) + logFile, err := os.OpenFile("logs/log-"+timeS+".log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) + if err != nil { + panic(err) + } + mw := io.MultiWriter(os.Stdout, logFile) + log.SetOutput(mw) +} diff --git a/clientApp/main.go b/clientApp/main.go new file mode 100644 index 0000000..3f14b9d --- /dev/null +++ b/clientApp/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "log" + "net/http" + + "github.com/fatih/color" + "github.com/gorilla/handlers" +) + +func main() { + color.Blue("Starting darkID clientApp") + + readConfig("config.json") + fmt.Println(config) + + //run thw webserver + go GUI() + + //run API + log.Println("api server running") + log.Print("port: ") + log.Println(config.Port) + router := NewRouter() + headersOk := handlers.AllowedHeaders([]string{"X-Requested-With", "Access-Control-Allow-Origin"}) + originsOk := handlers.AllowedOrigins([]string{"*"}) + methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"}) + log.Fatal(http.ListenAndServe(":"+config.Port, handlers.CORS(originsOk, headersOk, methodsOk)(router))) +} + +func GUI() { + //here, run electron app +} diff --git a/clientApp/ownrsa/prime.go b/clientApp/ownrsa/prime.go new file mode 100644 index 0000000..e5d214b --- /dev/null +++ b/clientApp/ownrsa/prime.go @@ -0,0 +1,54 @@ +package ownrsa + +import "math/rand" + +func randInt(min int, max int) int { + r := rand.Intn(max-min) + min + return r +} +func randPrime(min int, max int) int { + primes := sieveOfEratosthenes(max) + + randN := rand.Intn(len(primes)-0) + 0 + + return primes[randN] + +} + +// return list of primes less than N +func sieveOfEratosthenes(N int) (primes []int) { + b := make([]bool, N) + for i := 2; i < N; i++ { + if b[i] == true { + continue + } + primes = append(primes, i) + for k := i * i; k < N; k += i { + b[k] = true + } + } + return +} + +func gcd(a, b int) int { + var bgcd func(a, b, res int) int + + bgcd = func(a, b, res int) int { + switch { + case a == b: + return res * a + case a%2 == 0 && b%2 == 0: + return bgcd(a/2, b/2, 2*res) + case a%2 == 0: + return bgcd(a/2, b, res) + case b%2 == 0: + return bgcd(a, b/2, res) + case a > b: + return bgcd(a-b, b, res) + default: + return bgcd(a, b-a, res) + } + } + + return bgcd(a, b, 1) +} diff --git a/clientApp/ownrsa/rsa.go b/clientApp/ownrsa/rsa.go new file mode 100644 index 0000000..1e18627 --- /dev/null +++ b/clientApp/ownrsa/rsa.go @@ -0,0 +1,229 @@ +package ownrsa + +import ( + "errors" + "fmt" + "math/big" + "math/rand" + "strconv" + "strings" + "time" +) + +type RSAPublicKey struct { + E *big.Int `json:"e"` + N *big.Int `json:"n"` +} +type RSAPublicKeyString struct { + E string `json:"e"` + N string `json:"n"` +} +type RSAPrivateKey struct { + D *big.Int `json:"d"` + N *big.Int `json:"n"` +} + +type RSA struct { + PubK RSAPublicKey + PrivK RSAPrivateKey +} + +type PackRSA struct { + PubK string `json:"pubK"` + PrivK string `json:"privK"` + Date time.Time `json:"date"` + PubKSigned string `json:"pubKSigned"` + Verified bool `json:"verified"` +} + +const maxPrime = 500 +const minPrime = 100 + +func GenerateKeyPair() RSA { + + rand.Seed(time.Now().Unix()) + p := randPrime(minPrime, maxPrime) + q := randPrime(minPrime, maxPrime) + fmt.Print("p:") + fmt.Println(p) + fmt.Print("q:") + fmt.Println(q) + + n := p * q + phi := (p - 1) * (q - 1) + e := 65537 + var pubK RSAPublicKey + pubK.E = big.NewInt(int64(e)) + pubK.N = big.NewInt(int64(n)) + + d := new(big.Int).ModInverse(big.NewInt(int64(e)), big.NewInt(int64(phi))) + + var privK RSAPrivateKey + privK.D = d + privK.N = big.NewInt(int64(n)) + + var rsa RSA + rsa.PubK = pubK + rsa.PrivK = privK + return rsa +} +func Encrypt(m string, pubK RSAPublicKey) []int { + var c []int + mBytes := []byte(m) + for _, byte := range mBytes { + c = append(c, EncryptInt(int(byte), pubK)) + } + return c +} +func Decrypt(c []int, privK RSAPrivateKey) string { + var m string + var mBytes []byte + for _, indC := range c { + mBytes = append(mBytes, byte(DecryptInt(indC, privK))) + } + m = string(mBytes) + return m +} + +func EncryptBigInt(bigint *big.Int, pubK RSAPublicKey) *big.Int { + Me := new(big.Int).Exp(bigint, pubK.E, nil) + c := new(big.Int).Mod(Me, pubK.N) + return c +} +func DecryptBigInt(bigint *big.Int, privK RSAPrivateKey) *big.Int { + Cd := new(big.Int).Exp(bigint, privK.D, nil) + m := new(big.Int).Mod(Cd, privK.N) + return m +} + +func EncryptInt(char int, pubK RSAPublicKey) int { + charBig := big.NewInt(int64(char)) + Me := charBig.Exp(charBig, pubK.E, nil) + c := Me.Mod(Me, pubK.N) + return int(c.Int64()) +} +func DecryptInt(val int, privK RSAPrivateKey) int { + valBig := big.NewInt(int64(val)) + Cd := valBig.Exp(valBig, privK.D, nil) + m := Cd.Mod(Cd, privK.N) + return int(m.Int64()) +} + +func Blind(m []int, r int, pubK RSAPublicKey, privK RSAPrivateKey) []int { + var mBlinded []int + rBigInt := big.NewInt(int64(r)) + for i := 0; i < len(m); i++ { + mBigInt := big.NewInt(int64(m[i])) + rE := new(big.Int).Exp(rBigInt, pubK.E, nil) + mrE := new(big.Int).Mul(mBigInt, rE) + mrEmodN := new(big.Int).Mod(mrE, privK.N) + mBlinded = append(mBlinded, int(mrEmodN.Int64())) + } + return mBlinded +} + +func BlindSign(m []int, privK RSAPrivateKey) []int { + var r []int + for i := 0; i < len(m); i++ { + mBigInt := big.NewInt(int64(m[i])) + sigma := new(big.Int).Exp(mBigInt, privK.D, privK.N) + r = append(r, int(sigma.Int64())) + } + return r +} +func Unblind(blindsigned []int, r int, pubK RSAPublicKey) []int { + var mSigned []int + rBigInt := big.NewInt(int64(r)) + for i := 0; i < len(blindsigned); i++ { + bsBigInt := big.NewInt(int64(blindsigned[i])) + //r1 := new(big.Int).Exp(rBigInt, big.NewInt(int64(-1)), nil) + r1 := new(big.Int).ModInverse(rBigInt, pubK.N) + bsr := new(big.Int).Mul(bsBigInt, r1) + sig := new(big.Int).Mod(bsr, pubK.N) + mSigned = append(mSigned, int(sig.Int64())) + } + return mSigned +} +func Verify(msg []int, mSigned []int, pubK RSAPublicKey) bool { + if len(msg) != len(mSigned) { + return false + } + var mSignedDecrypted []int + for _, ms := range mSigned { + msBig := big.NewInt(int64(ms)) + //decrypt the mSigned with pubK + Cd := new(big.Int).Exp(msBig, pubK.E, nil) + m := new(big.Int).Mod(Cd, pubK.N) + mSignedDecrypted = append(mSignedDecrypted, int(m.Int64())) + } + fmt.Print("msg signed decrypted: ") + fmt.Println(mSignedDecrypted) + r := true + //check if the mSignedDecrypted == msg + for i := 0; i < len(msg); i++ { + if msg[i] != mSignedDecrypted[i] { + r = false + } + } + return r +} + +func HomomorphicMultiplication(c1 int, c2 int, pubK RSAPublicKey) int { + c1BigInt := big.NewInt(int64(c1)) + c2BigInt := big.NewInt(int64(c2)) + c1c2 := new(big.Int).Mul(c1BigInt, c2BigInt) + n2 := new(big.Int).Mul(pubK.N, pubK.N) + d := new(big.Int).Mod(c1c2, n2) + r := int(d.Int64()) + return r +} + +func PubKStringToBigInt(kS RSAPublicKeyString) (RSAPublicKey, error) { + var k RSAPublicKey + var ok bool + k.E, ok = new(big.Int).SetString(kS.E, 10) + if !ok { + return k, errors.New("error parsing big int E") + } + k.N, ok = new(big.Int).SetString(kS.N, 10) + if !ok { + return k, errors.New("error parsing big int N") + } + return k, nil +} + +func PackKey(k RSA) PackRSA { + var p PackRSA + p.PubK = k.PubK.E.String() + "," + k.PubK.N.String() + p.PrivK = k.PrivK.D.String() + "," + k.PrivK.N.String() + return p +} + +func UnpackKey(p PackRSA) RSA { + var k RSA + var ok bool + k.PubK.E, ok = new(big.Int).SetString(strings.Split(p.PubK, ",")[0], 10) + k.PubK.N, ok = new(big.Int).SetString(strings.Split(p.PubK, ",")[1], 10) + k.PrivK.D, ok = new(big.Int).SetString(strings.Split(p.PrivK, ",")[0], 10) + k.PrivK.N, ok = new(big.Int).SetString(strings.Split(p.PrivK, ",")[1], 10) + if !ok { + fmt.Println("error on Unpacking Keys") + } + return k +} + +func ArrayIntToString(a []int, delim string) string { + return strings.Trim(strings.Replace(fmt.Sprint(a), " ", delim, -1), "[]") +} +func StringToArrayInt(s string, delim string) []int { + var a []int + arrayString := strings.Split(s, delim) + for _, s := range arrayString { + i, err := strconv.Atoi(s) + if err != nil { + fmt.Println(err) + } + a = append(a, i) + } + return a +} diff --git a/clientApp/readConfig.go b/clientApp/readConfig.go new file mode 100755 index 0000000..c45fd1c --- /dev/null +++ b/clientApp/readConfig.go @@ -0,0 +1,22 @@ +package main + +import ( + "encoding/json" + "io/ioutil" +) + +//Config reads the config +type Config struct { + Port string `json:"port"` + KeysDirectory string `json:"keysDirectory"` + Server string `json:"server"` +} + +var config Config + +func readConfig(path string) { + file, err := ioutil.ReadFile(path) + check(err) + content := string(file) + json.Unmarshal([]byte(content), &config) +} diff --git a/clientApp/restConfig.go b/clientApp/restConfig.go new file mode 100755 index 0000000..36a332e --- /dev/null +++ b/clientApp/restConfig.go @@ -0,0 +1,47 @@ +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gorilla/mux" +) + +type Route struct { + Name string + Method string + Pattern string + HandlerFunc http.HandlerFunc +} + +func Logger(inner http.Handler, name string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + inner.ServeHTTP(w, r) + + log.Printf( + "%s\t%s\t%s\t%s", + r.Method, + r.RequestURI, + name, + time.Since(start), + ) + }) +} +func NewRouter() *mux.Router { + router := mux.NewRouter().StrictSlash(true) + for _, route := range routes { + var handler http.Handler + handler = route.HandlerFunc + handler = Logger(handler, route.Name) + + router. + Methods(route.Method). + Path(route.Pattern). + Name(route.Name). + Handler(handler) + } + return router +} diff --git a/clientApp/restRoutes.go b/clientApp/restRoutes.go new file mode 100755 index 0000000..4f38836 --- /dev/null +++ b/clientApp/restRoutes.go @@ -0,0 +1,42 @@ +package main + +type Routes []Route + +var routes = Routes{ + Route{ + "Index", + "GET", + "/", + Index, + }, + Route{ + "GetServer", + "GET", + "/getserver", + GetServer, + }, + Route{ + "IDs", + "GET", + "/ids", + IDs, + }, + Route{ + "NewID", + "GET", + "/newid", + NewID, + }, + Route{ + "BlindAndSendToSign", + "GET", + "/blindandsendtosign/{pubK}", + BlindAndSendToSign, + }, + Route{ + "Verify", + "GET", + "/verify/{pubK}", + Verify, + }, +} diff --git a/clientApp/testUser.sh b/clientApp/testUser.sh new file mode 100644 index 0000000..01b1ef7 --- /dev/null +++ b/clientApp/testUser.sh @@ -0,0 +1,20 @@ +echo "" +echo "sending the signup, response:" +curl -X POST http://127.0.0.1:3130/signup -d '{"email": "user1@e.com", "password": "user1"}' + +echo "" +echo "sending the login, response:" +curl -X POST http://127.0.0.1:3130/login -d '{"email": "user1@e.com", "password": "user1"}' + + +echo "" +echo "send pubK and m to blind sign" +echo "json to send to the serverIDsigner:" +echo '{"pubKstring": {"e": "65537", "n": "139093"}, "m": "hola"}' +echo "serverIDsigner response:" +BLINDSIGNED=$(curl -X POST http://127.0.0.1:3130/blindsign -d '{"pubKstring": {"e": "65537", "n": "139093"}, "m": "hola"}') +echo "$BLINDSIGNED" + +echo "" +echo "send blindsigned to the serverIDsigner to verify" +curl -X POST http://127.0.0.1:3130/verifysign -d '{"m": "hola", "mSigned": "131898 40373 107552 34687"}' diff --git a/documentation/darkID-network.png b/documentation/darkID-network.png new file mode 100644 index 0000000..219ee57 Binary files /dev/null and b/documentation/darkID-network.png differ diff --git a/documentation/darkID-network.xml b/documentation/darkID-network.xml new file mode 100644 index 0000000..48a8ea9 --- /dev/null +++ b/documentation/darkID-network.xml @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/documentation/screenshot01.png b/documentation/screenshot01.png new file mode 100644 index 0000000..03b066d Binary files /dev/null and b/documentation/screenshot01.png differ diff --git a/documentation/screenshot02.png b/documentation/screenshot02.png new file mode 100644 index 0000000..6808745 Binary files /dev/null and b/documentation/screenshot02.png differ diff --git a/runTmuxTestPeers.sh b/runTmuxTestPeers.sh new file mode 100644 index 0000000..7fd19e2 --- /dev/null +++ b/runTmuxTestPeers.sh @@ -0,0 +1,11 @@ +SESSION='darkIDtest' + +tmux new-session -d -s $SESSION +tmux split-window -d -t 0 -v +tmux split-window -d -t 0 -h + +tmux send-keys -t 0 'cd serverIDsigner && go run *.go' enter +tmux send-keys -t 1 'cd clientApp && go run *.go' enter +tmux send-keys -t 2 'cd clientApp/GUI && http-server' enter + +tmux attach diff --git a/serverIDsigner/README.md b/serverIDsigner/README.md new file mode 100644 index 0000000..3fc8892 --- /dev/null +++ b/serverIDsigner/README.md @@ -0,0 +1,5 @@ +# serverIDsign + +- The server where the user creates a non anonymous account +- Also is the server that blind signs the Anonymous ID of the users +- Have the webapp (frontend) to interact through a GUI interface diff --git a/serverIDsigner/config.json b/serverIDsigner/config.json new file mode 100755 index 0000000..896f7ec --- /dev/null +++ b/serverIDsigner/config.json @@ -0,0 +1,8 @@ +{ + "ip": "127.0.0.1", + "port": "3130", + "mongodb": { + "ip": "127.0.0.1:27017", + "database": "serverIDsigner" + } +} diff --git a/serverIDsigner/errors.go b/serverIDsigner/errors.go new file mode 100755 index 0000000..b3cf6b2 --- /dev/null +++ b/serverIDsigner/errors.go @@ -0,0 +1,15 @@ +package main + +import ( + "log" + "runtime" +) + +func check(err error) { + if err != nil { + _, fn, line, _ := runtime.Caller(1) + log.Println(line) + log.Println(fn) + log.Println(err) + } +} diff --git a/serverIDsigner/log.go b/serverIDsigner/log.go new file mode 100755 index 0000000..e8f391a --- /dev/null +++ b/serverIDsigner/log.go @@ -0,0 +1,24 @@ +package main + +import ( + "io" + "log" + "os" + "strings" + "time" +) + +func savelog() { + timeS := time.Now().String() + _ = os.Mkdir("logs", os.ModePerm) + //next 3 lines are to avoid windows filesystem errors + timeS = strings.Replace(timeS, " ", "_", -1) + timeS = strings.Replace(timeS, ".", "-", -1) + timeS = strings.Replace(timeS, ":", "-", -1) + logFile, err := os.OpenFile("logs/log-"+timeS+".log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) + if err != nil { + panic(err) + } + mw := io.MultiWriter(os.Stdout, logFile) + log.SetOutput(mw) +} diff --git a/serverIDsigner/main.go b/serverIDsigner/main.go new file mode 100644 index 0000000..ef87e00 --- /dev/null +++ b/serverIDsigner/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + "log" + "net/http" + + mgo "gopkg.in/mgo.v2" + + "github.com/fatih/color" + "github.com/gorilla/handlers" + + ownrsa "./ownrsa" +) + +var userCollection *mgo.Collection + +var serverRSA ownrsa.RSA + +func main() { + color.Blue("Starting serverIDsigner") + + //read configuration file + readConfig("config.json") + + initializeToken() + + //initialize RSA + serverRSA = ownrsa.GenerateKeyPair() + color.Blue("Public Key:") + fmt.Println(serverRSA.PubK) + color.Green("Private Key:") + fmt.Println(serverRSA.PrivK) + + //mongodb + session, err := getSession() + check(err) + userCollection = getCollection(session, "users") + + //run API + log.Println("api server running") + log.Print("port: ") + log.Println(config.Port) + router := NewRouter() + headersOk := handlers.AllowedHeaders([]string{"X-Requested-With", "Access-Control-Allow-Origin"}) + originsOk := handlers.AllowedOrigins([]string{"*"}) + methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"}) + log.Fatal(http.ListenAndServe(":"+config.Port, handlers.CORS(originsOk, headersOk, methodsOk)(router))) +} diff --git a/serverIDsigner/mongoOperations.go b/serverIDsigner/mongoOperations.go new file mode 100755 index 0000000..4b21ca6 --- /dev/null +++ b/serverIDsigner/mongoOperations.go @@ -0,0 +1,26 @@ +package main + +import ( + mgo "gopkg.in/mgo.v2" +) + +func getSession() (*mgo.Session, error) { + session, err := mgo.Dial("mongodb://" + config.Mongodb.IP) + if err != nil { + panic(err) + } + //defer session.Close() + + // Optional. Switch the session to a monotonic behavior. + session.SetMode(mgo.Monotonic, true) + + // Optional. Switch the session to a monotonic behavior. + session.SetMode(mgo.Monotonic, true) + + return session, err +} +func getCollection(session *mgo.Session, collection string) *mgo.Collection { + + c := session.DB(config.Mongodb.Database).C(collection) + return c +} diff --git a/serverIDsigner/ownrsa/prime.go b/serverIDsigner/ownrsa/prime.go new file mode 100644 index 0000000..e5d214b --- /dev/null +++ b/serverIDsigner/ownrsa/prime.go @@ -0,0 +1,54 @@ +package ownrsa + +import "math/rand" + +func randInt(min int, max int) int { + r := rand.Intn(max-min) + min + return r +} +func randPrime(min int, max int) int { + primes := sieveOfEratosthenes(max) + + randN := rand.Intn(len(primes)-0) + 0 + + return primes[randN] + +} + +// return list of primes less than N +func sieveOfEratosthenes(N int) (primes []int) { + b := make([]bool, N) + for i := 2; i < N; i++ { + if b[i] == true { + continue + } + primes = append(primes, i) + for k := i * i; k < N; k += i { + b[k] = true + } + } + return +} + +func gcd(a, b int) int { + var bgcd func(a, b, res int) int + + bgcd = func(a, b, res int) int { + switch { + case a == b: + return res * a + case a%2 == 0 && b%2 == 0: + return bgcd(a/2, b/2, 2*res) + case a%2 == 0: + return bgcd(a/2, b, res) + case b%2 == 0: + return bgcd(a, b/2, res) + case a > b: + return bgcd(a-b, b, res) + default: + return bgcd(a, b-a, res) + } + } + + return bgcd(a, b, 1) +} diff --git a/serverIDsigner/ownrsa/rsa.go b/serverIDsigner/ownrsa/rsa.go new file mode 100644 index 0000000..86c28e5 --- /dev/null +++ b/serverIDsigner/ownrsa/rsa.go @@ -0,0 +1,228 @@ +package ownrsa + +import ( + "errors" + "fmt" + "math/big" + "math/rand" + "strconv" + "strings" + "time" +) + +type RSAPublicKey struct { + E *big.Int `json:"e"` + N *big.Int `json:"n"` +} +type RSAPublicKeyString struct { + E string `json:"e"` + N string `json:"n"` +} +type RSAPrivateKey struct { + D *big.Int `json:"d"` + N *big.Int `json:"n"` +} + +type RSA struct { + PubK RSAPublicKey + PrivK RSAPrivateKey +} + +type PackRSA struct { + PubK string `json:"pubK"` + PrivK string `json:"privK"` + Date time.Time `json:"date"` + PubKSigned string `json:"pubKSigned"` +} + +const maxPrime = 500 +const minPrime = 100 + +func GenerateKeyPair() RSA { + + rand.Seed(time.Now().Unix()) + p := randPrime(minPrime, maxPrime) + q := randPrime(minPrime, maxPrime) + fmt.Print("p:") + fmt.Println(p) + fmt.Print("q:") + fmt.Println(q) + + n := p * q + phi := (p - 1) * (q - 1) + e := 65537 + var pubK RSAPublicKey + pubK.E = big.NewInt(int64(e)) + pubK.N = big.NewInt(int64(n)) + + d := new(big.Int).ModInverse(big.NewInt(int64(e)), big.NewInt(int64(phi))) + + var privK RSAPrivateKey + privK.D = d + privK.N = big.NewInt(int64(n)) + + var rsa RSA + rsa.PubK = pubK + rsa.PrivK = privK + return rsa +} +func Encrypt(m string, pubK RSAPublicKey) []int { + var c []int + mBytes := []byte(m) + for _, byte := range mBytes { + c = append(c, EncryptInt(int(byte), pubK)) + } + return c +} +func Decrypt(c []int, privK RSAPrivateKey) string { + var m string + var mBytes []byte + for _, indC := range c { + mBytes = append(mBytes, byte(DecryptInt(indC, privK))) + } + m = string(mBytes) + return m +} + +func EncryptBigInt(bigint *big.Int, pubK RSAPublicKey) *big.Int { + Me := new(big.Int).Exp(bigint, pubK.E, nil) + c := new(big.Int).Mod(Me, pubK.N) + return c +} +func DecryptBigInt(bigint *big.Int, privK RSAPrivateKey) *big.Int { + Cd := new(big.Int).Exp(bigint, privK.D, nil) + m := new(big.Int).Mod(Cd, privK.N) + return m +} + +func EncryptInt(char int, pubK RSAPublicKey) int { + charBig := big.NewInt(int64(char)) + Me := charBig.Exp(charBig, pubK.E, nil) + c := Me.Mod(Me, pubK.N) + return int(c.Int64()) +} +func DecryptInt(val int, privK RSAPrivateKey) int { + valBig := big.NewInt(int64(val)) + Cd := valBig.Exp(valBig, privK.D, nil) + m := Cd.Mod(Cd, privK.N) + return int(m.Int64()) +} + +func Blind(m []int, r int, pubK RSAPublicKey, privK RSAPrivateKey) []int { + var mBlinded []int + rBigInt := big.NewInt(int64(r)) + for i := 0; i < len(m); i++ { + mBigInt := big.NewInt(int64(m[i])) + rE := new(big.Int).Exp(rBigInt, pubK.E, nil) + mrE := new(big.Int).Mul(mBigInt, rE) + mrEmodN := new(big.Int).Mod(mrE, privK.N) + mBlinded = append(mBlinded, int(mrEmodN.Int64())) + } + return mBlinded +} + +func BlindSign(m []int, privK RSAPrivateKey) []int { + var r []int + for i := 0; i < len(m); i++ { + mBigInt := big.NewInt(int64(m[i])) + sigma := new(big.Int).Exp(mBigInt, privK.D, privK.N) + r = append(r, int(sigma.Int64())) + } + return r +} +func Unblind(blindsigned []int, r int, pubK RSAPublicKey) []int { + var mSigned []int + rBigInt := big.NewInt(int64(r)) + for i := 0; i < len(blindsigned); i++ { + bsBigInt := big.NewInt(int64(blindsigned[i])) + //r1 := new(big.Int).Exp(rBigInt, big.NewInt(int64(-1)), nil) + r1 := new(big.Int).ModInverse(rBigInt, pubK.N) + bsr := new(big.Int).Mul(bsBigInt, r1) + sig := new(big.Int).Mod(bsr, pubK.N) + mSigned = append(mSigned, int(sig.Int64())) + } + return mSigned +} +func Verify(msg []int, mSigned []int, pubK RSAPublicKey) bool { + if len(msg) != len(mSigned) { + return false + } + var mSignedDecrypted []int + for _, ms := range mSigned { + msBig := big.NewInt(int64(ms)) + //decrypt the mSigned with pubK + Cd := new(big.Int).Exp(msBig, pubK.E, nil) + m := new(big.Int).Mod(Cd, pubK.N) + mSignedDecrypted = append(mSignedDecrypted, int(m.Int64())) + } + fmt.Print("msg signed decrypted: ") + fmt.Println(mSignedDecrypted) + r := true + //check if the mSignedDecrypted == msg + for i := 0; i < len(msg); i++ { + if msg[i] != mSignedDecrypted[i] { + r = false + } + } + return r +} + +func HomomorphicMultiplication(c1 int, c2 int, pubK RSAPublicKey) int { + c1BigInt := big.NewInt(int64(c1)) + c2BigInt := big.NewInt(int64(c2)) + c1c2 := new(big.Int).Mul(c1BigInt, c2BigInt) + n2 := new(big.Int).Mul(pubK.N, pubK.N) + d := new(big.Int).Mod(c1c2, n2) + r := int(d.Int64()) + return r +} + +func PubKStringToBigInt(kS RSAPublicKeyString) (RSAPublicKey, error) { + var k RSAPublicKey + var ok bool + k.E, ok = new(big.Int).SetString(kS.E, 10) + if !ok { + return k, errors.New("error parsing big int E") + } + k.N, ok = new(big.Int).SetString(kS.N, 10) + if !ok { + return k, errors.New("error parsing big int N") + } + return k, nil +} + +func PackKey(k RSA) PackRSA { + var p PackRSA + p.PubK = k.PubK.E.String() + "," + k.PubK.N.String() + p.PrivK = k.PrivK.D.String() + "," + k.PrivK.N.String() + return p +} + +func UnpackKey(p PackRSA) RSA { + var k RSA + var ok bool + k.PubK.E, ok = new(big.Int).SetString(strings.Split(p.PubK, ",")[0], 10) + k.PubK.N, ok = new(big.Int).SetString(strings.Split(p.PubK, ",")[1], 10) + k.PrivK.D, ok = new(big.Int).SetString(strings.Split(p.PrivK, ",")[0], 10) + k.PrivK.N, ok = new(big.Int).SetString(strings.Split(p.PrivK, ",")[1], 10) + if !ok { + fmt.Println("error on Unpacking Keys") + } + return k +} + +func ArrayIntToString(a []int, delim string) string { + return strings.Trim(strings.Replace(fmt.Sprint(a), " ", delim, -1), "[]") +} +func StringToArrayInt(s string, delim string) []int { + var a []int + arrayString := strings.Split(s, delim) + for _, s := range arrayString { + i, err := strconv.Atoi(s) + if err != nil { + fmt.Println(err) + } + a = append(a, i) + } + return a +} diff --git a/serverIDsigner/readConfig.go b/serverIDsigner/readConfig.go new file mode 100755 index 0000000..b132f33 --- /dev/null +++ b/serverIDsigner/readConfig.go @@ -0,0 +1,26 @@ +package main + +import ( + "encoding/json" + "io/ioutil" +) + +//Config reads the config +type Config struct { + IP string `json:"ip"` + Port string `json:"port"` + Mongodb MongoConfig `json:"mongodb"` +} +type MongoConfig struct { + IP string `json:"ip"` + Database string `json:"database"` +} + +var config Config + +func readConfig(path string) { + file, err := ioutil.ReadFile(path) + check(err) + content := string(file) + json.Unmarshal([]byte(content), &config) +} diff --git a/serverIDsigner/restConfig.go b/serverIDsigner/restConfig.go new file mode 100755 index 0000000..36a332e --- /dev/null +++ b/serverIDsigner/restConfig.go @@ -0,0 +1,47 @@ +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gorilla/mux" +) + +type Route struct { + Name string + Method string + Pattern string + HandlerFunc http.HandlerFunc +} + +func Logger(inner http.Handler, name string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + inner.ServeHTTP(w, r) + + log.Printf( + "%s\t%s\t%s\t%s", + r.Method, + r.RequestURI, + name, + time.Since(start), + ) + }) +} +func NewRouter() *mux.Router { + router := mux.NewRouter().StrictSlash(true) + for _, route := range routes { + var handler http.Handler + handler = route.HandlerFunc + handler = Logger(handler, route.Name) + + router. + Methods(route.Method). + Path(route.Pattern). + Name(route.Name). + Handler(handler) + } + return router +} diff --git a/serverIDsigner/restRoutes.go b/serverIDsigner/restRoutes.go new file mode 100755 index 0000000..ddbfeef --- /dev/null +++ b/serverIDsigner/restRoutes.go @@ -0,0 +1,36 @@ +package main + +type Routes []Route + +var routes = Routes{ + Route{ + "Index", + "GET", + "/", + Index, + }, + Route{ + "Signup", + "POST", + "/signup", + Signup, + }, + Route{ + "Login", + "POST", + "/login", + Login, + }, + Route{ + "BlindSign", + "POST", + "/blindsign", + BlindSign, + }, + Route{ + "VerifySign", + "POST", + "/verifysign", + VerifySign, + }, +} diff --git a/serverIDsigner/testUser.sh b/serverIDsigner/testUser.sh new file mode 100644 index 0000000..521765b --- /dev/null +++ b/serverIDsigner/testUser.sh @@ -0,0 +1,20 @@ +echo "" +echo "sending the signup, response:" +curl -X POST http://127.0.0.1:3130/signup -d '{"email": "user1@e.com", "password": "user1"}' + +echo "" +echo "sending the login, response:" +curl -X POST http://127.0.0.1:3130/login -d '{"email": "user1@e.com", "password": "user1"}' + + +echo "" +echo "send pubK and m to blind sign" +echo "json to send to the serverIDsigner:" +echo '{"m": "hola"}' +echo "serverIDsigner response:" +BLINDSIGNED=$(curl -X POST http://127.0.0.1:3130/blindsign -d '{"m": "hola"}') +echo "$BLINDSIGNED" + +echo "" +echo "send blindsigned to the serverIDsigner to verify" +curl -X POST http://127.0.0.1:3130/verifysign -d '{"m": "hola", "mSigned": "131898 40373 107552 34687"}' diff --git a/serverIDsigner/tokens.go b/serverIDsigner/tokens.go new file mode 100644 index 0000000..94db69c --- /dev/null +++ b/serverIDsigner/tokens.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + "time" + + jwt "github.com/dgrijalva/jwt-go" +) + +const ( + signingKey = "this is the secret signing key" +) + +var createdToken string + +func initializeToken() { + var err error + createdToken, err = newToken() + if err != nil { + fmt.Println("Creating token failed") + } +} + +func newToken() (string, error) { + signingKeyB := []byte(signingKey) + // Create the token + token := jwt.New(jwt.SigningMethodHS256) + // Set some claims + claims := make(jwt.MapClaims) + claims["foo"] = "bar" + claims["exp"] = time.Now().Add(time.Hour * 72).Unix() + token.Claims = claims + + // Sign and get the complete encoded token as a string + tokenString, err := token.SignedString(signingKeyB) + return tokenString, err +} + +func parseToken(myToken string, myKey string) { + token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) { + return []byte(myKey), nil + }) + + if err == nil && token.Valid { + fmt.Println("Your token is valid. I like your style.") + } else { + fmt.Println("This token is terrible! I cannot accept this.") + } +} diff --git a/serverIDsigner/userRESTFunctions.go b/serverIDsigner/userRESTFunctions.go new file mode 100644 index 0000000..d6aa91a --- /dev/null +++ b/serverIDsigner/userRESTFunctions.go @@ -0,0 +1,186 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/fatih/color" + "gopkg.in/mgo.v2/bson" + + ownrsa "./ownrsa" +) + +type User struct { + Id bson.ObjectId `json:"id" bson:"_id,omitempty"` + Email string `json:"email"` + Password string `json:"password"` + Token string `json:"token"` +} + +func Index(w http.ResponseWriter, r *http.Request) { + //TODO return the public key, to allow others verifign signed strings by this server + + jResp, err := json.Marshal(serverRSA.PubK) + if err != nil { + panic(err) + } + fmt.Fprintln(w, string(jResp)) +} + +func Signup(w http.ResponseWriter, r *http.Request) { + + decoder := json.NewDecoder(r.Body) + var user User + err := decoder.Decode(&user) + if err != nil { + panic(err) + } + defer r.Body.Close() + + fmt.Print("user signup: ") + fmt.Println(user) + + //save the new project to mongodb + rUser := User{} + err = userCollection.Find(bson.M{"email": user.Email}).One(&rUser) + if err != nil { + //user not exists + err = userCollection.Insert(user) //TODO find a way to get the object result when inserting in one line, without need of the two mgo petitions + err = userCollection.Find(bson.M{"email": user.Email}).One(&user) + } else { + //user exists + fmt.Fprintln(w, "User already registered") + return + } + + jResp, err := json.Marshal(user) + if err != nil { + panic(err) + } + fmt.Fprintln(w, string(jResp)) +} + +func Login(w http.ResponseWriter, r *http.Request) { + + decoder := json.NewDecoder(r.Body) + var user User + err := decoder.Decode(&user) + if err != nil { + panic(err) + } + defer r.Body.Close() + //TODO check if the user password exists in the database + + fmt.Print("user login: ") + fmt.Println(user) + token, err := newToken() + check(err) + user.Token = token + + //save the new project to mongodb + rUser := User{} + err = userCollection.Find(bson.M{"email": user.Email}).One(&rUser) + if err != nil { + } else { + //user exists, update with the token + err = userCollection.Update(bson.M{"_id": rUser.Id}, user) + check(err) + } + + jResp, err := json.Marshal(user) + if err != nil { + panic(err) + } + fmt.Fprintln(w, string(jResp)) +} + +type Sign struct { + M string `json:"m"` + C string `json:"c"` +} + +type AskBlindSign struct { + /*PubKString ownrsa.RSAPublicKeyString `json:"pubKstring"` + PubK ownrsa.RSAPublicKey `json:"pubK"`*/ + M string `json:"m"` +} + +func BlindSign(w http.ResponseWriter, r *http.Request) { + decoder := json.NewDecoder(r.Body) + var askBlindSign AskBlindSign + err := decoder.Decode(&askBlindSign) + if err != nil { + panic(err) + } + defer r.Body.Close() + color.Red(askBlindSign.M) + fmt.Println(askBlindSign) + + /*fmt.Println(askBlindSign) + askBlindSign.PubK, err = ownrsa.PubKStringToBigInt(askBlindSign.PubKString) + if err != nil { + fmt.Fprintln(w, "error") + return + }*/ + + //convert msg to []int + /*var m []int + mBytes := []byte(askBlindSign.M) + for _, byte := range mBytes { + m = append(m, int(byte)) + }*/ + + m := ownrsa.StringToArrayInt(askBlindSign.M, "_") + + sigma := ownrsa.BlindSign(m, serverRSA.PrivK) //here the privK will be the CA privK, not the m emmiter's one. The pubK is the user's one + fmt.Print("Sigma': ") + fmt.Println(sigma) + sigmaString := ownrsa.ArrayIntToString(sigma, "_") + askBlindSign.M = sigmaString + + jResp, err := json.Marshal(askBlindSign) + if err != nil { + panic(err) + } + fmt.Fprintln(w, string(jResp)) +} + +type PetitionVerifySign struct { + M string `json:"m"` + MSigned string `json:"mSigned"` +} + +func VerifySign(w http.ResponseWriter, r *http.Request) { + decoder := json.NewDecoder(r.Body) + var petitionVerifySign PetitionVerifySign + err := decoder.Decode(&petitionVerifySign) + if err != nil { + panic(err) + } + defer r.Body.Close() + + fmt.Println(petitionVerifySign) + + //convert M to []int + var mOriginal []int + mBytes := []byte(petitionVerifySign.M) + for _, byte := range mBytes { + mOriginal = append(mOriginal, int(byte)) + } + + //convert MSigned to []int + var mSignedInts []int + mSignedString := strings.Split(petitionVerifySign.MSigned, " ") + for _, s := range mSignedString { + i, err := strconv.Atoi(s) + check(err) + mSignedInts = append(mSignedInts, i) + } + + verified := ownrsa.Verify(mOriginal, mSignedInts, serverRSA.PubK) + + fmt.Fprintln(w, verified) +}