├── .gitignore ├── .gitmodules ├── CREDITS ├── LICENSE ├── Makefile ├── README.md ├── client ├── LICENSE ├── README.md ├── apple-touch-icon-precomposed.png ├── basic.html ├── favicon.ico ├── fonts │ ├── RobotoCondensed-Bold.eot │ ├── RobotoCondensed-Bold.ttf │ ├── RobotoCondensed-Bold.woff │ ├── RobotoCondensed-BoldItalic.eot │ ├── RobotoCondensed-BoldItalic.ttf │ ├── RobotoCondensed-BoldItalic.woff │ ├── RobotoCondensed-Italic.eot │ ├── RobotoCondensed-Italic.ttf │ ├── RobotoCondensed-Italic.woff │ ├── RobotoCondensed-Light.eot │ ├── RobotoCondensed-Light.ttf │ ├── RobotoCondensed-Light.woff │ ├── RobotoCondensed-LightItalic.eot │ ├── RobotoCondensed-LightItalic.ttf │ ├── RobotoCondensed-LightItalic.woff │ ├── RobotoCondensed-Regular.eot │ ├── RobotoCondensed-Regular.ttf │ └── RobotoCondensed-Regular.woff ├── humans.txt ├── images │ ├── hamburger.svg │ ├── icons │ │ ├── icons-hinted.ttf │ │ ├── icons.eot │ │ ├── icons.svg │ │ ├── icons.ttf │ │ ├── icons.woff │ │ ├── icons.woff2 │ │ ├── placeholder--medium.png │ │ ├── placeholder--small.png │ │ └── placeholder--wide.png │ └── touch │ │ ├── chrome-touch-icon-196x196.png │ │ ├── icon-128x128.png │ │ └── ms-touch-icon-144x144-precomposed.png ├── index.html ├── manifest.webapp ├── robots.txt ├── scripts │ ├── README.md │ ├── app.js │ ├── bower.json │ ├── colorbox.ai │ ├── colorbox.jquery.json │ ├── i18n │ │ ├── jquery.colorbox-ar.js │ │ ├── jquery.colorbox-bg.js │ │ ├── jquery.colorbox-ca.js │ │ ├── jquery.colorbox-cs.js │ │ ├── jquery.colorbox-da.js │ │ ├── jquery.colorbox-de.js │ │ ├── jquery.colorbox-es.js │ │ ├── jquery.colorbox-et.js │ │ ├── jquery.colorbox-fa.js │ │ ├── jquery.colorbox-fi.js │ │ ├── jquery.colorbox-fr.js │ │ ├── jquery.colorbox-gl.js │ │ ├── jquery.colorbox-gr.js │ │ ├── jquery.colorbox-he.js │ │ ├── jquery.colorbox-hr.js │ │ ├── jquery.colorbox-hu.js │ │ ├── jquery.colorbox-id.js │ │ ├── jquery.colorbox-it.js │ │ ├── jquery.colorbox-ja.js │ │ ├── jquery.colorbox-kr.js │ │ ├── jquery.colorbox-lt.js │ │ ├── jquery.colorbox-lv.js │ │ ├── jquery.colorbox-my.js │ │ ├── jquery.colorbox-nl.js │ │ ├── jquery.colorbox-no.js │ │ ├── jquery.colorbox-pl.js │ │ ├── jquery.colorbox-pt-br.js │ │ ├── jquery.colorbox-ro.js │ │ ├── jquery.colorbox-ru.js │ │ ├── jquery.colorbox-si.js │ │ ├── jquery.colorbox-sk.js │ │ ├── jquery.colorbox-sr.js │ │ ├── jquery.colorbox-sv.js │ │ ├── jquery.colorbox-tr.js │ │ ├── jquery.colorbox-uk.js │ │ ├── jquery.colorbox-zh-CN.js │ │ └── jquery.colorbox-zh-TW.js │ ├── jquery.colorbox.js │ ├── jquery.min.js │ └── main.js └── styles │ ├── app.css │ ├── colorbox.css │ ├── components │ ├── _components │ │ ├── _articles-list.scss │ │ ├── _breadcrumb.scss │ │ ├── _button.scss │ │ ├── _column-list.scss │ │ ├── _grid.scss │ │ ├── _guides-list.scss │ │ ├── _icon-circle.scss │ │ ├── _icons.scss │ │ ├── _link.scss │ │ ├── _list.scss │ │ ├── _media.scss │ │ ├── _subsection-title.scss │ │ ├── _table.scss │ │ └── _typography.scss │ ├── _global.scss │ ├── _helper.scss │ ├── _modules │ │ ├── _article-nav.scss │ │ ├── _articles-section.scss │ │ ├── _did-you-know.scss │ │ ├── _editorial-header.scss │ │ ├── _featured-section.scss │ │ ├── _featured-spotlight.scss │ │ ├── _guides-section.scss │ │ ├── _highlight.scss │ │ ├── _in-this-guide.scss │ │ ├── _next-lessons.scss │ │ ├── _page-header.scss │ │ ├── _quote.scss │ │ ├── _related-guides.scss │ │ ├── _related-items.scss │ │ ├── _summary-header.scss │ │ └── _toc.scss │ ├── _normalize.scss │ ├── _pages │ │ ├── _page-resources.scss │ │ └── _styleguide.scss │ ├── _themed.scss │ ├── _utils.scss │ ├── components.css │ └── components.scss │ ├── h5bp.css │ ├── images │ ├── border.png │ ├── controls.png │ ├── loading.gif │ ├── loading_background.png │ └── overlay.png │ └── main.css ├── script ├── build.sh ├── msg.conf.example ├── start.sh ├── stop.sh └── test.sh └── src └── emp ├── EMP.go ├── api ├── api.go ├── api_test.go ├── aux.go └── config.go ├── db ├── db.go ├── db_test.go ├── error.go ├── hashlist.go └── objects.go ├── encryption ├── address.go ├── encrypted_data.go ├── guid.go ├── message.go ├── message_test.go └── symmetric.go ├── local ├── localapi │ ├── localapi.go │ ├── rpcfunc.go │ └── rpcmsg.go └── localdb │ ├── dbAdmin.go │ └── localdb.go └── objects ├── address.go ├── interface.go ├── localmsg.go ├── message.go ├── node.go ├── obj.go ├── objects_test.go ├── pubkey.go ├── purge.go └── version.go /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | pkg/ 3 | src/github.com/ 4 | src/golang.org/ 5 | src/*.local 6 | *~ 7 | *.db 8 | distro 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/quibit"] 2 | path = src/quibit 3 | url = git@github.com:encryptedmessaging/quibit.git 4 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | Ethan Gordon 2 | Alex Smith 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, JARST LLC. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | script/build.sh 3 | start: 4 | script/start.sh 5 | stop: 6 | script/stop.sh 7 | clean: stop 8 | rm -rf bin 9 | rm -rf pkg 10 | rm -rf ~/.config/emp/log/* 11 | 12 | clobber: clean 13 | rm -rf src/github.com 14 | rm -rf src/code.google.com 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EMP 2 | ===== 3 | EMP is a fully encrypted, distributed messaging service designed with speed in mind. 4 | Originally based off of BitMessage, EMP makes modifications to the API to include 5 | both Read Receipts that Purge the network of read messages, and an extra identification field 6 | to prevent clients from having to decrypt every single incoming message. 7 | 8 | **You can check out some more detailed information in the GitHub Wiki!** 9 | 10 | Submodules 11 | ---------- 12 | 13 | This repository contains submodules. You will need to run: 14 | ``` 15 | git submodule init 16 | git submodule update 17 | ``` 18 | 19 | Required Tools 20 | --------- 21 | In order to compile and run this software, you will need: 22 | 23 | * The [Go Compiler (gc)](http://golang.org/doc/install) 24 | * For Downloading Dependencies: [Git](http://git-scm.com/book/en/Getting-Started-Installing-Git) 25 | * For Downloading Dependencies: [Mercurial](http://mercurial.selenic.com/wiki/Download) 26 | 27 | Building and Launching 28 | --------- 29 | 30 | * `make build` will install the daemon to ./bin/emp 31 | * `make start` will set up the config directory at ~/.config/emp/, then build and run the daemon, outputting to the log file at ~/.config/emp/log/log_ 32 | * `make stop` will stop any existing emp daemon 33 | * `make clean` will remove all build packages and log files 34 | * `make clobber` will also remove all the dependency sources 35 | 36 | **Running as root user is NOT recommended!** 37 | 38 | Configuration 39 | --------- 40 | All configuration is found in `~/.config/emp/msg.conf`, which is installed automatically with `make start`. An example is found in `./script/msg.conf.example`. The example should be good for most users, but if you plan on running a "backbone" node, make sure to add your external IP to msg.conf in order to have it circulated around the network. 41 | 42 | Debian/Ubuntu Installation 43 | --------- 44 | * Add the APT repository with `add-apt-repository 'deb http://emp.jar.st/repos/apt/debian unstable main'` 45 | * Download and install the JARST GPG Key with `wget -O key http://emp.jar.st/repos/apt/debian/conf/jarst.gpg.key && sudo apt-key add key; rm -f key` 46 | * Update the APT Database: `sudo apt-get update` 47 | * You can now install EMP with `sudo apt-get install emp` 48 | 49 | **Note:** 50 | Configuration of the Debian installation will be stored in `/usr/share/emp/` instead of the home directory. 51 | 52 | Support 53 | --------- 54 | Support is available through our [Google group](https://groups.google.com/forum/#!forum/encryptedmessaging). 55 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # [![Web Starter Kit](https://cloud.githubusercontent.com/assets/170270/3343034/ceef6e92-f899-11e3-96b9-5d9d69d97a00.png)](https://github.com/google/web-starter-kit/releases/latest) 2 | 3 | 4 | ## Overview 5 | 6 | [Web Starter Kit](http://developers.google.com/web/starter-kit) is a starting point for multi-screen web development. It encompasses opinionated recommendations on boilerplate and tooling for building an experience that [works great across multiple devices](http://google.github.io/web-starter-kit/hello-world/). We help you stay productive and aligned with the best practices outlined in Google's [Web Fundamentals](http://developers.google.com/web/fundamentals). 7 | 8 | [![](https://cloud.githubusercontent.com/assets/170270/3343033/ceee251e-f899-11e3-9dd9-e313cf2522ec.png)](https://developers.google.com/web/starter-kit/ 'Features') 9 | 10 | ## Quickstart 11 | 12 | [Download](https://github.com/google/web-starter-kit/releases/latest) the kit or clone this repository and build on what we include in the `app` directory. 13 | 14 | We provide 2 HTML starting points, from which you can choose: 15 | 16 | - `index.html` - the default starting point, containing layout and a slide-out menu 17 | - `basic.html` - includes no layout 18 | 19 | ## Tooling 20 | 21 | If you would like to use the optional tooling we provide, make sure your system has [Node.js](http://nodejs.org), [Ruby](https://www.ruby-lang.org/), [gulp.js](http://gulpjs.com) and [Sass](http://sass-lang.com/install) installed. 22 | 23 | ### Node 24 | 25 | Let's check to see if you already have Node installed. Bring up a terminal and type `node --version`. If Node responds, and if it shows a version at or above v0.10.x, proceed to checking if you have Ruby installed too. If you require Node, go to [nodejs.org](http://nodejs.org/) and click on the big green Install button. 26 | 27 | ### Ruby 28 | 29 | Bring up a terminal and type `ruby --version`. If Ruby responds, and if it shows a version number at or above 1.8.7 then type `gem --version`. If you don't see any errors, proceed to installing the Sass gem. If you require Ruby, it can be installed from the [Ruby downloads](https://www.ruby-lang.org/en/downloads/) page. 30 | 31 | ### Sass 32 | 33 | Bring up a terminal and type `sass --version`. If Sass is installed it should return a version number at or above 3.3.x. If you don't see any errors, proceed to the Gulp installation. If you need to install Sass, see the command-line instructions on the [Sass installation](http://sass-lang.com/install) page. 34 | 35 | ### Gulp 36 | 37 | Bring up a terminal and type `gulp --version`. If Gulp is installed it should return a version number at or above 3.5.x. If you don't see any errors, proceed to the Gulp commands section. If you need to install Gulp, open up a terminal and type in the following: 38 | 39 | ```sh 40 | $ npm install --global gulp 41 | ``` 42 | 43 | This will install Gulp globally. Depending on your user account, you may need to gain elevated permissions using `sudo` (i.e `sudo npm install --global gulp`). Next, install the local dependencies Web Starter Kit requires: 44 | 45 | ```sh 46 | $ npm install 47 | ``` 48 | 49 | That's it! You should now have everything needed to use the Gulp tools in Web Starter Kit. 50 | 51 | ### Gulp Commands 52 | 53 | You can now use Gulp with the following commands to stay productive during development: 54 | 55 | #### Watch For Changes & Automatically Refresh Across Devices 56 | 57 | ```sh 58 | $ gulp serve 59 | ``` 60 | 61 | This outputs an IP address you can use to locally test and another that can be used on devices connected to your network. 62 | 63 | ### Build & Optimize 64 | 65 | ```sh 66 | $ gulp 67 | ``` 68 | 69 | Build and optimize the current project, ready for deployment. This includes linting as well as image, script, stylesheet and HTML optimization and minification. 70 | 71 | #### Performance Insights 72 | 73 | ```sh 74 | $ gulp pagespeed 75 | ``` 76 | 77 | Runs the deployed (public) version of your site against the [PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/) API to help you stay on top of where you can improve. 78 | 79 | ## Web Performance 80 | 81 | Web Starter Kit strives to give you a high performance starting point out of the box and we actively work on delivering the best [PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/) score and frame-rate possible. 82 | 83 | In terms of CSS, opting to just use the minimal layout (main.css, h5bp.css) weighs in at ~7KB before modifications are made. Opting to use the Style Guide styles (the default) will take this up to ~39KB. It is your choice which path makes the most sense for your project, however notes on excluding Style Guide styles are in our gulpfile. 84 | 85 | ## Browser Support 86 | 87 | At present, we officially aim to support the following browsers: 88 | 89 | * IE10, IE11, IE Mobile 10 90 | * FF 30, 31 91 | * Chrome 34, 35 92 | * Safari 7, 8 93 | * Opera 23, 24 94 | * iOS Safari 7, 8 95 | * Opera Coast 96 | * Android / Chrome 4.4, 4.4.3 97 | * BlackBerry 10 98 | 99 | This is not to say that Web Starter Kit cannot be used in browsers older than those reflected, but merely that our focus will be on ensuring our layouts work great in the above. 100 | 101 | ## Troubleshooting 102 | 103 | If you find yourself running into issues during installation or running the tools, please check our [Troubleshooting](https://github.com/google/web-starter-kit/wiki/Troubleshooting) guide and then open an [issue](https://github.com/google/web-starter-kit/issues). We would be happy to discuss how they can be solved. 104 | 105 | ## A Boilerplate-only Option 106 | 107 | If you would prefer not to use any of our tooling, delete the following files from the project: `package.json`, `gulpfile.js`, `.jshintrc` and `.travis.yml`. You can now safely use the boilerplate with an alternative build-system or no build-system at all if you choose. 108 | 109 | ## Inspiration 110 | 111 | Web Starter Kit is inspired by [Mobile HTML5 Boilerplate](http://html5boilerplate.com/mobile/) and Yeoman's [generator-gulp-webapp](https://github.com/yeoman/generator-gulp-webapp), having taken input from contributors to both projects during development. Our [FAQs](https://github.com/google/web-starter-kit/wiki/FAQ) attempt to answer commonly asked questions about the project. 112 | 113 | ## Contributing 114 | 115 | Contributions, questions and comments are all welcome and encouraged. For code contributions to Web Starter Kit, please see our [Contribution guide](CONTRIBUTING.md) before submitting a pull request. [Website](https://developers.google.com/web/starter-kit/) related issues should be filed on the [Web Fundamentals](https://github.com/google/WebFundamentals/issues/new) issue tracker. 116 | 117 | ## License 118 | 119 | Apache 2.0 120 | Copyright 2014 Google Inc 121 | -------------------------------------------------------------------------------- /client/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /client/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/favicon.ico -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-Bold.eot -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-Bold.ttf -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-Bold.woff -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-BoldItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-BoldItalic.eot -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-BoldItalic.ttf -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-BoldItalic.woff -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-Italic.eot -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-Italic.ttf -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-Italic.woff -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-Light.eot -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-Light.ttf -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-Light.woff -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-LightItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-LightItalic.eot -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-LightItalic.ttf -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-LightItalic.woff -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-Regular.eot -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-Regular.ttf -------------------------------------------------------------------------------- /client/fonts/RobotoCondensed-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/fonts/RobotoCondensed-Regular.woff -------------------------------------------------------------------------------- /client/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | -- -- 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | HTML5, CSS3 15 | -------------------------------------------------------------------------------- /client/images/hamburger.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/images/icons/icons-hinted.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/images/icons/icons-hinted.ttf -------------------------------------------------------------------------------- /client/images/icons/icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/images/icons/icons.eot -------------------------------------------------------------------------------- /client/images/icons/icons.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/images/icons/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/images/icons/icons.ttf -------------------------------------------------------------------------------- /client/images/icons/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/images/icons/icons.woff -------------------------------------------------------------------------------- /client/images/icons/icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/images/icons/icons.woff2 -------------------------------------------------------------------------------- /client/images/icons/placeholder--medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/images/icons/placeholder--medium.png -------------------------------------------------------------------------------- /client/images/icons/placeholder--small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/images/icons/placeholder--small.png -------------------------------------------------------------------------------- /client/images/icons/placeholder--wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/images/icons/placeholder--wide.png -------------------------------------------------------------------------------- /client/images/touch/chrome-touch-icon-196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/images/touch/chrome-touch-icon-196x196.png -------------------------------------------------------------------------------- /client/images/touch/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/images/touch/icon-128x128.png -------------------------------------------------------------------------------- /client/images/touch/ms-touch-icon-144x144-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/images/touch/ms-touch-icon-144x144-precomposed.png -------------------------------------------------------------------------------- /client/manifest.webapp: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "name": "Web Starter Kit", 4 | "launch_path": "/index.html", 5 | "description": "A front-end template that helps you build fast, modern mobile web apps.", 6 | "icons": { 7 | "128": "/images/touch/icon-128x128.png" 8 | }, 9 | "developer": { 10 | "name": "", 11 | "url": "" 12 | }, 13 | "installs_allowed_from": [ 14 | "*" 15 | ], 16 | "default_locale": "en", 17 | "permissions": { 18 | }, 19 | "locales": { 20 | "en": { 21 | "name": "Web Starter Kit", 22 | "description": "A front-end template that helps you build fast, modern mobile web apps." 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /client/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | 3 | # Allow crawling of all content 4 | User-agent: * 5 | Disallow: 6 | -------------------------------------------------------------------------------- /client/scripts/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-colorbox", 3 | "description": "jQuery lightbox and modal window plugin", 4 | "version": "1.5.10", 5 | "dependencies": { 6 | "jquery": ">=1.3.2" 7 | }, 8 | "keywords": [ 9 | "modal", 10 | "lightbox", 11 | "window", 12 | "popup", 13 | "ui", 14 | "jQuery" 15 | ], 16 | "authors": [ 17 | { 18 | "name": "Jack Moore", 19 | "url": "http://www.jacklmoore.com", 20 | "email": "hello@jacklmoore.com" 21 | } 22 | ], 23 | "licenses": [ 24 | { 25 | "type": "MIT", 26 | "url": "http://www.opensource.org/licenses/mit-license.php" 27 | } 28 | ], 29 | "homepage": "http://www.jacklmoore.com/colorbox", 30 | "main": "jquery.colorbox.js", 31 | "ignore": [ 32 | "colorbox.jquery.json", 33 | "colorbox.ai", 34 | "content", 35 | "example1/index.html", 36 | "example2/index.html", 37 | "example3/index.html", 38 | "example4/index.html", 39 | "example5/index.html" 40 | ] 41 | } -------------------------------------------------------------------------------- /client/scripts/colorbox.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/scripts/colorbox.ai -------------------------------------------------------------------------------- /client/scripts/colorbox.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colorbox", 3 | "title": "Colorbox", 4 | "description": "jQuery lightbox and modal window plugin", 5 | "version": "1.5.10", 6 | "dependencies": { 7 | "jquery": ">=1.3.2" 8 | }, 9 | "keywords": [ 10 | "modal", 11 | "lightbox", 12 | "window", 13 | "popup", 14 | "ui", 15 | "jQuery" 16 | ], 17 | "author": { 18 | "name": "Jack Moore", 19 | "url": "http://www.jacklmoore.com", 20 | "email": "hello@jacklmoore.com" 21 | }, 22 | "licenses": [ 23 | { 24 | "type": "MIT", 25 | "url": "http://www.opensource.org/licenses/mit-license.php" 26 | } 27 | ], 28 | "homepage": "http://www.jacklmoore.com/colorbox", 29 | "demo": "http://www.jacklmoore.com/colorbox" 30 | } -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-ar.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Arabic (ar) 4 | translated by: A.Rhman Sayes 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "الصورة {current} من {total}", 8 | previous: "السابق", 9 | next: "التالي", 10 | close: "إغلاق", 11 | xhrError: "حدث خطأ أثناء تحميل المحتوى.", 12 | imgError: "حدث خطأ أثناء تحميل الصورة.", 13 | slideshowStart: "تشغيل العرض", 14 | slideshowStop: "إيقاف العرض" 15 | }); 16 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-bg.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Bulgarian (bg) 4 | translated by: Marian M.Bida 5 | site: webmax.bg 6 | */ 7 | jQuery.extend(jQuery.colorbox.settings, { 8 | current: "изображение {current} от {total}", 9 | previous: "предишна", 10 | next: "следваща", 11 | close: "затвори", 12 | xhrError: "Неуспешно зареждане на съдържанието.", 13 | imgError: "Неуспешно зареждане на изображението.", 14 | slideshowStart: "пусни слайд-шоу", 15 | slideshowStop: "спри слайд-шоу" 16 | }); 17 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-ca.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Catala (ca) 4 | translated by: eduard salla 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "Imatge {current} de {total}", 8 | previous: "Anterior", 9 | next: "Següent", 10 | close: "Tancar", 11 | xhrError: "Error en la càrrega del contingut.", 12 | imgError: "Error en la càrrega de la imatge." 13 | }); 14 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-cs.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Czech (cs) 4 | translated by: Filip Novak 5 | site: mame.napilno.cz/filip-novak 6 | */ 7 | jQuery.extend(jQuery.colorbox.settings, { 8 | current: "{current}. obrázek z {total}", 9 | previous: "Předchozí", 10 | next: "Následující", 11 | close: "Zavřít", 12 | xhrError: "Obsah se nepodařilo načíst.", 13 | imgError: "Obrázek se nepodařilo načíst.", 14 | slideshowStart: "Spustit slideshow", 15 | slideshowStop: "Zastavit slideshow" 16 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-da.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Danish (da) 4 | translated by: danieljuhl 5 | site: danieljuhl.dk 6 | */ 7 | jQuery.extend(jQuery.colorbox.settings, { 8 | current: "Billede {current} af {total}", 9 | previous: "Forrige", 10 | next: "Næste", 11 | close: "Luk", 12 | xhrError: "Indholdet fejlede i indlæsningen.", 13 | imgError: "Billedet fejlede i indlæsningen.", 14 | slideshowStart: "Start slideshow", 15 | slideshowStop: "Stop slideshow" 16 | }); 17 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-de.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: German (de) 4 | translated by: wallenium 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "Bild {current} von {total}", 8 | previous: "Zurück", 9 | next: "Vor", 10 | close: "Schließen", 11 | xhrError: "Dieser Inhalt konnte nicht geladen werden.", 12 | imgError: "Dieses Bild konnte nicht geladen werden.", 13 | slideshowStart: "Slideshow starten", 14 | slideshowStop: "Slideshow anhalten" 15 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-es.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Spanish (es) 4 | translated by: migolo 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "Imagen {current} de {total}", 8 | previous: "Anterior", 9 | next: "Siguiente", 10 | close: "Cerrar", 11 | xhrError: "Error en la carga del contenido.", 12 | imgError: "Error en la carga de la imagen." 13 | }); 14 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-et.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Estonian (et) 4 | translated by: keevitaja 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "{current}/{total}", 8 | previous: "eelmine", 9 | next: "järgmine", 10 | close: "sulge", 11 | xhrError: "Sisu ei õnnestunud laadida.", 12 | imgError: "Pilti ei õnnestunud laadida.", 13 | slideshowStart: "Käivita slaidid", 14 | slideshowStop: "Peata slaidid" 15 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-fa.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Persian (Farsi) 4 | translated by: Mahdi Jaberzadeh Ansari (MJZSoft) 5 | site: www.mjzsoft.ir 6 | email: mahdijaberzadehansari (at) yahoo.co.uk 7 | Please note : Persian language is right to left like arabic. 8 | */ 9 | jQuery.extend(jQuery.colorbox.settings, { 10 | current: "تصویر {current} از {total}", 11 | previous: "قبلی", 12 | next: "بعدی", 13 | close: "بستن", 14 | xhrError: "متاسفانه محتویات مورد نظر قابل نمایش نیست.", 15 | imgError: "متاسفانه بارگذاری این عکس با مشکل مواجه شده است.", 16 | slideshowStart: "آغاز نمایش خودکار", 17 | slideshowStop: "توقف نمایش خودکار" 18 | }); 19 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-fi.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Finnish (fi) 4 | translated by: Mikko 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "Kuva {current} / {total}", 8 | previous: "Edellinen", 9 | next: "Seuraava", 10 | close: "Sulje", 11 | xhrError: "Sisällön lataaminen epäonnistui.", 12 | imgError: "Kuvan lataaminen epäonnistui.", 13 | slideshowStart: "Aloita kuvaesitys.", 14 | slideshowStop: "Lopeta kuvaesitys." 15 | }); 16 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-fr.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: French (fr) 4 | translated by: oaubert 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "image {current} sur {total}", 8 | previous: "précédente", 9 | next: "suivante", 10 | close: "fermer", 11 | xhrError: "Impossible de charger ce contenu.", 12 | imgError: "Impossible de charger cette image.", 13 | slideshowStart: "démarrer la présentation", 14 | slideshowStop: "arrêter la présentation" 15 | }); 16 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-gl.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Galician (gl) 4 | translated by: donatorouco 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "Imaxe {current} de {total}", 8 | previous: "Anterior", 9 | next: "Seguinte", 10 | close: "Pechar", 11 | xhrError: "Erro na carga do contido.", 12 | imgError: "Erro na carga da imaxe." 13 | }); 14 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-gr.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Greek (gr) 4 | translated by: S.Demirtzoglou 5 | site: webiq.gr 6 | */ 7 | jQuery.extend(jQuery.colorbox.settings, { 8 | current: "Εικόνα {current} από {total}", 9 | previous: "Προηγούμενη", 10 | next: "Επόμενη", 11 | close: "Απόκρυψη", 12 | xhrError: "Το περιεχόμενο δεν μπόρεσε να φορτωθεί.", 13 | imgError: "Απέτυχε η φόρτωση της εικόνας.", 14 | slideshowStart: "Έναρξη παρουσίασης", 15 | slideshowStop: "Παύση παρουσίασης" 16 | }); 17 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-he.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Hebrew (he) 4 | translated by: DavidCo 5 | site: DavidCo.me 6 | */ 7 | jQuery.extend(jQuery.colorbox.settings, { 8 | current: "תמונה {current} מתוך {total}", 9 | previous: "הקודם", 10 | next: "הבא", 11 | close: "סגור", 12 | xhrError: "שגיאה בטעינת התוכן.", 13 | imgError: "שגיאה בטעינת התמונה.", 14 | slideshowStart: "התחל מצגת", 15 | slideshowStop: "עצור מצגת" 16 | }); 17 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-hr.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Croatian (hr) 4 | translated by: Mladen Bicanic (base.hr) 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "Slika {current} od {total}", 8 | previous: "Prethodna", 9 | next: "Sljedeća", 10 | close: "Zatvori", 11 | xhrError: "Neuspješno učitavanje sadržaja.", 12 | imgError: "Neuspješno učitavanje slike.", 13 | slideshowStart: "Pokreni slideshow", 14 | slideshowStop: "Zaustavi slideshow" 15 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-hu.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Hungarian (hu) 4 | translated by: kovadani 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "{current}/{total} kép", 8 | previous: "Előző", 9 | next: "Következő", 10 | close: "Bezár", 11 | xhrError: "A tartalmat nem sikerült betölteni.", 12 | imgError: "A képet nem sikerült betölteni.", 13 | slideshowStart: "Diavetítés indítása", 14 | slideshowStop: "Diavetítés leállítása" 15 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-id.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Indonesian (id) 4 | translated by: sarwasunda 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "ke {current} dari {total}", 8 | previous: "Sebelumnya", 9 | next: "Berikutnya", 10 | close: "Tutup", 11 | xhrError: "Konten ini tidak dapat dimuat.", 12 | imgError: "Gambar ini tidak dapat dimuat.", 13 | slideshowStart: "Putar", 14 | slideshowStop: "Berhenti" 15 | }); 16 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-it.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Italian (it) 4 | translated by: maur8ino 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "Immagine {current} di {total}", 8 | previous: "Precedente", 9 | next: "Successiva", 10 | close: "Chiudi", 11 | xhrError: "Errore nel caricamento del contenuto.", 12 | imgError: "Errore nel caricamento dell'immagine.", 13 | slideshowStart: "Inizia la presentazione", 14 | slideshowStop: "Termina la presentazione" 15 | }); 16 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-ja.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Japanaese (ja) 4 | translated by: Hajime Fujimoto 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "{total}枚中{current}枚目", 8 | previous: "前", 9 | next: "次", 10 | close: "閉じる", 11 | xhrError: "コンテンツの読み込みに失敗しました", 12 | imgError: "画像の読み込みに失敗しました", 13 | slideshowStart: "スライドショー開始", 14 | slideshowStop: "スライドショー終了" 15 | }); 16 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-kr.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Korean (kr) 4 | translated by: lunareffect 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "총 {total} 중 {current}", 8 | previous: "이전", 9 | next: "다음", 10 | close: "닫기", 11 | xhrError: "컨텐츠를 불러오는 데 실패했습니다.", 12 | imgError: "이미지를 불러오는 데 실패했습니다.", 13 | slideshowStart: "슬라이드쇼 시작", 14 | slideshowStop: "슬라이드쇼 중지" 15 | }); 16 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-lt.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Lithuanian (lt) 4 | translated by: Tomas Norkūnas 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "Nuotrauka {current} iš {total}", 8 | previous: "Atgal", 9 | next: "Pirmyn", 10 | close: "Uždaryti", 11 | xhrError: "Nepavyko užkrauti turinio.", 12 | imgError: "Nepavyko užkrauti nuotraukos.", 13 | slideshowStart: "Pradėti automatinę peržiūrą", 14 | slideshowStop: "Sustabdyti automatinę peržiūrą" 15 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-lv.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Latvian (lv) 4 | translated by: Matiss Roberts Treinis 5 | site: x0.lv 6 | */ 7 | jQuery.extend(jQuery.colorbox.settings, { 8 | current: "attēls {current} no {total}", 9 | previous: "iepriekšējais", 10 | next: "nākamais", 11 | close: "aizvērt", 12 | xhrError: "Neizdevās ielādēt saturu.", 13 | imgError: "Neizdevās ielādēt attēlu.", 14 | slideshowStart: "sākt slaidrādi", 15 | slideshowStop: "apturēt slaidrādi" 16 | }); 17 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-my.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Myanmar (my) 4 | translated by: Yan Naing 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "ပုံ {total} မှာ {current} မြောက်ပုံ", 8 | previous: "ရှေ့သို့", 9 | next: "နောက်သို့", 10 | close: "ပိတ်မည်", 11 | xhrError: "ပါဝင်သော အကြောင်းအရာများ ဖော်ပြရာတွင် အနည်းငယ် ချို့ယွင်းမှုရှိနေပါသည်", 12 | imgError: "ပုံပြသရာတွင် အနည်းငယ် ချို့ယွင်းချက် ရှိနေပါသည်", 13 | slideshowStart: "ပုံများ စတင်ပြသမည်", 14 | slideshowStop: "ပုံပြသခြင်း ရပ်ဆိုင်မည်" 15 | }); 16 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-nl.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Dutch (nl) 4 | translated by: barryvdh 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "Afbeelding {current} van {total}", 8 | previous: "Vorige", 9 | next: "Volgende", 10 | close: "Sluiten", 11 | xhrError: "Deze inhoud kan niet geladen worden.", 12 | imgError: "Deze afbeelding kan niet geladen worden.", 13 | slideshowStart: "Diashow starten", 14 | slideshowStop: "Diashow stoppen" 15 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-no.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Norwegian (no) 4 | translated by: lars-erik 5 | site: markedspartner.no 6 | */ 7 | jQuery.extend(jQuery.colorbox.settings, { 8 | current: "Bilde {current} av {total}", 9 | previous: "Forrige", 10 | next: "Neste", 11 | close: "Lukk", 12 | xhrError: "Feil ved lasting av innhold.", 13 | imgError: "Feil ved lasting av bilde.", 14 | slideshowStart: "Start lysbildefremvisning", 15 | slideshowStop: "Stopp lysbildefremvisning" 16 | }); 17 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-pl.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Polski (pl) 4 | translated by: Tomasz Wasiński 5 | site: 2bevisible.pl 6 | */ 7 | jQuery.extend(jQuery.colorbox.settings, { 8 | current: "{current}. obrazek z {total}", 9 | previous: "Poprzedni", 10 | next: "Następny", 11 | close: "Zamknij", 12 | xhrError: "Nie udało się załadować treści.", 13 | imgError: "Nie udało się załadować obrazka.", 14 | slideshowStart: "rozpocznij pokaz slajdów", 15 | slideshowStop: "zatrzymaj pokaz slajdów" 16 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-pt-br.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Brazilian Portuguese (pt-br) 4 | translated by: ReinaldoMT 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "Imagem {current} de {total}", 8 | previous: "Anterior", 9 | next: "Próxima", 10 | close: "Fechar", 11 | slideshowStart: "iniciar apresentação de slides", 12 | slideshowStop: "parar apresentação de slides", 13 | xhrError: "Erro ao carregar o conteúdo.", 14 | imgError: "Erro ao carregar a imagem." 15 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-ro.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Romanian (ro) 4 | translated by: shurub3l 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "imagine {current} din {total}", 8 | previous: "precedenta", 9 | next: "următoarea", 10 | close: "închideți", 11 | xhrError: "Acest conținut nu poate fi încărcat.", 12 | imgError: "Această imagine nu poate fi încărcată", 13 | slideshowStart: "începeți prezentarea (slideshow)", 14 | slideshowStop: "opriți prezentarea (slideshow)" 15 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-ru.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Russian (ru) 4 | translated by: Marfa 5 | site: themarfa.name 6 | */ 7 | jQuery.extend(jQuery.colorbox.settings, { 8 | current: "изображение {current} из {total}", 9 | previous: "назад", 10 | next: "вперёд", 11 | close: "закрыть", 12 | xhrError: "Не удалось загрузить содержимое.", 13 | imgError: "Не удалось загрузить изображение.", 14 | slideshowStart: "начать слайд-шоу", 15 | slideshowStop: "остановить слайд-шоу" 16 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-si.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Slovenian (si) 4 | translated by: Boštjan Pišler (pisler.si) 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "Slika {current} od {total}", 8 | previous: "Prejšnja", 9 | next: "Naslednja", 10 | close: "Zapri", 11 | xhrError: "Vsebine ni bilo mogoče naložiti.", 12 | imgError: "Slike ni bilo mogoče naložiti.", 13 | slideshowStart: "Zaženi prezentacijo", 14 | slideshowStop: "Zaustavi prezentacijo" 15 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-sk.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Slovak (sk) 4 | translated by: Jaroslav Kostal 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "{current}. obrázok z {total}", 8 | previous: "Predchádzajúci", 9 | next: "Následujúci", 10 | close: "Zatvoriť", 11 | xhrError: "Obsah sa nepodarilo načítať.", 12 | imgError: "Obrázok sa nepodarilo načítať.", 13 | slideshowStart: "Spustiť slideshow", 14 | slideshowStop: "zastaviť slideshow" 15 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-sr.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Serbian (sr) 4 | translated by: Sasa Stefanovic (baguje.com) 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "Slika {current} od {total}", 8 | previous: "Prethodna", 9 | next: "Sledeća", 10 | close: "Zatvori", 11 | xhrError: "Neuspešno učitavanje sadržaja.", 12 | imgError: "Neuspešno učitavanje slike.", 13 | slideshowStart: "Pokreni slideshow", 14 | slideshowStop: "Zaustavi slideshow" 15 | }); 16 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-sv.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Swedish (sv) 4 | translated by: Mattias Reichel 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "Bild {current} av {total}", 8 | previous: "Föregående", 9 | next: "Nästa", 10 | close: "Stäng", 11 | xhrError: "Innehållet kunde inte laddas.", 12 | imgError: "Den här bilden kunde inte laddas.", 13 | slideshowStart: "Starta bildspel", 14 | slideshowStop: "Stoppa bildspel" 15 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-tr.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Turkish (tr) 4 | translated by: Caner ÖNCEL 5 | site: egonomik.com 6 | 7 | edited by: Sinan Eldem 8 | www.sinaneldem.com.tr 9 | */ 10 | jQuery.extend(jQuery.colorbox.settings, { 11 | current: "Görsel {current} / {total}", 12 | previous: "Önceki", 13 | next: "Sonraki", 14 | close: "Kapat", 15 | xhrError: "İçerik yüklenirken hata meydana geldi.", 16 | imgError: "Resim yüklenirken hata meydana geldi.", 17 | slideshowStart: "Slaytı Başlat", 18 | slideshowStop: "Slaytı Durdur" 19 | }); 20 | -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-uk.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery ColorBox language configuration 3 | language: Ukrainian (uk) 4 | translated by: Andrew 5 | http://acisoftware.com.ua 6 | */ 7 | jQuery.extend(jQuery.colorbox.settings, { 8 | current: "зображення {current} з {total}", 9 | previous: "попереднє", 10 | next: "наступне", 11 | close: "закрити", 12 | xhrError: "Не вдалося завантажити вміст.", 13 | imgError: "Не вдалося завантажити зображення.", 14 | slideshowStart: "почати слайд-шоу", 15 | slideshowStop: "зупинити слайд-шоу" 16 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-zh-CN.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Chinese Simplified (zh-CN) 4 | translated by: zhao weiming 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "当前图像 {current} 总共 {total}", 8 | previous: "前一页", 9 | next: "后一页", 10 | close: "关闭", 11 | xhrError: "此内容无法加载", 12 | imgError: "此图片无法加载", 13 | slideshowStart: "开始播放幻灯片", 14 | slideshowStop: "停止播放幻灯片" 15 | }); -------------------------------------------------------------------------------- /client/scripts/i18n/jquery.colorbox-zh-TW.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Colorbox language configuration 3 | language: Chinese Traditional (zh-TW) 4 | translated by: Atans Chiu 5 | */ 6 | jQuery.extend(jQuery.colorbox.settings, { 7 | current: "圖片 {current} 總共 {total}", 8 | previous: "上一頁", 9 | next: "下一頁", 10 | close: "關閉", 11 | xhrError: "此內容加載失敗.", 12 | imgError: "此圖片加載失敗.", 13 | slideshowStart: "開始幻燈片", 14 | slideshowStop: "結束幻燈片" 15 | }); -------------------------------------------------------------------------------- /client/scripts/main.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 3 | * Web Starter Kit 4 | * Copyright 2014 Google Inc. All rights reserved. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License 17 | * 18 | */ 19 | (function () { 20 | 'use strict'; 21 | 22 | var querySelector = document.querySelector.bind(document); 23 | 24 | var navdrawerContainer = querySelector('.navdrawer-container'); 25 | var body = document.body; 26 | var appbarElement = querySelector('.app-bar'); 27 | var menuBtn = querySelector('.menu'); 28 | var main = querySelector('main'); 29 | 30 | function closeMenu() { 31 | body.classList.remove('open'); 32 | appbarElement.classList.remove('open'); 33 | navdrawerContainer.classList.remove('open'); 34 | } 35 | 36 | function toggleMenu() { 37 | body.classList.toggle('open'); 38 | appbarElement.classList.toggle('open'); 39 | navdrawerContainer.classList.toggle('open'); 40 | } 41 | 42 | main.addEventListener('click', closeMenu); 43 | menuBtn.addEventListener('click', toggleMenu); 44 | navdrawerContainer.addEventListener('click', function (event) { 45 | if (event.target.nodeName === 'A' || event.target.nodeName === 'LI') { 46 | closeMenu(); 47 | } 48 | }); 49 | })(); 50 | -------------------------------------------------------------------------------- /client/styles/app.css: -------------------------------------------------------------------------------- 1 | .datarow :hover { 2 | cursor: pointer !important; 3 | cursor: hand !important; 4 | } 5 | 6 | .formText { 7 | width: 90%; 8 | } 9 | 10 | #status { 11 | position: fixed; 12 | bottom: 5px; 13 | right: 5px; 14 | width: 25px; 15 | height: 25px; 16 | } -------------------------------------------------------------------------------- /client/styles/colorbox.css: -------------------------------------------------------------------------------- 1 | /* 2 | Colorbox Core Style: 3 | The following CSS is consistent between example themes and should not be altered. 4 | */ 5 | #colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;} 6 | #cboxWrapper {max-width:none;} 7 | #cboxOverlay{position:fixed; width:100%; height:100%;} 8 | #cboxMiddleLeft, #cboxBottomLeft{clear:left;} 9 | #cboxContent{position:relative;} 10 | #cboxLoadedContent{overflow:auto; -webkit-overflow-scrolling: touch;} 11 | #cboxTitle{margin:0;} 12 | #cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%; height:100%;} 13 | #cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;} 14 | .cboxPhoto{float:left; margin:auto; border:0; display:block; max-width:none; -ms-interpolation-mode:bicubic;} 15 | .cboxIframe{width:100%; height:100%; display:block; border:0; padding:0; margin:0;} 16 | #colorbox, #cboxContent, #cboxLoadedContent{box-sizing:content-box; -moz-box-sizing:content-box; -webkit-box-sizing:content-box;} 17 | 18 | /* 19 | User Style: 20 | Change the following styles to modify the appearance of Colorbox. They are 21 | ordered & tabbed in a way that represents the nesting of the generated HTML. 22 | */ 23 | #cboxOverlay{background:url(images/overlay.png) repeat 0 0;} 24 | #colorbox{outline:0;} 25 | #cboxTopLeft{width:21px; height:21px; background:url(images/controls.png) no-repeat -101px 0;} 26 | #cboxTopRight{width:21px; height:21px; background:url(images/controls.png) no-repeat -130px 0;} 27 | #cboxBottomLeft{width:21px; height:21px; background:url(images/controls.png) no-repeat -101px -29px;} 28 | #cboxBottomRight{width:21px; height:21px; background:url(images/controls.png) no-repeat -130px -29px;} 29 | #cboxMiddleLeft{width:21px; background:url(images/controls.png) left top repeat-y;} 30 | #cboxMiddleRight{width:21px; background:url(images/controls.png) right top repeat-y;} 31 | #cboxTopCenter{height:21px; background:url(images/border.png) 0 0 repeat-x;} 32 | #cboxBottomCenter{height:21px; background:url(images/border.png) 0 -29px repeat-x;} 33 | #cboxContent{background:#fff; overflow:hidden;} 34 | .cboxIframe{background:#fff;} 35 | #cboxError{padding:50px; border:1px solid #ccc;} 36 | #cboxLoadedContent{margin-bottom:28px;} 37 | #cboxTitle{position:absolute; bottom:4px; left:0; text-align:center; width:100%; color:#949494;} 38 | #cboxCurrent{position:absolute; bottom:4px; left:58px; color:#949494;} 39 | #cboxLoadingOverlay{background:url(images/loading_background.png) no-repeat center center;} 40 | #cboxLoadingGraphic{background:url(images/loading.gif) no-repeat center center;} 41 | 42 | /* these elements are buttons, and may need to have additional styles reset to avoid unwanted base styles */ 43 | #cboxPrevious, #cboxNext, #cboxSlideshow, #cboxClose {border:0; padding:0; margin:0; overflow:visible; width:auto; background:none; } 44 | 45 | /* avoid outlines on :active (mouseclick), but preserve outlines on :focus (tabbed navigating) */ 46 | #cboxPrevious:active, #cboxNext:active, #cboxSlideshow:active, #cboxClose:active {outline:0;} 47 | 48 | #cboxSlideshow{position:absolute; bottom:4px; right:30px; color:#0092ef;} 49 | #cboxPrevious{position:absolute; bottom:0; left:0; background:url(images/controls.png) no-repeat -75px 0; width:25px; height:25px; text-indent:-9999px;} 50 | #cboxPrevious:hover{background-position:-75px -25px;} 51 | #cboxNext{position:absolute; bottom:0; left:27px; background:url(images/controls.png) no-repeat -50px 0; width:25px; height:25px; text-indent:-9999px;} 52 | #cboxNext:hover{background-position:-50px -25px;} 53 | #cboxClose{position:absolute; bottom:0; right:0; background:url(images/controls.png) no-repeat -25px 0; width:25px; height:25px; text-indent:-9999px;} 54 | #cboxClose:hover{background-position:-25px -25px;} 55 | 56 | /* 57 | The following fixes a problem where IE7 and IE8 replace a PNG's alpha transparency with a black fill 58 | when an alpha filter (opacity change) is set on the element or ancestor element. This style is not applied to or needed in IE9. 59 | See: http://jacklmoore.com/notes/ie-transparency-problems/ 60 | */ 61 | .cboxIE #cboxTopLeft, 62 | .cboxIE #cboxTopCenter, 63 | .cboxIE #cboxTopRight, 64 | .cboxIE #cboxBottomLeft, 65 | .cboxIE #cboxBottomCenter, 66 | .cboxIE #cboxBottomRight, 67 | .cboxIE #cboxMiddleLeft, 68 | .cboxIE #cboxMiddleRight { 69 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#00FFFFFF,endColorstr=#00FFFFFF); 70 | } -------------------------------------------------------------------------------- /client/styles/components/_components/_articles-list.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Articles list 4 | * 5 | **/ 6 | 7 | .articles-list { 8 | padding-left: 0; 9 | } 10 | 11 | .articles-list__item { 12 | 13 | padding-bottom: $lineHeight * 2; 14 | 15 | &:last-child { 16 | padding-bottom: $lineHeight * 2 + 1; 17 | } 18 | 19 | padding-left: 0; 20 | 21 | &::before { 22 | content: ""; 23 | display: block; 24 | width: 40%; 25 | height: 1px; 26 | box-shadow: inset 0 1px 0 0 $colorGrayKeyline; 27 | margin-right: 0; 28 | margin-left: 30%; 29 | } 30 | 31 | h3 { 32 | a:hover { 33 | text-decoration: none; 34 | } 35 | } 36 | 37 | p { 38 | margin-top: $lineHeight; 39 | margin-bottom: $lineHeight; 40 | } 41 | 42 | &:first-child { 43 | padding-top: 0; 44 | 45 | @include medium { 46 | padding-top: $lineHeight - 2; 47 | } 48 | 49 | &::before { 50 | display: none; 51 | } 52 | } 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /client/styles/components/_components/_breadcrumb.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Breadcrumb 4 | * 5 | **/ 6 | 7 | .breadcrumbs { 8 | display: none; 9 | @include medium { 10 | display: block; 11 | } 12 | 13 | position: relative; 14 | z-index: 1; 15 | } 16 | 17 | .breadcrumbs p { 18 | @include type--small; 19 | padding-top: $lineHeight; 20 | } 21 | 22 | .breadcrumbs__link { 23 | @include type--small; 24 | color: black; 25 | font-weight: 400; 26 | padding-top: 0; 27 | 28 | @include medium { 29 | padding-top: 0; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/styles/components/_components/_button.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Button 4 | * 5 | **/ 6 | 7 | .button { 8 | display: inline-block; 9 | padding: (($lineHeight / 2) - 1) 32px; 10 | margin-bottom: $lineHeight / 2; 11 | margin-top: $lineHeight / 2; 12 | min-height: $lineHeight; 13 | 14 | text-align: center; 15 | 16 | font-family: $fontHighlight; 17 | font-weight: 600; 18 | text-decoration: none; 19 | 20 | outline: 0; 21 | 22 | transition: none; 23 | 24 | &:hover { 25 | background: #4d4d4d; 26 | color: #ffffff; 27 | border: 1px solid #4d4d4d; 28 | text-decoration: none; 29 | } 30 | } 31 | 32 | // Mixin to create buttons 33 | @mixin style-button($color, $textColor, $isInverted: false) { 34 | 35 | background: $color; 36 | color: $textColor; 37 | border: 1px solid darken($color, 10%); 38 | 39 | @if $isInverted { border-color: transparent;} 40 | } 41 | 42 | 43 | .button--primary { 44 | @extend .button; 45 | @include style-button(#4285f4, #ffffff); 46 | } 47 | 48 | .button--secondary { 49 | @extend .button; 50 | @include style-button(#ffffff, $colorBlue); 51 | } 52 | 53 | .button--secondary-variation { 54 | @extend .button; 55 | @include style-button(#ffffff, $colorBlue, true); 56 | } 57 | -------------------------------------------------------------------------------- /client/styles/components/_components/_column-list.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Column list 4 | * 5 | **/ 6 | 7 | .columns-list-wrapper { 8 | @include wide { 9 | margin-right: $mediumColWidth; 10 | } 11 | 12 | } 13 | 14 | .list--columns { 15 | border-bottom: 1px solid #ccc; 16 | padding-bottom: $lineHeight - 1; 17 | margin-bottom: 1px; 18 | 19 | @include medium { 20 | column-count: 2; 21 | column-rule: 0 none transparent; 22 | column-gap: 0; 23 | } 24 | 25 | & li, 26 | & .columns-list-item { 27 | @include type--small; 28 | padding: 0; 29 | } 30 | 31 | & a { 32 | display: block; 33 | position: relative; 34 | padding-left: 39px; 35 | 36 | &::before { 37 | line-height: 26px; 38 | } 39 | } 40 | 41 | } 42 | 43 | -------------------------------------------------------------------------------- /client/styles/components/_components/_grid.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Grid 4 | * 5 | **/ 6 | 7 | 8 | @include medium-only { 9 | // Generating grid classes for medium view 10 | @for $i from 1 through $mediumColCount { 11 | .g-medium--#{$i} { 12 | @include rule--col(medium); 13 | width: ($mediumColWidth*$i) + ($mediumGutterWidth*($i + -1)); 14 | @if $i == $mediumColCount { margin-right: 0; } 15 | } 16 | 17 | @if $i < $mediumColCount { 18 | .g-medium--push-#{$i} { 19 | margin-left: ($mediumColWidth*$i) + ($mediumGutterWidth*$i); 20 | } 21 | .g-medium--pull-#{$i} { 22 | margin-right: ($mediumColWidth*$i) + ($mediumGutterWidth*$i); 23 | } 24 | } 25 | } 26 | 27 | .g-medium--full { 28 | @include rule--col(medium); 29 | margin-right: 0; 30 | width: 100%; 31 | } 32 | 33 | .g--third { 34 | @include rule--col(medium); 35 | width: $mediumColWidth; 36 | } 37 | 38 | .g--half, 39 | .g-medium--half { 40 | @include rule--col(medium); 41 | width: 50% - $mediumGutterWidth/2; 42 | } 43 | 44 | .g-medium--last { margin-right: 0; } 45 | .g-medium--last + .g-medium--half { clear: left; } 46 | 47 | .g--pull-half { margin-right: 50% + $mediumGutterWidth/2; } 48 | } 49 | 50 | 51 | @include wide { 52 | // Generating grid classes for wide view 53 | @for $i from 1 through $wideColCount { 54 | .g-wide--#{$i} { 55 | @include rule--col(wide); 56 | width: ($wideColWidth*$i) + ($wideGutterWidth*($i + -1)); 57 | @if $i == $wideColCount { margin-right: 0; } 58 | } 59 | 60 | @if $i < $wideColCount { 61 | .g-wide--push-#{$i} { 62 | margin-left: ($wideColWidth*$i) + ($wideGutterWidth*$i); 63 | } 64 | .g-wide--pull-#{$i} { 65 | margin-right: ($wideColWidth*$i) + ($wideGutterWidth*$i); 66 | } 67 | } 68 | } 69 | 70 | .g-wide--last { margin-right: 0; } 71 | 72 | .g-wide--full { 73 | @include rule--col(wide); 74 | margin-right: 0; 75 | width: 100%; 76 | } 77 | 78 | .g--third { 79 | @include rule--col(wide); 80 | width: 30.8%; 81 | } 82 | 83 | .g--half, 84 | .g-wide--half { 85 | @include rule--col(wide); 86 | width: 50% - $wideGutterWidth/2; 87 | } 88 | 89 | .g--pull-half { margin-right: 50% + $wideGutterWidth/2; } 90 | } 91 | 92 | 93 | // This is a global 'last' class 94 | // to be used with global grid classes, such as 'half' or 'third' 95 | // Example usage: 96 | //
97 | //
98 | //
99 | .g--last { margin-right: 0; } 100 | 101 | 102 | // This is a global 'centered' class 103 | .g--centered { 104 | float: none; // reset float to none so we can center it 105 | margin-left: auto; 106 | margin-right: auto; 107 | } 108 | 109 | // This is a grid overlay 110 | // Its purpose is to show users our grid system 111 | // It becomes visible when a class 'debug' is added to the body 112 | .grid-overlay { 113 | 114 | display: none; 115 | pointer-events: none; 116 | 117 | // Only show when parents (body) has debug class 118 | .debug & { 119 | @include container(true); 120 | 121 | position: absolute; 122 | top: 0; 123 | bottom: 0; 124 | left: 0; 125 | right: 0; 126 | height: 100%; 127 | display: block; 128 | 129 | [class*="g-"] { 130 | height: 100%; 131 | background-color: rgba(lighten(#000, 35%), .2); 132 | } 133 | 134 | @include medium-only { 135 | .g-wide--last { 136 | display: none; 137 | } 138 | } 139 | 140 | @include small-only { 141 | display: none; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /client/styles/components/_components/_guides-list.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Guides List 4 | * 5 | **/ 6 | 7 | 8 | .guides-list { 9 | overflow: hidden; 10 | 11 | @include medium { 12 | display: flex; 13 | justify-content: space-between; 14 | flex-wrap: wrap; 15 | 16 | padding-top: $lineHeight*2; 17 | } 18 | } 19 | 20 | .guides-list__item { 21 | padding: 0; 22 | background: #ffffff; 23 | margin-top: $lineHeight; 24 | margin-bottom: 0; 25 | 26 | @include medium { 27 | display: flex; 28 | flex-direction: column; 29 | flex-wrap: wrap; 30 | } 31 | 32 | h3 { 33 | margin: 0 32px; 34 | } 35 | 36 | p { 37 | margin: $lineHeight 32px 0; 38 | } 39 | 40 | .primary-content { 41 | @include medium { 42 | flex: 1; 43 | } 44 | } 45 | 46 | .secondary-content { 47 | position: relative; 48 | // background: #f5f5f5; 49 | margin-top: ($lineHeight*2) - 1; 50 | border-top: 1px solid $colorGrayKeyline; 51 | 52 | @include medium { 53 | width: 100%; // needed due to parent being flex 54 | } 55 | 56 | .icon-circle { 57 | position: absolute; 58 | top: -$lineHeight - 2; 59 | left: 50%; 60 | margin-left: -21px; 61 | border: 2px solid #ffffff; 62 | 63 | & i { 64 | font-size: 23px; 65 | } 66 | } 67 | } 68 | 69 | ol { 70 | margin: $lineHeight 0 0; 71 | padding: $lineHeight*2 0 $lineHeight*2; 72 | margin-top: 0; 73 | } 74 | 75 | &::before { 76 | display: none; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /client/styles/components/_components/_icon-circle.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Icon Circle 4 | * 5 | **/ 6 | 7 | .icon-circle, 8 | .icon-circle--large { 9 | height: 0; 10 | width: 0; 11 | background: $colorGray; 12 | display: block; 13 | position: relative; 14 | border-radius: 100%; 15 | font-size: 0; 16 | padding: 22px; // Breaks baseline grid 17 | margin: 4px auto; // Adds margin top/bottom to fix baseline grid ;) 18 | 19 | i, 20 | span { 21 | position: absolute; 22 | line-height: 0px; 23 | top: 50%; 24 | width: 100%; 25 | left: 0; 26 | text-align: center; 27 | color: #ffffff; 28 | font-size: $fontLarge; 29 | } 30 | 31 | span { 32 | font-family: $fontHighlight; 33 | font-size: $fontLarge; 34 | font-weight: 700; 35 | 36 | @include medium { 37 | font-size: $fontXLarge; 38 | } 39 | } 40 | } 41 | 42 | .icon-circle--large { 43 | margin-top: 0; 44 | margin-bottom: 0; 45 | padding: $lineHeight; 46 | position: relative; 47 | 48 | i { 49 | font-size: $fontLarge; 50 | 51 | @include medium { 52 | font-size: $fontXLarge; 53 | } 54 | } 55 | 56 | @include medium { 57 | padding: ($lineHeight + $lineHeight/2) - 2; 58 | border: 2px solid #ffffff; 59 | 60 | a & { 61 | padding: ($lineHeight + $lineHeight/2) - 1; 62 | 63 | box-shadow: inset 0px 0px 0px 1px rgba(#ffffff, .42); 64 | border: 1px solid; 65 | 66 | // demo transition 67 | transition: all 100ms linear; 68 | transform: translateZ(0); // kick in hardware acceleration 69 | } 70 | 71 | .no-touch a:hover & { 72 | box-shadow: inset 0px 0px 0px 1px #ffffff; 73 | transform: scale(1.1); 74 | } 75 | } 76 | 77 | } 78 | 79 | .icon-circle--nav { 80 | height: 0; 81 | width: 0; 82 | background: $colorGray; 83 | display: block; 84 | position: relative; 85 | border-radius: 100%; 86 | font-size: 0; 87 | padding: $lineHeight/2; 88 | margin: 0 auto; 89 | 90 | @include medium { 91 | padding: 22px; // Breaks baseline grid 92 | margin-top: 4px; // Adds margin top/bottom to fix baseline grid ;) 93 | margin-bottom: 4px; // Adds margin top/bottom to fix baseline grid ;) 94 | } 95 | 96 | i { 97 | position: absolute; 98 | line-height: 1px; 99 | top: 50%; 100 | width: 100%; 101 | left: 0; 102 | text-align: center; 103 | color: #ffffff; 104 | font-size: $fontBase; 105 | 106 | @include medium { 107 | font-size: $fontLarge; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /client/styles/components/_components/_icons.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family:"icons"; 3 | src:url("../../images/icons/icons.eot"); 4 | src:url("../../images/icons/icons.eot?#iefix") format("embedded-opentype"), 5 | url("../../images/icons/icons.woff2") format("woff2"), 6 | url("../../images/icons/icons.woff") format("woff"), 7 | url("../../images/icons/icons.ttf") format("truetype"), 8 | url("../../images/icons/icons.svg?#icons") format("svg"); 9 | font-weight:normal; 10 | font-style:normal; 11 | } 12 | 13 | 14 | .icon { 15 | font-family:"icons"; 16 | display:inline-block; 17 | vertical-align:top; 18 | line-height:1; 19 | font-weight:normal; 20 | font-style:normal; 21 | speak:none; 22 | text-decoration:inherit; 23 | text-transform:none; 24 | text-rendering:optimizeLegibility; 25 | -webkit-font-smoothing:antialiased; 26 | -moz-osx-font-smoothing:grayscale; 27 | } 28 | 29 | 30 | // Icons 31 | $icon-bullet: "\e001"; 32 | .icon-bullet::before { 33 | content:"\e001"; 34 | } 35 | 36 | $icon-chevron-down: "\e002"; 37 | .icon-chevron-down::before { 38 | content:"\e002"; 39 | } 40 | 41 | $icon-chevron-large: "\e003"; 42 | .icon-chevron-large::before { 43 | content:"\e003"; 44 | } 45 | 46 | $icon-chevron-left: "\e004"; 47 | .icon-chevron-left::before { 48 | content:"\e004"; 49 | } 50 | 51 | $icon-chevron-right: "\e005"; 52 | .icon-chevron-right::before { 53 | content:"\e005"; 54 | } 55 | 56 | $icon-chevron-up: "\e006"; 57 | .icon-chevron-up::before { 58 | content:"\e006"; 59 | } 60 | 61 | $icon-close: "\e007"; 62 | .icon-close::before { 63 | content:"\e007"; 64 | } 65 | 66 | $icon-cog: "\e008"; 67 | .icon-cog::before { 68 | content:"\e008"; 69 | } 70 | 71 | $icon-diamond: "\e009"; 72 | .icon-diamond::before { 73 | content:"\e009"; 74 | } 75 | 76 | $icon-exclamation: "\e00a"; 77 | .icon-exclamation::before { 78 | content:"\e00a"; 79 | } 80 | 81 | $icon-google-dev: "\e00b"; 82 | .icon-google-dev::before { 83 | content:"\e00b"; 84 | } 85 | 86 | $icon-hash: "\e00c"; 87 | .icon-hash::before { 88 | content:"\e00c"; 89 | } 90 | 91 | $icon-introduction-to-media: "\e00d"; 92 | .icon-introduction-to-media::before { 93 | content:"\e00d"; 94 | } 95 | 96 | $icon-lessons: "\e00e"; 97 | .icon-lessons::before { 98 | content:"\e00e"; 99 | } 100 | 101 | $icon-menu: "\e00f"; 102 | .icon-menu::before { 103 | content:"\e00f"; 104 | } 105 | 106 | $icon-minus: "\e010"; 107 | .icon-minus::before { 108 | content:"\e010"; 109 | } 110 | 111 | $icon-multi-device-layouts: "\e011"; 112 | .icon-multi-device-layouts::before { 113 | content:"\e011"; 114 | } 115 | 116 | $icon-performance: "\e012"; 117 | .icon-performance::before { 118 | content:"\e012"; 119 | } 120 | 121 | $icon-plus: "\e013"; 122 | .icon-plus::before { 123 | content:"\e013"; 124 | } 125 | 126 | $icon-question: "\e014"; 127 | .icon-question::before { 128 | content:"\e014"; 129 | } 130 | 131 | $icon-slash: "\e015"; 132 | .icon-slash::before { 133 | content:"\e015"; 134 | } 135 | 136 | $icon-star: "\e016"; 137 | .icon-star::before { 138 | content:"\e016"; 139 | } 140 | 141 | $icon-tick: "\e017"; 142 | .icon-tick::before { 143 | content:"\e017"; 144 | } 145 | 146 | $icon-user-input: "\e018"; 147 | .icon-user-input::before { 148 | content:"\e018"; 149 | } 150 | -------------------------------------------------------------------------------- /client/styles/components/_components/_link.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Link 4 | * 5 | **/ 6 | 7 | a { 8 | color: $colorBlue; 9 | } 10 | 11 | a:hover { 12 | text-decoration: none; 13 | } 14 | 15 | .cta--primary { 16 | @include style-cta($colorBlue, $icon-chevron-right); 17 | } 18 | 19 | .cta--secondary { 20 | @include style-cta($colorBlue); 21 | } 22 | -------------------------------------------------------------------------------- /client/styles/components/_components/_list.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * List 4 | * 5 | **/ 6 | 7 | ul, 8 | ol { 9 | list-style: none; 10 | margin: 0; 11 | 12 | @include small-only { 13 | padding-left: 0; 14 | } 15 | } 16 | 17 | ul li { 18 | position: relative; 19 | padding-left: 16px; 20 | @include bullet-type("", icon-bullet); 21 | 22 | &::before { 23 | font-size: 4px; 24 | } 25 | } 26 | 27 | ol { 28 | counter-reset: list; 29 | > li { 30 | @include numbered-list; 31 | position: relative; 32 | padding-left: 32px; 33 | 34 | // This selected every adjacent
  • 35 | // from the 10th and overrides the 36 | // content property of each 37 | &:nth-child(10n) ~ li::before, 38 | &:nth-child(10n)::before { 39 | content: counter(list); 40 | } 41 | 42 | } 43 | } 44 | 45 | ul ol, 46 | ol ul { 47 | padding-top: 0; 48 | } 49 | 50 | 51 | /*========== LIST LINKS ==========*/ 52 | 53 | ul.list-links { 54 | li::before { 55 | display: none; 56 | } 57 | a { 58 | @include bullet-type("", icon-bullet); 59 | 60 | &::before { 61 | font-size: 4px; 62 | } 63 | 64 | font-weight: 400; 65 | } 66 | 67 | &.list-links--primary { 68 | a { 69 | @include bullet-type("", icon-chevron-right); 70 | font-weight: 400; 71 | font-family: $fontHighlight; 72 | line-height: 1; // fixes baseline grid alignment 73 | text-decoration: none; 74 | } 75 | } 76 | } 77 | 78 | ol.list-links { 79 | li { 80 | &::before { 81 | display: none; 82 | } 83 | 84 | a { 85 | display: inline-block; 86 | @include numbered-list; 87 | font-weight: 300; 88 | } 89 | 90 | &:nth-child(10n) ~ li a::before, 91 | &:nth-child(10n) a::before { 92 | content: counter(list); 93 | } 94 | } 95 | 96 | &.list-links--secondary { 97 | a::before { 98 | display: none; 99 | } 100 | } 101 | } 102 | 103 | .list-links--secondary { 104 | @include type--base; 105 | padding-left: 0; 106 | 107 | li { 108 | padding-left: 0; 109 | } 110 | } 111 | 112 | /*========== ANCHOR LIST ==========*/ 113 | 114 | .list-anchor { 115 | padding-left: 0; 116 | 117 | li { 118 | @include type--base; 119 | padding-top: 0; 120 | padding-left: 0; 121 | 122 | &::before { 123 | display: none 124 | } 125 | 126 | } 127 | 128 | a { 129 | @include bullet-type("", icon-bullet); 130 | line-height: 1; // fixes baseline grid alignment 131 | display: inline-block; 132 | padding-left: 16px; 133 | 134 | &::before { 135 | font-size: 4px; 136 | } 137 | } 138 | } 139 | 140 | /*========== SMALL LIST ==========*/ 141 | 142 | .list-small { 143 | 144 | li { 145 | @include medium { 146 | @include type--small; 147 | padding-top: 0; 148 | } 149 | } 150 | } 151 | 152 | /*========== CENTERED LIST ==========*/ 153 | 154 | .list-centered { 155 | text-align: center; 156 | padding-left: 0; 157 | } 158 | 159 | /*========== FEATURED LIST ==========*/ 160 | 161 | .featured-list { 162 | padding-top: $lineHeight * 3; 163 | padding-bottom: $lineHeight * 3; 164 | } 165 | 166 | .featured-list__item { 167 | background: #ffffff; 168 | padding-left: 0; 169 | padding-top: $lineHeight; 170 | padding-bottom: $lineHeight; 171 | 172 | @include medium { 173 | min-height: $lineHeight * 13; 174 | padding: $lineHeight * 2 32px; 175 | } 176 | 177 | margin-top: $lineHeight; 178 | 179 | &:first-child { 180 | margin-top: 0; 181 | } 182 | 183 | p { 184 | margin-bottom: $lineHeight; 185 | } 186 | } 187 | 188 | .featured-list__img-wrapper { 189 | display: none; 190 | position: relative; 191 | padding-top: $lineHeight; 192 | margin: 0 -5%; 193 | 194 | @include medium { 195 | display: block; 196 | padding-top: 0; 197 | margin: 0; 198 | } 199 | } 200 | 201 | .featured-list__img { 202 | @include medium { 203 | padding-top: 60.8%; 204 | padding-bottom: 0; 205 | height: 0; 206 | overflow: hidden; 207 | position: absolute; 208 | width: 100%; 209 | 210 | } 211 | 212 | img { 213 | display: block; 214 | margin: 0 auto; 215 | max-width: 100%; 216 | 217 | @include medium { 218 | margin: 0; 219 | position: absolute; 220 | top: 0; 221 | height: 100%; 222 | width: 100%; 223 | left: 0; 224 | } 225 | } 226 | } 227 | 228 | /*========== RELATED GUIDES LIST ==========*/ 229 | .related-guides-list { 230 | font-family: $fontHighlight; 231 | padding-top: 0; 232 | padding-left: 0; 233 | 234 | @include medium { 235 | padding-top: $lineHeight 236 | } 237 | 238 | @include wide { 239 | padding-top: 0 240 | } 241 | 242 | p { 243 | padding-top: 0; 244 | } 245 | 246 | .tag { 247 | padding-top: 0; 248 | } 249 | 250 | li { 251 | padding-top: $lineHeight; 252 | padding-bottom: $lineHeight - 1; 253 | border-bottom: 1px solid $colorGrayKeyline; 254 | 255 | &:last-child { 256 | border-color: transparent; 257 | } 258 | 259 | @include medium { 260 | padding-top: 0; 261 | padding-bottom: 0; 262 | border-color: transparent; 263 | } 264 | } 265 | } 266 | 267 | /*========== LIST RESET ==========*/ 268 | 269 | .list--reset { 270 | padding-left: 0; 271 | 272 | li { 273 | padding-left: 0; 274 | } 275 | 276 | &.list-links a::before, 277 | & li::before { 278 | display: none !important; // Fine to use !important when we are forcing an override 279 | } 280 | } 281 | 282 | 283 | /*========== LESSONS ==========*/ 284 | .list-lessons { 285 | padding-left: 0; 286 | 287 | & a { 288 | color: #ffffff; 289 | } 290 | 291 | & .current { 292 | &, 293 | a { 294 | text-decoration: none; 295 | cursor: default; 296 | } 297 | 298 | & .icon { 299 | font-size: $fontSmall; 300 | display: inline-block; 301 | background: rgba(#000000, .2); 302 | border-radius: 100%; 303 | width: 26px; 304 | line-height: 26px; 305 | text-align: center; 306 | margin-left: 7px; 307 | } 308 | } 309 | 310 | } 311 | 312 | 313 | /*========== GUIDES INTO - used on homepage ==========*/ 314 | .list-guides-intro { 315 | margin-bottom: $lineHeight*2; 316 | 317 | @include small-only { 318 | padding-top: $lineHeight*2; 319 | } 320 | 321 | li { 322 | border-bottom: 1px solid $colorGrayKeyline; 323 | padding-bottom: ($lineHeight*2) - 1; 324 | margin-bottom: ($lineHeight*2); 325 | 326 | @include medium { 327 | border-color: transparent; 328 | padding-bottom: 0; 329 | } 330 | 331 | &:last-child { 332 | border-bottom: transparent; 333 | margin-bottom: 0; 334 | } 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /client/styles/components/_components/_media.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Media - imgs/videos 4 | * 5 | **/ 6 | 7 | img, 8 | video, 9 | object { 10 | max-width: 100%; 11 | } 12 | 13 | img { 14 | 15 | .content & { 16 | margin-top: $lineHeight; 17 | margin-bottom: $lineHeight; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/styles/components/_components/_subsection-title.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * subsection__title 4 | * 5 | **/ 6 | 7 | .subsection-title { 8 | color: $colorGrayDark; 9 | margin-top: $lineHeight * 2; 10 | } 11 | 12 | .subsection-number { 13 | @include type--base; 14 | padding-top: 0; 15 | display: block; 16 | } 17 | -------------------------------------------------------------------------------- /client/styles/components/_components/_table.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Table 4 | * 5 | **/ 6 | 7 | table { 8 | margin-top: $lineHeight; 9 | width: 100%; 10 | 11 | thead { 12 | background: $colorBlue; 13 | color: #ffffff; 14 | } 15 | 16 | th { 17 | text-align: center; 18 | display: none; 19 | font-family: $fontHighlight; 20 | @include type--medium; 21 | } 22 | 23 | tr { 24 | @include medium { 25 | border-bottom: 1px solid #ffffff; 26 | } 27 | } 28 | 29 | tbody { 30 | background: $colorGrayBackground; 31 | } 32 | 33 | td { 34 | display: block; 35 | padding-top: $lineHeight/2; 36 | padding-bottom: $lineHeight/2; 37 | 38 | // This is to re-plicate the table-headers for mobile 39 | &::before { 40 | content: attr(data-th) " :"; 41 | display: inline-block; 42 | color: #ffffff; 43 | background: $colorBlue; 44 | border-right: 2px solid #ffffff; 45 | position: absolute; 46 | top: 0; 47 | left: 0; 48 | bottom: 0; 49 | width: 100px; 50 | max-height: 100%; 51 | font-family: $fontHighlight; 52 | font-size: 16px; 53 | font-weight: 400; 54 | padding-left: $lineHeight/2; 55 | padding-top: $lineHeight/2; 56 | 57 | @include medium { 58 | display: none; 59 | } 60 | } 61 | } 62 | 63 | th, 64 | td { 65 | position: relative; 66 | padding-left: 140px; 67 | 68 | @include medium { 69 | display: table-cell; 70 | } 71 | } 72 | 73 | th { 74 | @include medium { 75 | padding: $lineHeight; 76 | padding-top: 13px; 77 | padding-bottom: $lineHeight/2 - 1; 78 | } 79 | } 80 | 81 | td { 82 | @include medium { 83 | padding: $lineHeight; 84 | padding-bottom: $lineHeight - 1; 85 | } 86 | } 87 | } 88 | 89 | td:last-child::after { 90 | content: ""; 91 | display: block; 92 | background: #ffffff; 93 | height: 1px; 94 | left: 0; 95 | position: absolute; 96 | bottom: 0; 97 | width: 100%; 98 | @include medium { 99 | display: none; 100 | } 101 | } 102 | 103 | .table-2 { 104 | col { 105 | width: $mediumContainer / 2; 106 | @include wide { 107 | width: $wideContainer / 2; 108 | } 109 | } 110 | 111 | th, 112 | td { 113 | @include medium { 114 | &:first-child { 115 | border-right: 2px solid #ffffff; 116 | } 117 | } 118 | } 119 | } 120 | 121 | .table-3 { 122 | col { 123 | width: $mediumContainer / 3; 124 | @include wide { 125 | width: $wideContainer / 3; 126 | } 127 | } 128 | 129 | th, 130 | td { 131 | @include medium { 132 | &:nth-child(2) { 133 | border-left: 2px solid #ffffff; 134 | border-right: 2px solid #ffffff; 135 | } 136 | } 137 | } 138 | } 139 | 140 | .table-4 { 141 | col { 142 | width: $mediumContainer / 4; 143 | @include wide { 144 | width: $wideContainer / 4; 145 | } 146 | } 147 | 148 | th, 149 | td { 150 | @include medium { 151 | &:nth-child(2), 152 | &:nth-child(3) { 153 | border-left: 2px solid #ffffff; 154 | border-right: 2px solid #ffffff; 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /client/styles/components/_components/_typography.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Typography 4 | * 5 | **/ 6 | 7 | // Just normalizing text 8 | // Recommend using padding instead of margin 9 | h1, h2, h3, h4, h5, p { 10 | margin: 0; 11 | } 12 | 13 | // Definitions 14 | .small, 15 | small { 16 | @include type--small; 17 | } 18 | 19 | .base, 20 | p, 21 | ul, 22 | ol { 23 | @include type--base; 24 | } 25 | 26 | .medium, 27 | h4 { 28 | @include type--medium; 29 | } 30 | 31 | .large, 32 | h3 { 33 | @include type--large; 34 | } 35 | .xlarge, 36 | h2 { 37 | @include type--xlarge; 38 | } 39 | 40 | .xxlarge, 41 | h1 { 42 | @include type--xxlarge; 43 | } 44 | 45 | .huge { 46 | @include type--huge; 47 | } 48 | 49 | 50 | li > p { 51 | padding-top: 0; 52 | } 53 | -------------------------------------------------------------------------------- /client/styles/components/_global.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Global 4 | * 5 | **/ 6 | 7 | *, *::before, *::after { 8 | box-sizing: border-box; 9 | } 10 | 11 | html, body, button { 12 | -webkit-font-smoothing: antialiased; 13 | font-smoothing: antialiased; 14 | } 15 | 16 | body { 17 | font-family: $fontDefault; 18 | font-size: $fontBase; 19 | line-height: 1.6250em; /* 26px */ 20 | font-weight: 300; // to thicken it a bit, we need to remove font-smoothing 21 | color: $colorText; 22 | 23 | @include baseline-grid(); 24 | } 25 | 26 | pre { 27 | background: $colorGrayBackground; 28 | padding: 13px; 29 | } 30 | 31 | .main-container { 32 | @include container(true); 33 | } 34 | 35 | .container { 36 | @include container(true); 37 | } 38 | 39 | .container-medium { 40 | @include medium { 41 | @include container(true); 42 | } 43 | } 44 | 45 | .container-small { 46 | @include small-only { 47 | @include container(true); 48 | } 49 | } 50 | 51 | .content { 52 | @include wide { 53 | margin-right: 25.9%; // took this from the grid output - using @extend inside media-query is deprecated, need to figure out a better way to do this. 54 | 55 | & pre { 56 | margin-right: -25.9%; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /client/styles/components/_helper.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Helper 4 | * 5 | **/ 6 | 7 | 8 | .clear { 9 | &::before, 10 | &::after { 11 | content:""; 12 | display:table; 13 | } 14 | &::after { 15 | clear:both; 16 | } 17 | } 18 | 19 | 20 | /*========== COLORS ==========*/ 21 | 22 | .color--blue { color: $colorBlue; } 23 | .color--red { color: $colorRed; } 24 | .color--green { color: $colorGreen; } 25 | .color--yellow { color: $colorYellow; } 26 | .color--blue-secondary { color: $colorBlueSecondary; } 27 | .color--red-secondary { color: $colorRedSecondary; } 28 | .color--green-secondary { color: $colorGreenSecondary; } 29 | .color--yellow-secondary { color: $colorYellowSecondary; } 30 | 31 | .color--gray-background { color: $colorGrayBackground; } 32 | .color--gray-keyline { color: $colorGrayKeyline; } 33 | .color--gray { color: $colorGray; } 34 | .color--gray-dark { color: $colorGrayDark; } 35 | 36 | .color--text { color: $colorText; } 37 | .color--highlight { color: $colorHighlight; } 38 | .color--warning { color: $colorWarning; } 39 | .color--danger { color: $colorDanger; } 40 | .color--muted { color: $colorMuted; } 41 | 42 | .color--remember { color: $colorRemember; } 43 | .color--learning { color: $colorLearning; } 44 | 45 | .color--layouts { color: $colorLayouts; } 46 | .color--user { color: $colorUser; } 47 | .color--media { color: $colorMedia; } 48 | .color--performance { color: $colorPerformance; } 49 | .color--layouts-secondary { color: $colorLayoutsSecondary; } 50 | .color--user-secondary { color: $colorUserSecondary; } 51 | .color--media-secondary { color: $colorMediaSecondary; } 52 | .color--performance-secondary { color: $colorPerformanceSecondary; } 53 | 54 | 55 | 56 | /*========== TEXT DIVIDER ==========*/ 57 | 58 | .text-divider { 59 | position: relative; 60 | margin-bottom: $lineHeight; 61 | 62 | &::after { 63 | content: ""; 64 | display: block; 65 | position: absolute; 66 | width: 40%; 67 | height: 1px; 68 | box-shadow: 0 1px 0 0 $colorGrayKeyline; 69 | left: 30%; 70 | bottom: -$lineHeight/2; 71 | } 72 | 73 | &.xlarge { 74 | margin-bottom: $lineHeight*2; 75 | 76 | &::after { 77 | bottom: -($lineHeight*2)/2; 78 | } 79 | } 80 | 81 | &.xxlarge { 82 | margin-bottom: $lineHeight*3; 83 | 84 | &::after { 85 | bottom: -($lineHeight*3)/2; 86 | } 87 | } 88 | 89 | &.huge { 90 | margin-bottom: $lineHeight*3; 91 | 92 | &::after { 93 | bottom: -($lineHeight*3)/2; 94 | } 95 | } 96 | } 97 | 98 | 99 | /*========== GENERIC ==========*/ 100 | .centered { 101 | text-align: center; 102 | } 103 | 104 | 105 | /*========== TAG ==========*/ 106 | .tag { 107 | @include type--small; 108 | font-family: $fontHighlight; 109 | text-transform: uppercase; 110 | font-weight: 700; 111 | display: inline-block; 112 | text-decoration: none; 113 | 114 | &:hover { 115 | color: $colorGrayDark; 116 | } 117 | 118 | &::before { 119 | content: "# "; 120 | display: inline-block; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_article-nav.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Article nav 4 | * 5 | **/ 6 | 7 | .article-nav { 8 | overflow: hidden; 9 | position: relative; 10 | 11 | &::before { 12 | content: ""; 13 | border-left: 2px solid $colorGrayKeyline; 14 | height: 100%; 15 | position: absolute; 16 | top: 0; 17 | left: 50%; 18 | } 19 | } 20 | 21 | .article-nav-link { 22 | padding: $lineHeight 32px; 23 | float: left; 24 | width: 50%; 25 | position: relative; 26 | 27 | &::before{ 28 | position: absolute; 29 | top: 21px; 30 | font-family: $fontHighlight; 31 | font-size: $fontMedium; 32 | font-weight: 400; 33 | 34 | @include medium { 35 | top: 25px; 36 | font-size: $fontLarge; 37 | display: block; 38 | padding: 13px 10px; 39 | color: #ffffff; 40 | background: $colorBlue; 41 | } 42 | } 43 | } 44 | 45 | .article-nav p { 46 | padding: 0; 47 | margin: 0; 48 | } 49 | 50 | .article-nav-link--prev { 51 | text-align: right; 52 | // border-right-width: 1px; 53 | 54 | &::before { 55 | font-family: $fontIcon; 56 | @extend .icon-chevron-left::before; 57 | left: 32px; 58 | } 59 | 60 | p { 61 | @include medium { 62 | padding-left: 52px; 63 | } 64 | } 65 | } 66 | 67 | .article-nav-link--next { 68 | // border-left-width: 1px; 69 | 70 | &::before { 71 | font-family: $fontIcon; 72 | @extend .icon-chevron-right::before; 73 | right: 32px; 74 | } 75 | 76 | p { 77 | @include medium { 78 | padding-right: 52px; 79 | } 80 | } 81 | } 82 | 83 | .article-nav-count { 84 | @include type--large; 85 | font-weight: 700; 86 | @include medium {font-weight: 400;} 87 | } 88 | 89 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_articles-section.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Articles section 4 | * 5 | **/ 6 | 7 | .articles-section { 8 | background: $colorGrayBackground; 9 | text-align: center; 10 | padding: $lineHeight 0 $lineHeight*4; 11 | } 12 | 13 | .articles-count { 14 | color: $colorBlue; 15 | font-family: $fontHighlight; 16 | font-weight: 400; 17 | } 18 | 19 | .article-section__icon { 20 | top: -($lineHeight); 21 | 22 | @include medium { 23 | top: -($lineHeight + $lineHeight/2); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_did-you-know.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Text module 4 | * 5 | **/ 6 | 7 | .did-you-know { 8 | 9 | ol { 10 | @include medium { 11 | padding-top: 0 !important; 12 | } 13 | } 14 | 15 | .cta--primary { 16 | margin-top: $lineHeight; 17 | font-weight: 500; 18 | } 19 | 20 | &>.g--half { 21 | position: relative; 22 | padding-left: 0; 23 | @include medium {padding-left: 32px} 24 | } 25 | } 26 | 27 | .did-you-know__symbol { 28 | padding-bottom: $lineHeight*12; 29 | @include medium {padding-bottom: $lineHeight} 30 | 31 | &::after { 32 | content: $icon-question; 33 | color: $colorBlue; 34 | font-family: $fontIcon; 35 | font-size: 300px; 36 | top: 150px; 37 | left: 30%; 38 | position: relative; 39 | display: block; 40 | width: 0; 41 | 42 | @include medium { 43 | position: absolute; 44 | font-size: 400px; 45 | top: 200px; 46 | left: 110%; 47 | } 48 | 49 | @include wide { 50 | position: absolute; 51 | font-size: 400px; 52 | top: 200px; 53 | left: 124%; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_editorial-header.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Editorial Header 4 | * 5 | **/ 6 | 7 | .editorial-header { 8 | overflow: hidden; 9 | 10 | .breadcrumbs { 11 | color: $colorBlue; 12 | 13 | a { 14 | color: $colorBlue; 15 | } 16 | } 17 | 18 | .container { 19 | 20 | @include medium { 21 | position: relative; 22 | 23 | // Pseudo elements to add the background characters 24 | &::before { 25 | content: $icon-chevron-large; 26 | font-family: $fontIcon; 27 | font-size: 1000px; 28 | line-height: 0; 29 | display: block; 30 | position: absolute; 31 | top: 0; 32 | right: 100%; 33 | color: $colorGrayBackground; 34 | margin: 168px -35px 0 0; 35 | } 36 | } 37 | } 38 | 39 | } 40 | 41 | .editorial-header__excerpt { 42 | @include type--medium(true); 43 | font-family: $fontHighlight; 44 | } 45 | 46 | .editorial-header .tag{ 47 | padding-top: $lineHeight*2; 48 | } 49 | 50 | .editorial-header__subtitle { 51 | @include type--xxlarge; 52 | padding-top: 0; 53 | @include medium { 54 | padding-top: 0; 55 | padding-bottom: $lineHeight; 56 | } 57 | color: $colorBlue; 58 | } 59 | 60 | .editorial-header__toc { 61 | margin-top: $lineHeight; 62 | 63 | ol { 64 | padding-top: 0; 65 | 66 | @include medium { 67 | padding-top: 0; 68 | } 69 | } 70 | } 71 | 72 | .editorial-header__toc-title { 73 | font-family: $fontHighlight; 74 | border-bottom: 1px solid $colorGrayKeyline; 75 | margin-bottom: 13px; 76 | padding-bottom: 13px !important; 77 | color: $colorBlue; 78 | } 79 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_featured-section.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Editorial Header 4 | * 5 | **/ 6 | 7 | .featured-section { 8 | background: $colorGrayBackground; 9 | } 10 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_featured-spotlight.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Editorial Header 4 | * 5 | **/ 6 | 7 | .featured-spotlight { 8 | background: $colorGrayDark; 9 | color: #ffffff; 10 | overflow: hidden; 11 | padding-bottom: $lineHeight*3 - 1; 12 | margin-top: $lineHeight*2; 13 | 14 | p { 15 | padding-bottom: $lineHeight; 16 | } 17 | 18 | .cta--primary { 19 | color: #ffffff; 20 | 21 | &:hover { 22 | color: #ffffff; 23 | } 24 | } 25 | } 26 | 27 | .featured-spotlight__container { 28 | position: relative; 29 | } 30 | 31 | .featured-spotlight__img { 32 | @include small-only { 33 | padding-top: 58.4%; 34 | padding-bottom: 0; 35 | height: 0; 36 | overflow: hidden; 37 | position: relative; 38 | width: 100%; 39 | } 40 | 41 | img { 42 | margin: 0 auto; 43 | display: block; 44 | width: 100%; 45 | 46 | position: absolute; 47 | left: 0; 48 | top: 0; 49 | margin: 0; 50 | 51 | @include medium { 52 | width: auto; 53 | max-width: none; 54 | left: 100% + $mediumGutterWidth*2; 55 | } 56 | 57 | @include wide { 58 | left: 100% + $wideGutterWidth*2; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_guides-section.scss: -------------------------------------------------------------------------------- 1 | .guides-section { 2 | background: $colorGrayBackground; 3 | text-align: center; 4 | padding: $lineHeight 0 $lineHeight*4; 5 | } 6 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_in-this-guide.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * In this guide 4 | * 5 | **/ 6 | 7 | .in-this-guide { 8 | margin-top: - $lineHeight * 3; 9 | } 10 | 11 | .in-this-guide__title { 12 | @include type--medium(true); 13 | font-family: $fontHighlight; 14 | margin-bottom: $lineHeight; 15 | } 16 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_next-lessons.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Next Lessons 4 | * 5 | **/ 6 | 7 | .next-lessons { 8 | background: $colorGrayDark; 9 | padding: $lineHeight $lineHeight $lineHeight*2; 10 | margin-top: $lineHeight; 11 | color: #ffffff; 12 | position: relative; 13 | 14 | h3 { 15 | i { 16 | @include medium { 17 | display: none; 18 | } 19 | } 20 | } 21 | 22 | &::before, 23 | &::after { 24 | color: rgba(255, 255, 255, 0.5); 25 | position: absolute; 26 | display: none; 27 | 28 | @include medium { 29 | display: inline-block; 30 | } 31 | } 32 | 33 | &::before { 34 | @include medium { 35 | content: attr(data-current-lesson); 36 | 37 | font-family: $fontHighlight; 38 | font-size: $fontBase; 39 | font-weight: 400; 40 | line-height: 1; 41 | 42 | background: $colorGrayDark; 43 | display: inline-block; 44 | padding: 5px 7px; 45 | 46 | right: 127px; 47 | top: 143px; 48 | 49 | z-index: 1; 50 | color: rgba(255, 255, 255, 0.5); 51 | } 52 | 53 | @include wide { 54 | font-size: $fontMedium; 55 | padding-left: 15px; 56 | padding-right: 15px; 57 | top: 126px; 58 | right: 230px; 59 | } 60 | 61 | } 62 | 63 | &::after { 64 | @include medium { 65 | content: $icon-lessons; 66 | font-family: $fontIcon; 67 | font-size: 150px; 68 | 69 | right: 40px; 70 | top: 185px; 71 | } 72 | 73 | @include wide { 74 | font-size: 210px; 75 | right: 120px; 76 | } 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_page-header.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Page header 4 | * 5 | **/ 6 | 7 | .page-header { 8 | text-align: center; 9 | 10 | .breadcrumbs { 11 | text-align: left; 12 | color: $colorBlue; 13 | 14 | a { 15 | color: $colorBlue; 16 | } 17 | } 18 | 19 | h3 { 20 | color: $colorGrayDark; 21 | padding-top: $lineHeight * 2; 22 | } 23 | } 24 | 25 | .page-header__excerpt { 26 | position: relative; 27 | padding-top: 0; 28 | 29 | &:last-child { 30 | padding-bottom: $lineHeight * 3; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_quote.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Quote 4 | * 5 | **/ 6 | 7 | .quote__content { 8 | position: relative; 9 | font-family: $fontHighlight; 10 | @include type--medium; 11 | padding-top: $lineHeight * 4; 12 | padding-left: $lineHeight; 13 | 14 | @include medium { 15 | padding-top: $lineHeight * 2; 16 | padding-left: 0; 17 | } 18 | 19 | p { 20 | border-top: 1px solid $colorGrayKeyline; 21 | text-align: right; 22 | font-weight: 500; 23 | margin-top: $lineHeight/2 - 1; 24 | padding-top: $lineHeight/2; 25 | } 26 | 27 | &::before { 28 | content: open-quote; 29 | display: block; 30 | position: absolute; 31 | font-family: $fontHighlight; 32 | font-weight: 700; 33 | color: $colorGrayBackground; 34 | top: 90px; 35 | left: $lineHeight; 36 | font-size: 260px; 37 | 38 | @include medium { 39 | top: 225px; 40 | left: -210px; 41 | font-size: 540px; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_related-guides.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Related items 4 | * 5 | **/ 6 | 7 | .related-guides { 8 | margin-top: $lineHeight*3; 9 | padding-bottom: ($lineHeight*2) - 2; 10 | border-top: 2px solid $colorGrayKeyline; 11 | padding-top: ($lineHeight*2) - 2; 12 | } 13 | 14 | .related-guides__list { 15 | .list-links { 16 | padding-top: 0; 17 | } 18 | 19 | a { 20 | display: block; 21 | } 22 | } 23 | 24 | .related-guides__title { 25 | @include type--xlarge; 26 | padding-top: 0; 27 | 28 | @include medium { 29 | padding-top: 0; 30 | } 31 | } 32 | 33 | .related-guides__main-link { 34 | text-transform: uppercase; 35 | 36 | &::before { 37 | content: "#"; 38 | display: inline-block; 39 | padding-right: 2px; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_related-items.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Related items 4 | * 5 | **/ 6 | 7 | .related-items { 8 | background-color: $colorGrayDark; 9 | color: #ffffff; 10 | padding-bottom: $lineHeight * 2; 11 | margin-top: $lineHeight * 2; 12 | 13 | .list-links { 14 | a { 15 | color: #ffffff; 16 | } 17 | } 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_summary-header.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Editorial Header 4 | * 5 | **/ 6 | 7 | .summary-header { 8 | background-color: $colorBlue; 9 | padding-bottom: $lineHeight * 3; 10 | color: #ffffff; 11 | margin-bottom: $lineHeight; 12 | box-shadow: inset 0 2px 0 0 #fff; 13 | 14 | .breadcrumbs__link { 15 | color: #ffffff; 16 | } 17 | 18 | } 19 | 20 | .summary-header__anchor-list { 21 | margin-top: $lineHeight * 2; 22 | } 23 | 24 | 25 | .summary-header__anchors-item { 26 | & a { 27 | color: #ffffff; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client/styles/components/_modules/_toc.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Table of contents 4 | * 5 | **/ 6 | 7 | .toc__title { 8 | @include type--medium; 9 | font-family: $fontHighlight; 10 | padding-bottom: $lineHeight/2; 11 | margin-bottom: ($lineHeight/2) - 1; 12 | border-bottom: 1px solid $colorGrayKeyline; 13 | 14 | @include medium { 15 | padding-bottom: $lineHeight/2; 16 | margin-bottom: $lineHeight/2; 17 | } 18 | } 19 | 20 | .toc__list { 21 | padding-top: 0; 22 | 23 | border-bottom: 1px solid $colorGrayKeyline; 24 | padding-bottom: ($lineHeight/2) - 1; 25 | margin-bottom: $lineHeight/2; 26 | 27 | a { 28 | display: block; 29 | } 30 | } 31 | 32 | .toc__sublist { 33 | padding-top: 0; 34 | } 35 | -------------------------------------------------------------------------------- /client/styles/components/_pages/_page-resources.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Resources page 4 | * 5 | **/ 6 | 7 | .page--resources { 8 | & .article-section__icon, 9 | & .articles-count, 10 | & .guides-list__item .secondary-content { 11 | display: none; 12 | } 13 | 14 | & .primary-content { 15 | padding-top: $lineHeight; 16 | padding-bottom: $lineHeight*2; 17 | 18 | p { 19 | margin-top: 0; 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /client/styles/components/_pages/_styleguide.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Styleguide 4 | * 5 | **/ 6 | 7 | .page--styleguide { 8 | 9 | .styleguide__module-title { 10 | margin-bottom: $lineHeight; 11 | } 12 | 13 | section { 14 | margin-bottom: $lineHeight*2; 15 | border-bottom: 1px solid #ccc; 16 | padding-bottom: $lineHeight*3 - 1; 17 | 18 | } 19 | 20 | .styleguide__color-list { 21 | text-align: center; 22 | 23 | li { 24 | border-bottom: $lineHeight*2 solid; 25 | margin-bottom: $lineHeight; 26 | position: relative; 27 | } 28 | } 29 | 30 | .styleguide__breadcrumb .breadcrumbs{ 31 | display: block; 32 | } 33 | 34 | .styleguide__lists { 35 | ul, 36 | ol { 37 | margin-bottom: $lineHeight; 38 | } 39 | } 40 | 41 | .styleguide__inverted-block { 42 | background: #e8e8e8; 43 | padding: 0 13px; 44 | } 45 | 46 | .styleguide__theme-block { 47 | background: $colorLayouts; 48 | padding: 0 13px; 49 | } 50 | 51 | } 52 | 53 | .demo { 54 | margin-bottom: $lineHeight; 55 | margin-top: $lineHeight; 56 | } 57 | 58 | .demo { 59 | [class*="g-"] { 60 | background-color: $colorGrayLight; 61 | position: relative; 62 | margin-bottom: $lineHeight; 63 | min-height: $lineHeight*6; 64 | 65 | &::before, 66 | &::after { 67 | @include type--small; 68 | display: block; 69 | margin: 0 10px; 70 | } 71 | 72 | &::before { 73 | content: "HTML classes: "; 74 | font-weight: 700; 75 | } 76 | 77 | &::after { 78 | content: attr(class); 79 | word-spacing: 15px; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /client/styles/components/_themed.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Themed styles 4 | * 5 | **/ 6 | 7 | .themed { 8 | 9 | .theme--multi-device-layouts & { color: $colorLayouts; } 10 | 11 | .theme--introduction-to-media & { color: $colorMedia; } 12 | 13 | .theme--user-input & { color: $colorUser; } 14 | 15 | .theme--performance & { color: $colorPerformance; } 16 | 17 | } 18 | 19 | .themed--background { 20 | 21 | .theme--multi-device-layouts &, 22 | .theme--multi-device-layouts &.next-lessons::before { background-color: $colorLayouts; } 23 | 24 | .theme--introduction-to-media &, 25 | .theme--introduction-to-media &.next-lessons::before { background-color: $colorMedia; } 26 | 27 | .theme--user-input &, 28 | .theme--user-input &.next-lessons::before { background-color: $colorUser; } 29 | 30 | .theme--performance &, 31 | .theme--performance &.next-lessons::before { background-color: $colorPerformance; } 32 | 33 | } 34 | 35 | .themed--hover { 36 | 37 | .theme--multi-device-layouts &:hover { color: $colorLayouts; } 38 | 39 | .theme--introduction-to-media &:hover { color: $colorMedia; } 40 | 41 | .theme--user-input &:hover { color: $colorUser; } 42 | 43 | .theme--performance &:hover { color: $colorPerformance; } 44 | 45 | } 46 | 47 | .themed--hover-secondary { 48 | 49 | .theme--multi-device-layouts &:hover { color: $colorLayoutsSecondary; } 50 | 51 | .theme--introduction-to-media &:hover { color: $colorMediaSecondary; } 52 | 53 | .theme--user-input &:hover { color: $colorUserSecondary; } 54 | 55 | .theme--performance &:hover { color: $colorPerformanceSecondary; } 56 | 57 | } 58 | 59 | 60 | // Wrapped content in .article-container so we can 61 | // encapsulate what needs to be themed without overriding 62 | // anything outside of this container. 63 | .article-container h1, 64 | .article-container h2 { 65 | 66 | .article--multi-device-layouts & { color: $colorLayouts; } 67 | 68 | .article--introduction-to-media & { color: $colorMedia; } 69 | 70 | .article--user-input & { color: $colorUser; } 71 | 72 | .article--performance & { color: $colorPerformance; } 73 | } 74 | 75 | 76 | // Let's make sure this is at the end so we can override 77 | // previous rules. Specicifity rules. 78 | .themed--hover { 79 | 80 | .nav-theme--multi-device-layouts &:hover { color: $colorLayouts; } 81 | 82 | .nav-theme--introduction-to-media &:hover { color: $colorMedia; } 83 | 84 | .nav-theme--user-input &:hover { color: $colorUser; } 85 | 86 | .nav-theme--performance &:hover { color: $colorPerformance; } 87 | 88 | } 89 | 90 | .themed { 91 | 92 | .nav-theme--multi-device-layouts & { color: $colorLayouts; } 93 | 94 | .nav-theme--introduction-to-media & { color: $colorMedia; } 95 | 96 | .nav-theme--user-input & { color: $colorUser; } 97 | 98 | .nav-theme--performance & { color: $colorPerformance; } 99 | 100 | } 101 | 102 | .themed--background { 103 | 104 | .nav-theme--multi-device-layouts & { background-color: $colorLayouts; } 105 | 106 | .nav-theme--introduction-to-media & { background-color: $colorMedia; } 107 | 108 | .nav-theme--user-input & { background-color: $colorUser; } 109 | 110 | .nav-theme--performance & { background-color: $colorPerformance; } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /client/styles/components/components.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Main Stylesheet For Visual Style Guide 4 | * 5 | **/ 6 | 7 | @import "_utils"; 8 | @import "_normalize"; 9 | @import "_global"; 10 | 11 | // Icons - Leave it here to otherfiles can extend if needed 12 | @import "_components/_icons"; 13 | 14 | // Modules 15 | @import "_modules/_highlight"; 16 | @import "_modules/_editorial-header"; 17 | @import "_modules/_summary-header"; 18 | @import "_modules/_related-guides"; 19 | @import "_modules/_in-this-guide"; 20 | @import "_modules/_articles-section"; 21 | @import "_modules/_guides-section"; 22 | @import "_modules/_page-header"; 23 | @import "_modules/_featured-section"; 24 | @import "_modules/_featured-spotlight"; 25 | @import "_modules/_quote"; 26 | @import "_modules/_article-nav"; 27 | @import "_modules/_did-you-know"; 28 | @import "_modules/_toc"; 29 | @import "_modules/_next-lessons"; 30 | 31 | // Components 32 | @import "_components/_grid"; 33 | @import "_components/_typography"; 34 | @import "_components/_button"; 35 | @import "_components/_list"; 36 | @import "_components/_link"; 37 | @import "_components/_table"; 38 | @import "_components/_media"; 39 | @import "_components/_breadcrumb"; 40 | @import "_components/_subsection-title"; 41 | @import "_components/_articles-list"; 42 | @import "_components/_guides-list"; 43 | @import "_components/_icon-circle"; 44 | 45 | // Themed styles 46 | @import "_themed"; 47 | 48 | // Pages 49 | @import "_pages/_styleguide"; 50 | @import "_pages/_page-resources"; 51 | 52 | // Make sure this is last to override anything else :) 53 | @import "_helper"; 54 | -------------------------------------------------------------------------------- /client/styles/h5bp.css: -------------------------------------------------------------------------------- 1 | /* 2 | * HTML5 Boilerplate 3 | * 4 | * What follows is the result of much research on cross-browser styling. 5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, 6 | * Kroc Camen, and the H5BP dev community and team. 7 | */ 8 | 9 | /* ========================================================================== 10 | Base styles: opinionated defaults 11 | ========================================================================== */ 12 | 13 | html, 14 | button, 15 | input, 16 | select, 17 | textarea { 18 | color: #222; 19 | } 20 | 21 | body { 22 | font-size: 1em; 23 | line-height: 1.4; 24 | } 25 | 26 | a { 27 | color: #00e; 28 | } 29 | 30 | a:visited { 31 | color: #551a8b; 32 | } 33 | 34 | a:hover { 35 | color: #06e; 36 | } 37 | 38 | /* 39 | * Remove the gap between images and the bottom of their containers: h5bp.com/i/440 40 | */ 41 | 42 | img { 43 | vertical-align: middle; 44 | } 45 | 46 | /* 47 | * Remove default fieldset styles. 48 | */ 49 | 50 | fieldset { 51 | border: 0; 52 | margin: 0; 53 | padding: 0; 54 | } 55 | 56 | /* 57 | * Allow only vertical resizing of textareas. 58 | */ 59 | 60 | textarea { 61 | resize: vertical; 62 | } 63 | 64 | /* ========================================================================== 65 | Author's custom styles 66 | ========================================================================== */ 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | /* ========================================================================== 86 | Helper classes 87 | ========================================================================== */ 88 | 89 | /* Prevent callout */ 90 | 91 | 92 | 93 | /* A hack for HTML5 contenteditable attribute on mobile */ 94 | 95 | 96 | /* A workaround for S60 3.x and 5.0 devices which do not animated gif images if 97 | they have been set as display: none */ 98 | 99 | 100 | /* 101 | * Image replacement 102 | */ 103 | 104 | 105 | 106 | /* 107 | * Hide from both screenreaders and browsers: h5bp.com/u 108 | */ 109 | 110 | 111 | /* 112 | * Hide only visually, but have it available for screenreaders: h5bp.com/v 113 | */ 114 | 115 | 116 | /* 117 | * Extends the .visuallyhidden class to allow the element to be focusable 118 | * when navigated to via the keyboard: h5bp.com/p 119 | */ 120 | 121 | 122 | /* 123 | * Hide visually and from screenreaders, but maintain layout 124 | */ 125 | 126 | 127 | /** 128 | * Clearfix helper 129 | * Used to contain floats: h5bp.com/q 130 | */ 131 | 132 | 133 | 134 | /* ========================================================================== 135 | EXAMPLE Media Queries for Responsive Design. 136 | Theses examples override the primary ('mobile first') styles. 137 | Modify as content requires. 138 | ========================================================================== */ 139 | 140 | @media only screen and (min-width: 800px) { 141 | /* Style adjustments for viewports that meet the condition */ 142 | } 143 | 144 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), 145 | only screen and (min-resolution: 144dpi) { 146 | /* Style adjustments for viewports that meet the condition */ 147 | } 148 | -------------------------------------------------------------------------------- /client/styles/images/border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/styles/images/border.png -------------------------------------------------------------------------------- /client/styles/images/controls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/styles/images/controls.png -------------------------------------------------------------------------------- /client/styles/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/styles/images/loading.gif -------------------------------------------------------------------------------- /client/styles/images/loading_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/styles/images/loading_background.png -------------------------------------------------------------------------------- /client/styles/images/overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encryptedmessaging/emp/1a47046cba2ada14a93e5fd221e1e222165618fc/client/styles/images/overlay.png -------------------------------------------------------------------------------- /script/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | : ' 3 | Copyright 2014 JARST, LLC 4 | 5 | This file is part of EMP. 6 | 7 | EMP is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | EMP is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Foobar. If not, see . 19 | ' 20 | 21 | TMPGOPATH=$GOPATH 22 | 23 | # Check for go 24 | echo "Checking for go command..." 25 | if ! which go > /dev/null; then 26 | echo "Go command not found, please install it." 27 | exit -1 28 | fi 29 | 30 | # Setup environment variables 31 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 32 | export GOPATH=$DIR/.. 33 | 34 | # Get Dependencies 35 | echo "Installing dependencies..." 36 | go get golang.org/x/crypto/ripemd160 37 | go get github.com/BurntSushi/toml 38 | go get github.com/gorilla/rpc 39 | go get github.com/mxk/go-sqlite/sqlite3 40 | 41 | # Install and go! 42 | echo "Building..." 43 | if `go install emp`; then 44 | echo "Build succeeded." 45 | exit 0 46 | else echo "Build Failed, could not start client." 47 | fi 48 | 49 | export GOPATH=$TMPGOPATH 50 | exit -1 51 | -------------------------------------------------------------------------------- /script/msg.conf.example: -------------------------------------------------------------------------------- 1 | #### EMP Config File 2 | #### Version 1 3 | 4 | 5 | # Databases 6 | inventory = "inventory.db" 7 | local = "local.db" 8 | nodes = "known_nodes.dat" 9 | 10 | # Network 11 | 12 | # If a backbone node, change to a custom IP (v4 or v6) 13 | # ip = 14 | ip = "0.0.0.0" 15 | port = 5000 16 | 17 | # Bootstrap Peers 18 | bootstrap = [ 19 | "52.201.225.192:5000" 20 | ] 21 | 22 | # RPC 23 | [rpc] 24 | user = "rpcUser" 25 | pass = "rpcPass" 26 | port = 8080 27 | local_client = "client" 28 | local_only = true 29 | -------------------------------------------------------------------------------- /script/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | : ' 3 | Copyright 2014 JARST, LLC 4 | 5 | This file is part of EMP. 6 | 7 | EMP is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | EMP is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Foobar. If not, see . 19 | ' 20 | 21 | 22 | TMPGOPATH=$GOPATH 23 | 24 | # Check for go 25 | echo "Checking for go command..." 26 | if ! which go > /dev/null; then 27 | echo "Go command not found, please install it." 28 | exit -1 29 | fi 30 | 31 | # Setup environment variables 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | export GOPATH=$DIR/.. 34 | 35 | # Make and fill config directory 36 | echo "Checking config directory..." 37 | mkdir -p ~/.config/emp 38 | mkdir -p ~/.config/emp/log 39 | touch ~/.config/emp/known_nodes.dat 40 | if [ ! -f ~/.config/emp/msg.conf ]; then 41 | cp "$DIR/msg.conf.example" ~/.config/emp/msg.conf 42 | fi 43 | rm -rf ~/.config/emp/client 44 | cp -r "$DIR/../client" ~/.config/emp/ 45 | 46 | # Kill existing process 47 | if [ -f ~/.config/emp/pid ]; 48 | then 49 | echo 'Killing existing process...' 50 | kill -15 `cat ~/.config/emp/pid` 51 | rm -f ~/.config/emp/pid 52 | fi 53 | 54 | # Get Dependencies 55 | echo "Installing dependencies..." 56 | go get golang.org/x/crypto/ripemd160 57 | go get github.com/BurntSushi/toml 58 | go get github.com/gorilla/rpc 59 | go get github.com/mxk/go-sqlite/sqlite3 60 | 61 | # Install and go! 62 | echo "Building and running..." 63 | if `go install emp`; then 64 | "$GOPATH/bin/emp" "$HOME/.config/emp/" > ~/.config/emp/log/log_`date +%s` & 65 | echo $! > ~/.config/emp/pid 66 | 67 | # Get Ports 68 | PORTS=$(sed -n 's/.*port *= *\([^ ]*.*\)/\1/p' < ~/.config/emp/msg.conf) 69 | IFS=' ' read -a array <<< $PORTS 70 | 71 | # Final Output 72 | echo "Started EMP client on local port ${array[0]}." 73 | echo "Access local client at: http://localhost:${array[1]}" 74 | 75 | export GOPATH=$TMPGOPATH 76 | exit 0 77 | 78 | else echo "Build Failed, could not start client." 79 | fi 80 | 81 | export GOPATH=$TMPGOPATH 82 | exit -1 83 | -------------------------------------------------------------------------------- /script/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | : ' 3 | Copyright 2014 JARST, LLC 4 | 5 | This file is part of EMP. 6 | 7 | EMP is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | EMP is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Foobar. If not, see . 19 | ' 20 | 21 | 22 | # Kill existing process 23 | if [ -f ~/.config/emp/pid ]; 24 | then 25 | echo "Killing emp server (pid="`cat ~/.config/emp/pid`")" 26 | kill -2 `cat ~/.config/emp/pid` 27 | rm ~/.config/emp/pid 28 | else 29 | echo "Server not running, execute start.sh" 30 | fi 31 | 32 | -------------------------------------------------------------------------------- /script/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | : ' 3 | Copyright 2014 JARST, LLC 4 | 5 | This file is part of EMP. 6 | 7 | EMP is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | EMP is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Foobar. If not, see . 19 | ' 20 | 21 | go test quibit 22 | go test emp/encryption 23 | go test emp/objects 24 | go test emp/db 25 | go test emp/api 26 | -------------------------------------------------------------------------------- /src/emp/EMP.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package main 13 | 14 | import ( 15 | "emp/api" 16 | "emp/local/localapi" 17 | "fmt" 18 | "os" 19 | "os/signal" 20 | "quibit" 21 | ) 22 | 23 | func BlockingLogger(channel chan string) { 24 | var log string 25 | for { 26 | log = <-channel 27 | fmt.Println(log) 28 | if log == "Quit" { 29 | break 30 | } 31 | } 32 | } 33 | 34 | func main() { 35 | 36 | if len(os.Args) > 2 { 37 | fmt.Println("Usage: emp [config_directory]") 38 | return 39 | } 40 | 41 | if len(os.Args) == 2 { 42 | api.SetConfDir(os.Args[1]) 43 | } 44 | 45 | confFile := api.GetConfDir() + "msg.conf" 46 | 47 | config := api.GetConfig(confFile) 48 | 49 | if config == nil { 50 | fmt.Println("Error Loading Config, exiting...") 51 | return 52 | } 53 | 54 | // Start Network Services 55 | err := quibit.Initialize(config.Log, config.RecvQueue, config.SendQueue, config.PeerQueue, config.LocalVersion.Port) 56 | defer quibit.Cleanup() 57 | if err != nil { 58 | fmt.Printf("Error initializing network: %s", err) 59 | return 60 | } 61 | 62 | // Start Signal Handler 63 | signal.Notify(config.Quit, os.Interrupt, os.Kill) 64 | 65 | // Start API 66 | go api.Start(config) 67 | 68 | go localapi.Initialize(config) 69 | 70 | // Start Logger 71 | fmt.Println("Starting logger...") 72 | BlockingLogger(config.Log) 73 | 74 | // Give some time for cleanup... 75 | fmt.Println("Cleaning up...") 76 | localapi.Cleanup() 77 | } 78 | -------------------------------------------------------------------------------- /src/emp/api/api.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | // Package api provides a TCP server that fully implements the EMProtocol. 13 | package api 14 | 15 | import ( 16 | "emp/db" 17 | "emp/objects" 18 | "fmt" 19 | "quibit" 20 | "runtime" 21 | "time" 22 | ) 23 | 24 | // Starts a new TCP Server wth configuration specified in ApiConfig. 25 | // Server will terminate cleanly only when data is sent to the Quit channel. 26 | // 27 | // See (struct ApiConfig) for details. 28 | func Start(config *ApiConfig) { 29 | var err error 30 | var frame quibit.Frame 31 | 32 | defer quit(config) 33 | 34 | config.Log <- "Starting api..." 35 | 36 | // Start Database Services 37 | err = db.Initialize(config.Log, config.DbFile) 38 | defer db.Cleanup() 39 | if err != nil { 40 | config.Log <- fmt.Sprintf("Error initializing database: %s", err) 41 | config.Log <- "Quit" 42 | return 43 | } 44 | config.LocalVersion.Timestamp = time.Now().Round(time.Second) 45 | 46 | locVersion := objects.MakeFrame(objects.VERSION, objects.REQUEST, &config.LocalVersion) 47 | for str, _ := range config.NodeList.Nodes { 48 | locVersion.Peer = str 49 | config.SendQueue <- *locVersion 50 | } 51 | 52 | // Set Up Clocks 53 | second := time.Tick(2 * time.Second) 54 | minute := time.Tick(time.Minute) 55 | 56 | for { 57 | select { 58 | case frame = <-config.RecvQueue: 59 | config.Log <- fmt.Sprintf("Received %s frame...", CmdString(frame.Header.Command)) 60 | switch frame.Header.Command { 61 | case objects.VERSION: 62 | version := new(objects.Version) 63 | err = version.FromBytes(frame.Payload) 64 | if err != nil { 65 | config.Log <- fmt.Sprintf("Error parsing version: %s", err) 66 | } else { 67 | fVERSION(config, frame, version) 68 | } 69 | case objects.PEER: 70 | nodeList := new(objects.NodeList) 71 | err = nodeList.FromBytes(frame.Payload) 72 | if err != nil { 73 | config.Log <- fmt.Sprintf("Error parsing peer list: %s", err) 74 | } else { 75 | fPEER(config, frame, nodeList) 76 | } 77 | case objects.OBJ: 78 | obj := new(objects.Obj) 79 | err = obj.FromBytes(frame.Payload) 80 | if err != nil { 81 | config.Log <- fmt.Sprintf("Error parsing obj list: %s", err) 82 | } else { 83 | fOBJ(config, frame, obj) 84 | } 85 | case objects.GETOBJ: 86 | getObj := new(objects.Hash) 87 | if len(frame.Payload) == 0 { 88 | break 89 | } 90 | err = getObj.FromBytes(frame.Payload) 91 | if err != nil { 92 | config.Log <- fmt.Sprintf("Error parsing getobj hash: %s", err) 93 | } else { 94 | fGETOBJ(config, frame, getObj) 95 | } 96 | case objects.PUBKEY_REQUEST: 97 | pubReq := new(objects.Hash) 98 | err = pubReq.FromBytes(frame.Payload) 99 | if err != nil { 100 | config.Log <- fmt.Sprintf("Error parsing pubkey request hash: %s", err) 101 | } else { 102 | fPUBKEY_REQUEST(config, frame, pubReq) 103 | } 104 | case objects.PUBKEY: 105 | pub := new(objects.EncryptedPubkey) 106 | err = pub.FromBytes(frame.Payload) 107 | if err != nil { 108 | config.Log <- fmt.Sprintf("Error parsing pubkey: %s", err) 109 | } else { 110 | fPUBKEY(config, frame, pub) 111 | } 112 | case objects.MSG: 113 | msg := new(objects.Message) 114 | err = msg.FromBytes(frame.Payload) 115 | if err != nil { 116 | config.Log <- fmt.Sprintf("Error parsing message: %s", err) 117 | } else { 118 | fMSG(config, frame, msg) 119 | } 120 | case objects.PUB: 121 | msg := new(objects.Message) 122 | err = msg.FromBytes(frame.Payload) 123 | if err != nil { 124 | config.Log <- fmt.Sprintf("Error parsing publication: %s", err) 125 | } else { 126 | fPUB(config, frame, msg) 127 | } 128 | case objects.PURGE: 129 | purge := new(objects.Purge) 130 | err = purge.FromBytes(frame.Payload) 131 | if err != nil { 132 | config.Log <- fmt.Sprintf("Error parsing purge: %s", err) 133 | } else { 134 | fPURGE(config, frame, purge) 135 | } 136 | case objects.CHECKTXID: 137 | chkTxid := new(objects.Hash) 138 | if len(frame.Payload) == 0 { 139 | break 140 | } 141 | err = chkTxid.FromBytes(frame.Payload) 142 | if err != nil { 143 | config.Log <- fmt.Sprintf("Error parsing checktxid hash: %s", err) 144 | } else { 145 | fCHECKTXID(config, frame, chkTxid) 146 | } 147 | default: 148 | config.Log <- fmt.Sprintf("Received invalid frame for command: %d", frame.Header.Command) 149 | } 150 | case <-config.Quit: 151 | fmt.Println() 152 | // Dump Nodes to File 153 | DumpNodes(config) 154 | return 155 | case <-second: 156 | // Reconnection Logic 157 | for key, node := range config.NodeList.Nodes { 158 | peer := quibit.GetPeer(key) 159 | if peer == nil || !peer.IsConnected() { 160 | quibit.KillPeer(key) 161 | if node.Attempts >= 3 { 162 | config.Log <- fmt.Sprintf("Max connection attempts reached for %s, disconnecting...", key) 163 | // Max Attempts Reached, disconnect 164 | delete(config.NodeList.Nodes, key) 165 | } else { 166 | config.Log <- fmt.Sprintf("Disconnected from peer %s, trying to reconnect...", key) 167 | peer = new(quibit.Peer) 168 | peer.IP = node.IP 169 | peer.Port = node.Port 170 | config.PeerQueue <- *peer 171 | runtime.Gosched() 172 | peer = nil 173 | node.Attempts++ 174 | config.NodeList.Nodes[key] = node 175 | locVersion.Peer = key 176 | config.SendQueue <- *locVersion 177 | } 178 | } 179 | } 180 | 181 | if len(config.NodeList.Nodes) < 1 { 182 | config.Log <- "All connections lost, re-bootstrapping..." 183 | 184 | for i, str := range config.Bootstrap { 185 | if i >= bufLen { 186 | break 187 | } 188 | 189 | p := new(quibit.Peer) 190 | n := new(objects.Node) 191 | err := n.FromString(str) 192 | if err != nil { 193 | fmt.Println("Error Decoding Peer ", str, ": ", err) 194 | continue 195 | } 196 | 197 | p.IP = n.IP 198 | p.Port = n.Port 199 | config.PeerQueue <- *p 200 | runtime.Gosched() 201 | config.NodeList.Nodes[n.String()] = *n 202 | } 203 | 204 | for str, _ := range config.NodeList.Nodes { 205 | locVersion.Peer = str 206 | config.SendQueue <- *locVersion 207 | } 208 | } 209 | case <-minute: 210 | // Dump old messages 211 | err = db.SweepMessages(30 * 24 * time.Hour) 212 | if err != nil { 213 | config.Log <- fmt.Sprintf("Error Sweeping Messages: %s", err) 214 | } 215 | } 216 | } 217 | 218 | // Should NEVER get here! 219 | panic("Must've been a cosmic ray!") 220 | } 221 | 222 | func quit(config *ApiConfig) { 223 | config.Log <- "Quit" 224 | } 225 | -------------------------------------------------------------------------------- /src/emp/api/api_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package api 13 | 14 | import ( 15 | "emp/objects" 16 | "fmt" 17 | "os" 18 | "os/exec" 19 | "quibit" 20 | "testing" 21 | "time" 22 | ) 23 | 24 | func initialize() *ApiConfig { 25 | config := new(ApiConfig) 26 | 27 | // Network Channels 28 | config.RecvQueue = make(chan quibit.Frame) 29 | config.SendQueue = make(chan quibit.Frame) 30 | config.PeerQueue = make(chan quibit.Peer) 31 | 32 | // Local Logic 33 | config.DbFile = "testdb.db" 34 | 35 | config.LocalVersion.Version = 1 36 | config.LocalVersion.Timestamp = time.Now().Round(time.Second) 37 | config.LocalVersion.Port = 4444 38 | config.LocalVersion.UserAgent = "strongmsg v0.1" 39 | 40 | // Administration 41 | config.Log = make(chan string, 100) 42 | config.Quit = make(chan os.Signal, 1) 43 | 44 | go Start(config) 45 | 46 | return config 47 | } 48 | 49 | func cleanup(config *ApiConfig) { 50 | var s os.Signal 51 | config.Quit <- s 52 | 53 | str := <-config.Log 54 | for str != "Quit" { 55 | fmt.Println(str) 56 | str = <-config.Log 57 | } 58 | 59 | exec.Command("rm", "testdb.db").Run() 60 | 61 | } 62 | 63 | func TestHandshake(t *testing.T) { 64 | config := initialize() 65 | 66 | var frame quibit.Frame 67 | var err error 68 | 69 | // Test Version 70 | frame = *objects.MakeFrame(objects.VERSION, objects.REQUEST, &config.LocalVersion) 71 | frame.Peer = "127.0.0.1:4444" 72 | 73 | config.RecvQueue <- frame 74 | 75 | frame = <-config.SendQueue 76 | 77 | if frame.Header.Command != objects.VERSION || frame.Header.Type != objects.REPLY { 78 | fmt.Println("Frame is not a proper reply to a version request: ", frame.Header) 79 | t.FailNow() 80 | } 81 | 82 | version := new(objects.Version) 83 | err = version.FromBytes(frame.Payload) 84 | if err != nil { 85 | fmt.Println("Error parsing version reply: ", err) 86 | t.FailNow() 87 | } 88 | 89 | // Test Peer 90 | frame = *objects.MakeFrame(objects.PEER, objects.REQUEST, &config.NodeList) 91 | frame.Peer = "127.0.0.1:4444" 92 | 93 | config.RecvQueue <- frame 94 | 95 | frame = <-config.SendQueue 96 | 97 | if frame.Header.Command != objects.PEER || frame.Header.Type != objects.REPLY || frame.Header.Length != 0 { 98 | fmt.Println("Frame is not a proper reply to a peer request: ", frame.Header) 99 | t.FailNow() 100 | } 101 | 102 | // Test Obj 103 | frame = *objects.MakeFrame(objects.OBJ, objects.REQUEST, &config.NodeList) 104 | frame.Peer = "127.0.0.1:4444" 105 | 106 | config.RecvQueue <- frame 107 | 108 | frame = <-config.SendQueue 109 | 110 | if frame.Header.Command != objects.OBJ || frame.Header.Type != objects.REPLY || frame.Header.Length != 0 { 111 | fmt.Println("Frame is not a proper reply to a peer request: ", frame.Header) 112 | t.FailNow() 113 | } 114 | 115 | cleanup(config) 116 | } 117 | -------------------------------------------------------------------------------- /src/emp/api/config.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package api 13 | 14 | import ( 15 | "emp/objects" 16 | "fmt" 17 | "github.com/BurntSushi/toml" 18 | "net" 19 | "os" 20 | "bufio" 21 | "os/user" 22 | "quibit" 23 | "time" 24 | "io/ioutil" 25 | ) 26 | 27 | var confDir string; 28 | 29 | type ApiConfig struct { 30 | // Network Channels 31 | RecvQueue chan quibit.Frame // Send frames here to be handled by the Running API 32 | SendQueue chan quibit.Frame // Frames to be broadcast to the network are sent here 33 | PeerQueue chan quibit.Peer // New peers to connect to are sent here 34 | 35 | // Local Logic 36 | DbFile string // Inventory File relative to Config Directory 37 | LocalDB string // EMPLocal Database relative to Config Directory 38 | NodeFile string // File to store list of : Strings 39 | NodeList objects.NodeList // Active list of connected backbone nodes. 40 | LocalVersion objects.Version // Local version broadcast to nodes upon connection 41 | Bootstrap []string // List of bootstrap nodes to use when all other nodes are disconnected. 42 | 43 | // Local Register 44 | PubkeyRegister chan objects.Hash // Identifiers for incoming encrypted public keys are sent here. 45 | MessageRegister chan objects.Message // Incoming basic messages are copied here. 46 | PubRegister chan objects.Message // Incoming published messages are copied here. 47 | PurgeRegister chan [16]byte // Incomping purge tokens are copied here. 48 | 49 | // Administration 50 | Log chan string // Error messages are sent here. WILL BLOCK if not 51 | Quit chan os.Signal // Send data here to cleanly quit the API Server 52 | 53 | // Network 54 | RPCPort uint16 // Port to run RPC API and EMPLocal Client 55 | RPCUser string // Username for RPC server 56 | RPCPass string // Password for RPC Server 57 | LocalOnly bool // If true, only allow RPC from 127.0.0.1 58 | 59 | HttpRoot string // HTML Root of EMPLocal Client 60 | } 61 | 62 | // Returns Human-Readable string for a specific EMP command. 63 | func CmdString(cmd uint8) string { 64 | var ret string 65 | 66 | switch cmd { 67 | case objects.VERSION: 68 | ret = "version" 69 | case objects.PEER: 70 | ret = "peer list" 71 | case objects.OBJ: 72 | ret = "object vector" 73 | case objects.GETOBJ: 74 | ret = "object request" 75 | case objects.PUBKEY_REQUEST: 76 | ret = "public key request" 77 | case objects.PUBKEY: 78 | ret = "public key" 79 | case objects.MSG: 80 | ret = "encrypted message" 81 | case objects.PUB: 82 | ret = "encrypted publication" 83 | case objects.PURGE: 84 | ret = "purge notification" 85 | case objects.CHECKTXID: 86 | ret = "purge check" 87 | default: 88 | ret = "unknown" 89 | } 90 | 91 | return ret 92 | } 93 | 94 | const ( 95 | bufLen = 10 96 | ) 97 | 98 | type tomlConfig struct { 99 | Inventory string `toml:"inventory"` 100 | Local string `toml:"local"` 101 | Nodes string `toml:"nodes"` 102 | 103 | IP string 104 | Port uint16 105 | 106 | Peers []string `toml:"bootstrap"` 107 | 108 | RPCConf rpcConf `toml:"rpc"` 109 | } 110 | 111 | type rpcConf struct { 112 | User string 113 | Pass string 114 | Port uint16 115 | Local string `toml:"local_client"` 116 | LocalOnly bool `toml:"local_only"` 117 | } 118 | 119 | // Set Config Directory where databases and configuration are stored. 120 | func SetConfDir(conf string) { 121 | confDir = conf 122 | } 123 | 124 | // Get Config Directory: Defaults to $(HOME)/.config/emp/ 125 | func GetConfDir() string { 126 | if len(confDir) != 0 { 127 | return confDir 128 | } 129 | 130 | usr, err := user.Current() 131 | if err != nil { 132 | return "./" 133 | } 134 | 135 | return usr.HomeDir + "/.config/emp/" 136 | } 137 | 138 | // Generate new config from configuration file. File provided as an Absolute Path. 139 | func GetConfig(confFile string) *ApiConfig { 140 | 141 | var tomlConf tomlConfig 142 | 143 | if _, err := toml.DecodeFile(confFile, &tomlConf); err != nil { 144 | fmt.Println("Config Error: ", err) 145 | return nil 146 | } 147 | 148 | config := new(ApiConfig) 149 | 150 | // Network Channels 151 | config.RecvQueue = make(chan quibit.Frame, bufLen) 152 | config.SendQueue = make(chan quibit.Frame, bufLen) 153 | config.PeerQueue = make(chan quibit.Peer, bufLen) 154 | 155 | // Local Logic 156 | config.DbFile = GetConfDir() + tomlConf.Inventory 157 | config.LocalDB = GetConfDir() + tomlConf.Local 158 | if len(config.DbFile) == 0 || len(config.LocalDB) == 0 { 159 | fmt.Println("Database file not found in config!") 160 | return nil 161 | } 162 | config.NodeFile = GetConfDir() + tomlConf.Nodes 163 | 164 | config.LocalVersion.Port = tomlConf.Port 165 | if tomlConf.IP != "0.0.0.0" { 166 | config.LocalVersion.IpAddress = net.ParseIP(tomlConf.IP) 167 | } 168 | config.LocalVersion.Timestamp = time.Now().Round(time.Second) 169 | config.LocalVersion.Version = objects.LOCAL_VERSION 170 | config.LocalVersion.UserAgent = objects.LOCAL_USER 171 | 172 | // RPC 173 | config.RPCPort = tomlConf.RPCConf.Port 174 | config.RPCUser = tomlConf.RPCConf.User 175 | config.RPCPass = tomlConf.RPCConf.Pass 176 | config.LocalOnly = tomlConf.RPCConf.LocalOnly 177 | config.HttpRoot = GetConfDir() + tomlConf.RPCConf.Local 178 | 179 | // Local Registers 180 | config.PubkeyRegister = make(chan objects.Hash, bufLen) 181 | config.MessageRegister = make(chan objects.Message, bufLen) 182 | config.PubRegister = make(chan objects.Message, bufLen) 183 | config.PurgeRegister = make(chan [16]byte, bufLen) 184 | 185 | // Administration 186 | config.Log = make(chan string, bufLen) 187 | config.Quit = make(chan os.Signal, 1) 188 | 189 | // Initialize Map 190 | config.NodeList.Nodes = make(map[string]objects.Node) 191 | 192 | // Bootstrap Nodes 193 | config.Bootstrap = make([]string, len(tomlConf.Peers), cap(tomlConf.Peers)) 194 | copy(config.Bootstrap, tomlConf.Peers) 195 | for i, str := range tomlConf.Peers { 196 | if i >= bufLen { 197 | break 198 | } 199 | 200 | p := new(quibit.Peer) 201 | n := new(objects.Node) 202 | err := n.FromString(str) 203 | if err != nil { 204 | fmt.Println("Error Decoding Peer ", str, ": ", err) 205 | continue 206 | } 207 | 208 | p.IP = n.IP 209 | p.Port = n.Port 210 | config.PeerQueue <- *p 211 | config.NodeList.Nodes[n.String()] = *n 212 | } 213 | 214 | // Pull Nodes from node file 215 | if len(config.NodeFile) > 0 { 216 | ReadNodes(config) 217 | } 218 | 219 | return config 220 | } 221 | 222 | // Load and connect to all nodes from the NodeFile found in the ApiConfig. 223 | func ReadNodes(config *ApiConfig) { 224 | file, err := os.Open(config.NodeFile); 225 | defer file.Close() 226 | if err != nil { 227 | fmt.Println("Could not open node file: ", err) 228 | } 229 | 230 | var count int 231 | 232 | scanner := bufio.NewScanner(file) 233 | 234 | for scanner.Scan() { 235 | str := scanner.Text() 236 | if len(str) < 0 || str == "" { 237 | continue 238 | } 239 | 240 | p := new(quibit.Peer) 241 | n := new(objects.Node) 242 | err = n.FromString(str) 243 | if err != nil { 244 | fmt.Println("Error Decoding Peer ", str, ": ", err) 245 | continue 246 | } 247 | 248 | p.IP = n.IP 249 | p.Port = n.Port 250 | config.PeerQueue <- *p 251 | config.NodeList.Nodes[n.String()] = *n 252 | count++ 253 | } 254 | fmt.Println(count, "nodes pulled from node file.") 255 | } 256 | 257 | // Dump all nodes in config.NodeList to config.NodeFile. 258 | func DumpNodes(config *ApiConfig) { 259 | if config == nil { 260 | return 261 | } 262 | if len(config.NodeFile) < 1 { 263 | return 264 | } 265 | writeBytes := make([]byte, 0, 0) 266 | 267 | for key, _ := range config.NodeList.Nodes { 268 | if quibit.GetPeer(key).IsConnected() { 269 | writeBytes = append(writeBytes, key...) 270 | writeBytes = append(writeBytes, byte('\n')) 271 | } 272 | } 273 | 274 | err := ioutil.WriteFile(config.NodeFile, writeBytes, 0644) 275 | if err != nil { 276 | fmt.Println("Error writing peers to file: ", err) 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/emp/db/db.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | // Package db provides a connection to a local SQLite database to store the EMP inventory. 13 | package db 14 | 15 | import ( 16 | "fmt" 17 | "github.com/mxk/go-sqlite/sqlite3" 18 | "sync" 19 | ) 20 | 21 | // Database Connection 22 | var dbConn *sqlite3.Conn 23 | var mutex *sync.Mutex 24 | 25 | // Initialize Database connection from Database File (Absolute Path), mutexes, and the global hash list. 26 | func Initialize(log chan string, dbFile string) error { 27 | var err error 28 | if dbConn != nil { 29 | return nil 30 | } 31 | 32 | mutex = new(sync.Mutex) 33 | 34 | // Create Database Connection 35 | dbConn, err = sqlite3.Open(dbFile) 36 | if err != nil || dbConn == nil { 37 | log <- fmt.Sprintf("Error opening sqlite database at %s... %s", dbFile, err) 38 | dbConn = nil 39 | return err 40 | } 41 | 42 | // Create Database Schema 43 | err = dbConn.Exec("CREATE TABLE IF NOT EXISTS pubkey (hash BLOB NOT NULL UNIQUE, payload BLOB NOT NULL, PRIMARY KEY (hash))") 44 | if err != nil { 45 | log <- fmt.Sprintf("Error setting up pubkey schema... %s", err) 46 | dbConn = nil 47 | return err 48 | } 49 | 50 | err = dbConn.Exec("CREATE TABLE IF NOT EXISTS purge (hash BLOB NOT NULL UNIQUE, txid BLOB NOT NULL UNIQUE, PRIMARY KEY (hash))") 51 | if err != nil { 52 | log <- fmt.Sprintf("Error setting up purge schema... %s", err) 53 | dbConn = nil 54 | return err 55 | } 56 | 57 | err = dbConn.Exec("CREATE TABLE IF NOT EXISTS msg (hash BLOB NOT NULL UNIQUE, addrHash BLOB NOT NULL, timestamp INTEGER NOT NULL, payload BLOB NOT NULL, PRIMARY KEY (hash))") 58 | if err != nil { 59 | log <- fmt.Sprintf("Error setting up msg schema... %s", err) 60 | dbConn = nil 61 | return err 62 | } 63 | 64 | err = dbConn.Exec("CREATE TABLE IF NOT EXISTS pub (hash BLOB NOT NULL UNIQUE, addrHash BLOB NOT NULL, timestamp INTEGER NOT NULL, payload BLOB NOT NULL, PRIMARY KEY (hash))") 65 | if err != nil { 66 | log <- fmt.Sprintf("Error setting up pub schema... %s", err) 67 | dbConn = nil 68 | return err 69 | } 70 | 71 | err = dbConn.Exec("CREATE TABLE IF NOT EXISTS peer (ip BLOB NOT NULL, port INTEGER NOT NULL, port_admin INTEGER NOT NULL, last_seen INTEGER NOT NULL, id INTEGER PRIMARY KEY AUTOINCREMENT)") 72 | if err != nil { 73 | log <- fmt.Sprintf("Error setting up peer schema... %s", err) 74 | dbConn = nil 75 | return err 76 | } 77 | 78 | err = dbConn.Exec("CREATE UNIQUE INDEX IF NOT EXISTS ip_index ON peer (ip, port, port_admin)") 79 | if err != nil { 80 | log <- fmt.Sprintf("Error setting up peer index... %s", err) 81 | dbConn = nil 82 | return err 83 | } 84 | 85 | if hashList == nil { 86 | hashList = make(map[string]int) 87 | return populateHashes() 88 | } 89 | 90 | if dbConn == nil || hashList == nil { 91 | fmt.Println("ERROR! ERROR! WTF!!! SHOULD BE INITIALIZED!") 92 | } 93 | 94 | return nil 95 | } 96 | 97 | func populateHashes() error { 98 | mutex.Lock() 99 | 100 | for s, err := dbConn.Query("SELECT hash FROM pubkey"); err == nil; err = s.Next() { 101 | var hash []byte 102 | s.Scan(&hash) // Assigns 1st column to rowid, the rest to row 103 | hashList[string(hash)] = PUBKEY 104 | } 105 | 106 | for s, err := dbConn.Query("SELECT hash FROM msg"); err == nil; err = s.Next() { 107 | var hash []byte 108 | s.Scan(&hash) // Assigns 1st column to rowid, the rest to row 109 | hashList[string(hash)] = MSG 110 | } 111 | 112 | for s, err := dbConn.Query("SELECT hash FROM pub"); err == nil; err = s.Next() { 113 | var hash []byte 114 | s.Scan(&hash) // Assigns 1st column to rowid, the rest to row 115 | hashList[string(hash)] = PUB 116 | } 117 | 118 | for s, err := dbConn.Query("SELECT hash FROM purge"); err == nil; err = s.Next() { 119 | var hash []byte 120 | s.Scan(&hash) // Assigns 1st column to rowid, the rest to row 121 | hashList[string(hash)] = PURGE 122 | } 123 | 124 | mutex.Unlock() 125 | return nil 126 | } 127 | 128 | // Closes the database connection and de-initializes the global hash list. 129 | func Cleanup() { 130 | dbConn.Close() 131 | dbConn = nil 132 | hashList = nil 133 | } 134 | -------------------------------------------------------------------------------- /src/emp/db/db_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package db 13 | 14 | import ( 15 | "fmt" 16 | "os/exec" 17 | "emp/objects" 18 | "testing" 19 | "time" 20 | ) 21 | 22 | func TestDatabase(t *testing.T) { 23 | // Start Logger 24 | log := make(chan string, 100) 25 | go func() { 26 | for { 27 | log_stmt := <-log 28 | fmt.Println(log_stmt) 29 | } 30 | }() 31 | 32 | err := Initialize(log, "testdb.db") 33 | if dbConn == nil || hashList == nil { 34 | fmt.Println("ERROR! ERROR! WTF!!!") 35 | } 36 | 37 | if err != nil { 38 | fmt.Printf("ERROR: %s\n", err) 39 | t.FailNow() 40 | } 41 | 42 | txid := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'} 43 | purgeHash := objects.MakeHash(txid) 44 | pubHash := objects.MakeHash([]byte{'e', 'f', 'g', 'h'}) 45 | 46 | if Contains(purgeHash) != NOTFOUND { 47 | fmt.Println("Purge Hash already in list...") 48 | t.FailNow() 49 | } 50 | if Contains(pubHash) != NOTFOUND { 51 | fmt.Println("Pubkey Hash already in list...") 52 | t.FailNow() 53 | } 54 | 55 | pub := new(objects.EncryptedPubkey) 56 | pub.AddrHash = pubHash 57 | pub.IV = [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 58 | pub.Payload = []byte{'a', 'b', 'c', 'd'} 59 | 60 | err = AddPubkey(log, *pub) 61 | if err != nil { 62 | fmt.Printf("ERROR: %s\n", err) 63 | t.FailNow() 64 | } 65 | 66 | if Contains(pubHash) != PUBKEY { 67 | fmt.Println("Pubkey not in hash list") 68 | time.Sleep(time.Millisecond) 69 | t.FailNow() 70 | } 71 | 72 | RemoveHash(log, pubHash) 73 | 74 | if Contains(pubHash) != NOTFOUND { 75 | fmt.Println("Pubkey stuck in hash list") 76 | t.FailNow() 77 | } 78 | 79 | purge := new(objects.Purge) 80 | copy(purge.Txid[:], txid) 81 | 82 | err = AddPurge(log, *purge) 83 | if err != nil { 84 | fmt.Printf("ERROR: %s\n", err) 85 | t.FailNow() 86 | } 87 | 88 | if Contains(purgeHash) != PURGE { 89 | fmt.Println("Purge not in hash list") 90 | t.FailNow() 91 | } 92 | 93 | RemoveHash(log, purgeHash) 94 | 95 | if Contains(purgeHash) != NOTFOUND { 96 | fmt.Println("Purge stuck in hash list") 97 | t.FailNow() 98 | } 99 | 100 | Cleanup() 101 | 102 | // Remove DB 103 | err = exec.Command("rm", "testdb.db").Run() 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/emp/db/error.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package db 13 | 14 | const ( 15 | EUNINIT = iota 16 | ) 17 | 18 | type DBError int 19 | 20 | func (e DBError) Error() string { 21 | switch int(e) { 22 | case EUNINIT: 23 | return "Database or hash list not initialized! Please call Initialize()" 24 | default: 25 | return "Unknown error..." 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/emp/db/hashlist.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package db 13 | 14 | import "emp/objects" 15 | 16 | const ( 17 | PUBKEY = iota // Encrypted Public Key 18 | PURGE = iota // Purged Message 19 | MSG = iota // Basic Message 20 | PUBKEYRQ = iota // Public Key Request 21 | PUB = iota // Published Message 22 | NOTFOUND = iota // Object not in hash list. 23 | ) 24 | 25 | // Hash List 26 | var hashList map[string]int 27 | 28 | // Add an object to the hash list with a given type. 29 | func Add(hashObj objects.Hash, hashType int) { 30 | hash := string(hashObj.GetBytes()) 31 | if hashList != nil { 32 | hashList[hash] = hashType 33 | } 34 | } 35 | 36 | // Remove an object from the hash list. 37 | func Delete(hashObj objects.Hash) { 38 | hash := string(hashObj.GetBytes()) 39 | if hashList != nil { 40 | delete(hashList, hash) 41 | } 42 | } 43 | 44 | // Return the type the item in the hash list (see constants). 45 | func Contains(hashObj objects.Hash) int { 46 | hash := string(hashObj.GetBytes()) 47 | if hashList != nil { 48 | hashType, ok := hashList[hash] 49 | if ok { 50 | return hashType 51 | } else { 52 | return NOTFOUND 53 | } 54 | } 55 | return NOTFOUND 56 | } 57 | 58 | // List of all hashes in the hash list. 59 | func ObjList() *objects.Obj { 60 | if hashList == nil { 61 | return nil 62 | } 63 | 64 | ret := new(objects.Obj) 65 | ret.HashList = make([]objects.Hash, 0, 0) 66 | 67 | hash := new(objects.Hash) 68 | 69 | for key, _ := range hashList { 70 | hash.FromBytes([]byte(key)) 71 | ret.HashList = append(ret.HashList, *hash) 72 | } 73 | return ret 74 | } 75 | -------------------------------------------------------------------------------- /src/emp/db/objects.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package db 13 | 14 | import ( 15 | "crypto/sha512" 16 | "fmt" 17 | "emp/objects" 18 | "time" 19 | ) 20 | 21 | // Remove all basic and published messages older than (Current Time - duration). 22 | func SweepMessages(duration time.Duration) error { 23 | mutex.Lock() 24 | defer mutex.Unlock() 25 | 26 | deadline := time.Now().Add(-duration).Unix() 27 | 28 | return dbConn.Exec("DELETE FROM msg WHERE timestamp <= ?", deadline) 29 | } 30 | 31 | // Add Encrypted public key to database and hash list. 32 | func AddPubkey(log chan string, pubkey objects.EncryptedPubkey) error { 33 | mutex.Lock() 34 | defer mutex.Unlock() 35 | 36 | hash := pubkey.AddrHash.GetBytes() 37 | payload := append(pubkey.IV[:], pubkey.Payload...) 38 | 39 | if hashList == nil || dbConn == nil { 40 | return DBError(EUNINIT) 41 | } 42 | if Contains(pubkey.AddrHash) == PUBKEY { 43 | return nil 44 | } 45 | 46 | err := dbConn.Exec("INSERT INTO pubkey VALUES (?, ?)", hash, payload) 47 | if err != nil { 48 | log <- fmt.Sprintf("Error inserting pubkey into db... %s", err) 49 | return err 50 | } 51 | 52 | Add(pubkey.AddrHash, PUBKEY) 53 | return nil 54 | } 55 | 56 | // Get Encrypted Public Key from database. 57 | func GetPubkey(log chan string, addrHash objects.Hash) *objects.EncryptedPubkey { 58 | mutex.Lock() 59 | defer mutex.Unlock() 60 | 61 | hash := addrHash.GetBytes() 62 | 63 | if hashList == nil || dbConn == nil { 64 | return nil 65 | } 66 | if hashList[string(hash)] != PUBKEY { 67 | return nil 68 | } 69 | 70 | for s, err := dbConn.Query("SELECT payload FROM pubkey WHERE hash=?", hash); err == nil; err = s.Next() { 71 | var payload []byte 72 | s.Scan(&payload) // Assigns 1st column to rowid, the rest to row 73 | pub := new(objects.EncryptedPubkey) 74 | pub.AddrHash = addrHash 75 | copy(pub.IV[:], payload[:16]) 76 | pub.Payload = payload[16:] 77 | return pub 78 | } 79 | // Not Found 80 | return nil 81 | } 82 | 83 | // Add Purge Token to database, and remove corresponding message if necessary. 84 | func AddPurge(log chan string, p objects.Purge) error { 85 | mutex.Lock() 86 | defer mutex.Unlock() 87 | 88 | txid := p.GetBytes() 89 | hashArr := sha512.Sum384(txid) 90 | hash := hashArr[:] 91 | 92 | if hashList == nil || dbConn == nil { 93 | return DBError(EUNINIT) 94 | } 95 | hashObj := new(objects.Hash) 96 | hashObj.FromBytes(hash) 97 | 98 | if Contains(*hashObj) == PURGE { 99 | return nil 100 | } 101 | 102 | err := dbConn.Exec("INSERT INTO purge VALUES (?, ?)", hash, txid) 103 | if err != nil { 104 | log <- fmt.Sprintf("Error inserting purge into db... %s", err) 105 | return err 106 | } 107 | 108 | Add(*hashObj, PURGE) 109 | return nil 110 | } 111 | 112 | // Get purge token from the database. 113 | func GetPurge(log chan string, txidHash objects.Hash) *objects.Purge { 114 | mutex.Lock() 115 | defer mutex.Unlock() 116 | 117 | hash := txidHash.GetBytes() 118 | 119 | if hashList == nil || dbConn == nil { 120 | return nil 121 | } 122 | if hashList[string(hash)] != PURGE { 123 | return nil 124 | } 125 | 126 | for s, err := dbConn.Query("SELECT txid FROM purge WHERE hash=?", hash); err == nil; err = s.Next() { 127 | var txid []byte 128 | s.Scan(&txid) // Assigns 1st column to rowid, the rest to row 129 | p := new(objects.Purge) 130 | p.FromBytes(txid) 131 | return p 132 | } 133 | // Not Found 134 | return nil 135 | } 136 | 137 | // Add Published Message to database and hash list. 138 | func AddPub(log chan string, msg *objects.Message) error { 139 | mutex.Lock() 140 | defer mutex.Unlock() 141 | 142 | if hashList == nil || dbConn == nil { 143 | return DBError(EUNINIT) 144 | } 145 | if Contains(msg.TxidHash) == MSG { 146 | return nil 147 | } 148 | 149 | err := dbConn.Exec("INSERT INTO pub VALUES (?, ?, ?, ?)", msg.TxidHash.GetBytes(), msg.AddrHash.GetBytes(), msg.Timestamp.Unix(), msg.Content.GetBytes()) 150 | if err != nil { 151 | log <- fmt.Sprintf("Error inserting message into db... %s", err) 152 | return err 153 | } 154 | 155 | Add(msg.TxidHash, PUB) 156 | return nil 157 | } 158 | 159 | // Add basic message to database and hash list. 160 | func AddMessage(log chan string, msg *objects.Message) error { 161 | mutex.Lock() 162 | defer mutex.Unlock() 163 | 164 | if hashList == nil || dbConn == nil { 165 | return DBError(EUNINIT) 166 | } 167 | if Contains(msg.TxidHash) == MSG { 168 | return nil 169 | } 170 | 171 | err := dbConn.Exec("INSERT INTO msg VALUES (?, ?, ?, ?)", msg.TxidHash.GetBytes(), msg.AddrHash.GetBytes(), msg.Timestamp.Unix(), msg.Content.GetBytes()) 172 | if err != nil { 173 | log <- fmt.Sprintf("Error inserting message into db... %s", err) 174 | return err 175 | } 176 | 177 | Add(msg.TxidHash, MSG) 178 | return nil 179 | 180 | } 181 | 182 | // Get basic message from database. 183 | func GetMessage(log chan string, txidHash objects.Hash) *objects.Message { 184 | mutex.Lock() 185 | defer mutex.Unlock() 186 | 187 | hash := txidHash.GetBytes() 188 | 189 | if hashList == nil || dbConn == nil { 190 | return nil 191 | } 192 | if hashList[string(hash)] != MSG && hashList[string(hash)] != PUB { 193 | return nil 194 | } 195 | 196 | msg := new(objects.Message) 197 | 198 | for s, err := dbConn.Query("SELECT * FROM msg WHERE hash=?", hash); err == nil; err = s.Next() { 199 | var timestamp int64 200 | encrypted := make([]byte, 0, 0) 201 | txidhash := make([]byte, 0, 0) 202 | addrhash := make([]byte, 0, 0) 203 | s.Scan(&txidhash, &addrhash, ×tamp, &encrypted) 204 | 205 | msg.TxidHash.FromBytes(txidhash) 206 | msg.AddrHash.FromBytes(addrhash) 207 | msg.Timestamp = time.Unix(timestamp, 0) 208 | msg.Content.FromBytes(encrypted) 209 | 210 | return msg 211 | } 212 | // Not Found 213 | return nil 214 | } 215 | 216 | // Remove any object from the database and hash list. 217 | func RemoveHash(log chan string, hashObj objects.Hash) error { 218 | mutex.Lock() 219 | defer mutex.Unlock() 220 | 221 | hash := hashObj.GetBytes() 222 | 223 | if hashList == nil || dbConn == nil { 224 | return DBError(EUNINIT) 225 | } 226 | 227 | var sql string 228 | 229 | switch Contains(hashObj) { 230 | case PUBKEY: 231 | sql = "DELETE FROM pubkey WHERE hash=?" 232 | case MSG: 233 | sql = "DELETE FROM msg WHERE hash=?" 234 | case PURGE: 235 | sql = "DELETE FROM purge WHERE hash=?" 236 | default: 237 | return nil 238 | } 239 | 240 | err := dbConn.Exec(sql, hash) 241 | if err != nil { 242 | log <- fmt.Sprintf("Error deleting hash from db... %s", err) 243 | return nil 244 | } 245 | 246 | Delete(hashObj) 247 | return nil 248 | } 249 | -------------------------------------------------------------------------------- /src/emp/encryption/address.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package encryption 13 | 14 | import ( 15 | "golang.org/x/crypto/ripemd160" 16 | "crypto/elliptic" 17 | "crypto/rand" 18 | "crypto/sha512" 19 | "encoding/base64" 20 | "math/big" 21 | "strconv" 22 | ) 23 | 24 | // Create a new Public-Private ECC-256 Keypair. 25 | func CreateKey(log chan string) ([]byte, *big.Int, *big.Int) { 26 | priv, x, y, err := elliptic.GenerateKey(elliptic.P256(), rand.Reader) 27 | if err != nil { 28 | log <- "Key Generation Error" 29 | return nil, nil, nil 30 | } 31 | return priv, x, y 32 | } 33 | 34 | // Convert public key to uncompressed 65-byte slice (2 32-bit integers with prefix 0x04) 35 | func MarshalPubkey(x, y *big.Int) []byte { 36 | return elliptic.Marshal(elliptic.P256(), x, y) 37 | } 38 | 39 | // Convert 65-byte slice as created by MarshalPubkey() into an ECC-256 Public Key. 40 | func UnmarshalPubkey(data []byte) (x, y *big.Int) { 41 | return elliptic.Unmarshal(elliptic.P256(), data) 42 | } 43 | 44 | func GetCurve() elliptic.Curve { 45 | return elliptic.P256() 46 | } 47 | 48 | // Convert ECC-256 Public Key to an EMP address (raw 25 bytes). 49 | func GetAddress(log chan string, x, y *big.Int) []byte { 50 | pubKey := elliptic.Marshal(elliptic.P256(), x, y) 51 | ripemd := ripemd160.New() 52 | 53 | sum := sha512.Sum384(pubKey) 54 | sumslice := make([]byte, sha512.Size384, sha512.Size384) 55 | for i := 0; i < sha512.Size384; i++ { 56 | sumslice[i] = sum[i] 57 | } 58 | 59 | ripemd.Write(sumslice) 60 | appender := ripemd.Sum(nil) 61 | appender = appender[len(appender)-20:] 62 | address := make([]byte, 1, 1) 63 | 64 | // Version 0x01 65 | address[0] = 0x01 66 | address = append(address, appender...) 67 | 68 | sum = sha512.Sum384(address) 69 | sum = sha512.Sum384(sum[:]) 70 | 71 | for i := 0; i < 4; i++ { 72 | address = append(address, sum[i]) 73 | } 74 | 75 | return address 76 | } 77 | 78 | // Determine if address is valid (checksum is correct, correct length, and it starts with a 1-byte number). 79 | func ValidateAddress(addr []byte) bool { 80 | if len(addr) != 25 { 81 | return false 82 | } 83 | ripe := addr[:21] 84 | sum := sha512.Sum384(ripe) 85 | sum = sha512.Sum384(sum[:]) 86 | 87 | for i := 0; i < 4; i++ { 88 | if sum[i] != addr[i+21] { 89 | return false 90 | } 91 | } 92 | 93 | return true 94 | } 95 | 96 | // Converts 25-byte address to String representation. 97 | func AddressToString(addr []byte) string { 98 | if !ValidateAddress(addr) { 99 | return "" 100 | } 101 | 102 | return strconv.Itoa(int(addr[0])) + base64.StdEncoding.EncodeToString(addr[1:]) 103 | } 104 | 105 | // Converts String representation to 25-byte address. 106 | func StringToAddress(addr string) []byte { 107 | if len(addr) < 2 { 108 | return nil 109 | } 110 | data, err := base64.StdEncoding.DecodeString(addr[1:]) 111 | if err != nil { 112 | return nil 113 | } 114 | version := make([]byte, 1, 1) 115 | version[0] = byte(addr[0] - 48) 116 | address := append(version, data...) 117 | if !ValidateAddress(address) { 118 | return nil 119 | } 120 | return address 121 | } 122 | -------------------------------------------------------------------------------- /src/emp/encryption/encrypted_data.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package encryption 13 | 14 | import "errors" 15 | 16 | type EncryptedMessage struct { 17 | IV [16]byte // Initialization Vector for AES encryption 18 | PublicKey [65]byte // Random Public Key used for decryption 19 | CipherText []byte // CipherText, length is multiple of AES blocksize 20 | HMAC [32]byte // HMAC-SHA256, used to validate key before decryption 21 | } 22 | 23 | const ( 24 | ivLen = 16 25 | pubkeyLen = 65 26 | hmacLen = 32 27 | minLen = ivLen + pubkeyLen + hmacLen 28 | ) 29 | 30 | func (ret *EncryptedMessage) FromBytes(b []byte) error { 31 | if len(b) < minLen { 32 | return errors.New("Bytes too short to create EncryptedMessage object.") 33 | } 34 | if ret == nil { 35 | return errors.New("Can't fill nil object.") 36 | } 37 | 38 | copy(ret.IV[:], b[:ivLen]) 39 | copy(ret.PublicKey[:], b[ivLen:ivLen+pubkeyLen]) 40 | ret.CipherText = append(ret.CipherText, b[ivLen+pubkeyLen:len(b)-hmacLen]...) 41 | copy(ret.HMAC[:], b[len(b)-hmacLen:]) 42 | 43 | return nil 44 | } 45 | 46 | func (e *EncryptedMessage) GetBytes() []byte { 47 | if e == nil { 48 | return nil 49 | } 50 | ret := make([]byte, 0, 0) 51 | ret = append(ret, e.IV[:]...) 52 | ret = append(ret, e.PublicKey[:]...) 53 | ret = append(ret, e.CipherText...) 54 | ret = append(ret, e.HMAC[:]...) 55 | 56 | return ret 57 | } 58 | -------------------------------------------------------------------------------- /src/emp/encryption/guid.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package encryption 13 | 14 | import ( 15 | "crypto/rand" 16 | "fmt" 17 | ) 18 | 19 | // Print a randomly-generated string. 20 | func GUID(bytes int) { 21 | b := make([]byte, 16) 22 | _, _ = rand.Read(b) 23 | uuid := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) 24 | fmt.Println(uuid) 25 | } 26 | -------------------------------------------------------------------------------- /src/emp/encryption/message.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | // Package encryption wraps around Go's native crypto library to provide 13 | // ECIES and AES-256 encryption for EMP Basic and Published Messages. 14 | package encryption 15 | 16 | import ( 17 | "crypto/elliptic" 18 | "crypto/hmac" 19 | "crypto/sha256" 20 | "crypto/sha512" 21 | ) 22 | 23 | 24 | // Encrypt plainText into an Encrypted Message using the given public key. 25 | func Encrypt(log chan string, dest_pubkey []byte, plainText string) *EncryptedMessage { 26 | // Generate New Public/Private Key Pair 27 | D1, X1, Y1 := CreateKey(log) 28 | // Unmarshal the Destination's Pubkey 29 | X2, Y2 := elliptic.Unmarshal(elliptic.P256(), dest_pubkey) 30 | 31 | // Point Multiply to get new Pubkey 32 | PubX, PubY := elliptic.P256().ScalarMult(X2, Y2, D1) 33 | 34 | // Generate Pubkey hashes 35 | PubHash := sha512.Sum512(elliptic.Marshal(elliptic.P256(), PubX, PubY)) 36 | PubHash_E := PubHash[:32] 37 | PubHash_M := PubHash[32:64] 38 | 39 | IV, cipherText, _ := SymmetricEncrypt(PubHash_E, plainText) 40 | 41 | // Generate HMAC 42 | mac := hmac.New(sha256.New, PubHash_M) 43 | mac.Write(cipherText) 44 | HMAC := mac.Sum(nil) 45 | 46 | ret := new(EncryptedMessage) 47 | copy(ret.IV[:], IV[:]) 48 | copy(ret.PublicKey[:], elliptic.Marshal(elliptic.P256(), X1, Y1)) 49 | ret.CipherText = cipherText 50 | copy(ret.HMAC[:], HMAC) 51 | 52 | return ret 53 | } 54 | 55 | // Encrypt plainText into an Encrypted Published Message using the given private key. 56 | func EncryptPub(log chan string, src_privkey []byte, plainText string) *EncryptedMessage { 57 | // Generate New Public/Private Key Pair 58 | D1, X1, Y1 := CreateKey(log) 59 | 60 | // Point Multiply to get new Pubkey 61 | PubX, PubY := elliptic.P256().ScalarMult(X1, Y1, src_privkey) 62 | 63 | // Generate Pubkey hashes 64 | PubHash := sha512.Sum512(elliptic.Marshal(elliptic.P256(), PubX, PubY)) 65 | PubHash_E := PubHash[:32] 66 | PubHash_M := PubHash[32:64] 67 | 68 | IV, cipherText, _ := SymmetricEncrypt(PubHash_E, plainText) 69 | 70 | // Generate HMAC 71 | mac := hmac.New(sha256.New, PubHash_M) 72 | mac.Write(cipherText) 73 | HMAC := mac.Sum(nil) 74 | 75 | ret := new(EncryptedMessage) 76 | copy(ret.IV[:], IV[:]) 77 | copy(ret.PublicKey[:32], D1) 78 | ret.CipherText = cipherText 79 | copy(ret.HMAC[:], HMAC) 80 | 81 | return ret 82 | } 83 | 84 | // checkMAC returns true if messageMAC is a valid HMAC tag for message. 85 | func checkMAC(message, messageMAC, key []byte) bool { 86 | mac := hmac.New(sha256.New, key) 87 | mac.Write(message) 88 | expectedMAC := mac.Sum(nil) 89 | return hmac.Equal(messageMAC, expectedMAC) 90 | } 91 | 92 | // Decrypt a given Encrypted Message using the given private key. 93 | // is returned if the key fails the HMAC-SHA256 test. 94 | func Decrypt(log chan string, privKey []byte, encrypted *EncryptedMessage) []byte { 95 | if encrypted == nil || privKey == nil || log == nil { 96 | return nil 97 | } 98 | 99 | // Unmarshal the Sender's Pubkey 100 | X2, Y2 := elliptic.Unmarshal(elliptic.P256(), encrypted.PublicKey[:]) 101 | 102 | // Point Multiply to get the new Pubkey 103 | PubX, PubY := elliptic.P256().ScalarMult(X2, Y2, privKey) 104 | 105 | // Generate Pubkey hashes 106 | PubHash := sha512.Sum512(elliptic.Marshal(elliptic.P256(), PubX, PubY)) 107 | PubHash_E := PubHash[:32] 108 | PubHash_M := PubHash[32:64] 109 | 110 | // Check HMAC 111 | if !checkMAC(encrypted.CipherText[:], encrypted.HMAC[:], PubHash_M) { 112 | log <- "Invalid HMAC Message" 113 | return nil 114 | } 115 | 116 | return SymmetricDecrypt(encrypted.IV, PubHash_E, encrypted.CipherText) 117 | } 118 | 119 | // Decrypt the given Published Message using the given Pubkey. 120 | // is returned if the HMAC-SHA256 test fails. 121 | func DecryptPub(log chan string, pubkey []byte, encrypted *EncryptedMessage) []byte { 122 | if encrypted == nil || pubkey == nil || log == nil { 123 | return nil 124 | } 125 | 126 | // Unmarshal the Sender's Pubkey 127 | X2, Y2 := elliptic.Unmarshal(elliptic.P256(), pubkey) 128 | 129 | // Point Multiply to get the new Pubkey 130 | PubX, PubY := elliptic.P256().ScalarMult(X2, Y2, encrypted.PublicKey[:32]) 131 | 132 | // Generate Pubkey hashes 133 | PubHash := sha512.Sum512(elliptic.Marshal(elliptic.P256(), PubX, PubY)) 134 | PubHash_E := PubHash[:32] 135 | PubHash_M := PubHash[32:64] 136 | 137 | // Check HMAC 138 | if !checkMAC(encrypted.CipherText[:], encrypted.HMAC[:], PubHash_M) { 139 | log <- "Invalid HMAC Message" 140 | return nil 141 | } 142 | 143 | return SymmetricDecrypt(encrypted.IV, PubHash_E, encrypted.CipherText) 144 | } -------------------------------------------------------------------------------- /src/emp/encryption/message_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package encryption 13 | 14 | import ( 15 | "bytes" 16 | "crypto/elliptic" 17 | "fmt" 18 | "testing" 19 | ) 20 | 21 | func TestCryptPub(t *testing.T) { 22 | log := make(chan string, 5) 23 | 24 | // Generate personal key 25 | priv, x, y := CreateKey(log) 26 | 27 | pub := elliptic.Marshal(elliptic.P256(), x, y) 28 | 29 | message := "If you see this, the test has passed!" 30 | 31 | enc := EncryptPub(log, priv, message) 32 | 33 | plainBytes := DecryptPub(log, pub, enc) 34 | plainBytes = bytes.Split(plainBytes, []byte{0})[0] 35 | if message != string(plainBytes) { 36 | t.Fail() 37 | } 38 | } 39 | 40 | func TestCrypt(t *testing.T) { 41 | log := make(chan string, 5) 42 | 43 | // Generate personal key 44 | priv, x, y := CreateKey(log) 45 | 46 | pub := elliptic.Marshal(elliptic.P256(), x, y) 47 | 48 | message := "If you see this, the test has passed!" 49 | 50 | enc := Encrypt(log, pub, message) 51 | 52 | plainBytes := Decrypt(log, priv, enc) 53 | plainBytes = bytes.Split(plainBytes, []byte{0})[0] 54 | if message != string(plainBytes) { 55 | t.Fail() 56 | } 57 | } 58 | 59 | func TestSampleAddr(t *testing.T) { 60 | log := make(chan string, 5) 61 | 62 | // Generate Key 63 | _, x, y := CreateKey(log) 64 | 65 | byteAddr := GetAddress(log, x, y) 66 | 67 | //Check lengths 68 | if len(byteAddr) != 25 { 69 | fmt.Println("Bad length: ", len(byteAddr)) 70 | t.Fail() 71 | } 72 | 73 | if !ValidateAddress(byteAddr) { 74 | fmt.Println("Address validation falied!") 75 | t.Fail() 76 | } 77 | 78 | if string(StringToAddress(AddressToString(byteAddr))) != string(byteAddr) { 79 | fmt.Println("Error in the address/string conversion functions: ", StringToAddress(AddressToString(byteAddr))) 80 | t.Fail() 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/emp/encryption/symmetric.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package encryption 13 | 14 | import ( 15 | "crypto/aes" 16 | "crypto/cipher" 17 | "crypto/rand" 18 | "fmt" 19 | ) 20 | 21 | // Symmetrically encryptes plainText using AES-256 and the given key. Returns the IV and ciphertext. 22 | // 23 | // Special Case: If the key is length 25-bytes (an address), it is padded to 32-bytes with 0x00. 24 | func SymmetricEncrypt(key []byte, plainText string) ([aes.BlockSize]byte, []byte, error) { 25 | 26 | // Make Initialization Vector 27 | var IV [aes.BlockSize]byte 28 | n, err := rand.Reader.Read(IV[:]) 29 | if err != nil || n != 16 { 30 | return IV, nil, err 31 | } 32 | 33 | // Pad Plaintext 34 | plainBytes := []byte(plainText) 35 | 36 | pad_len := aes.BlockSize - (len(plainBytes) % aes.BlockSize) 37 | 38 | padding := make([]byte, pad_len, pad_len) 39 | plainBytes = append(plainBytes, padding...) 40 | 41 | // Generate AES Cipher 42 | 43 | if len(key) == 25 { 44 | key = append(key, make([]byte, 7, 7)...) 45 | } 46 | 47 | block, err := aes.NewCipher(key) 48 | if err != nil { 49 | fmt.Println("Uh Oh: ", err) 50 | return IV, nil, err 51 | } 52 | mode := cipher.NewCBCEncrypter(block, IV[:]) 53 | 54 | // Do encryption 55 | cipherText := make([]byte, len(plainBytes), len(plainBytes)) 56 | mode.CryptBlocks(cipherText, plainBytes) 57 | 58 | return IV, cipherText, nil 59 | } 60 | 61 | 62 | // Decrypted cipherText encrypted with AES-256 using the given IV and key. 63 | func SymmetricDecrypt(IV [aes.BlockSize]byte, key, cipherText []byte) []byte { 64 | plainText := make([]byte, len(cipherText), len(cipherText)) 65 | 66 | if len(key) == 25 { 67 | key = append(key, make([]byte, 7, 7)...) 68 | } 69 | 70 | // Generate AES Cipher 71 | block, _ := aes.NewCipher(key) 72 | mode := cipher.NewCBCDecrypter(block, IV[:]) 73 | 74 | // Do decryption 75 | plainText = make([]byte, len(cipherText), len(cipherText)) 76 | mode.CryptBlocks(plainText, cipherText[:]) 77 | 78 | return plainText 79 | } 80 | -------------------------------------------------------------------------------- /src/emp/local/localapi/localapi.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | // Package LocalAPI provides the RPC-API that allows management of Addresses and Messages 13 | // in EMPLocal. 14 | package localapi 15 | 16 | import ( 17 | "emp/api" 18 | "emp/db" 19 | "emp/encryption" 20 | "emp/local/localdb" 21 | "emp/objects" 22 | "encoding/base64" 23 | "errors" 24 | "fmt" 25 | "github.com/gorilla/rpc" 26 | "github.com/gorilla/rpc/json" 27 | "net" 28 | "net/http" 29 | "time" 30 | ) 31 | 32 | type EMPService struct { 33 | Config *api.ApiConfig 34 | } 35 | 36 | type NilParam struct{} 37 | 38 | func (s *EMPService) Version(r *http.Request, args *NilParam, reply *objects.Version) error { 39 | if !basicAuth(s.Config, r) { 40 | s.Config.Log <- fmt.Sprintf("Unauthorized RPC Request from: %s", r.RemoteAddr) 41 | return errors.New("Unauthorized") 42 | } 43 | 44 | *reply = s.Config.LocalVersion 45 | return nil 46 | } 47 | 48 | func basicAuth(config *api.ApiConfig, r *http.Request) bool { 49 | if config == nil || r == nil { 50 | return false 51 | } 52 | 53 | if config.LocalOnly { 54 | ip, _, error := net.SplitHostPort(r.RemoteAddr) 55 | if error != nil { 56 | return false 57 | } 58 | if ip != "127.0.0.1" && ip != "::1" { 59 | return false 60 | } 61 | } 62 | 63 | auth := r.Header.Get("Authorization") 64 | 65 | auth2 := "Basic " + base64.StdEncoding.EncodeToString([]byte(config.RPCUser+":"+config.RPCPass)) 66 | 67 | return (auth == auth2) 68 | } 69 | 70 | func Initialize(config *api.ApiConfig) error { 71 | 72 | e := localdb.Initialize(config.Log, config.LocalDB) 73 | 74 | if e != nil { 75 | return e 76 | } 77 | 78 | s := rpc.NewServer() 79 | s.RegisterCodec(json.NewCodec(), "application/json") 80 | service := new(EMPService) 81 | service.Config = config 82 | s.RegisterService(service, "EMPService") 83 | 84 | // Register RPC Services 85 | http.Handle("/rpc", s) 86 | 87 | // Register JS Client 88 | http.Handle("/", http.FileServer(http.Dir(config.HttpRoot))) 89 | 90 | l, e := net.Listen("tcp", fmt.Sprintf(":%d", config.RPCPort)) 91 | if e != nil { 92 | config.Log <- fmt.Sprintf("RPC Listen Error: %s", e) 93 | return e 94 | } 95 | 96 | go http.Serve(l, nil) 97 | 98 | go register(config) 99 | 100 | portStr := fmt.Sprintf(":%d", config.RPCPort) 101 | 102 | config.Log <- fmt.Sprintf("Started RPC Server on: %s", portStr) 103 | return nil 104 | } 105 | 106 | func Cleanup() { 107 | localdb.Cleanup() 108 | } 109 | 110 | // Handle Pubkey, Message, and Purge Registration 111 | func register(config *api.ApiConfig) { 112 | var message objects.Message 113 | var txid [16]byte 114 | 115 | for { 116 | select { 117 | case pubHash := <-config.PubkeyRegister: 118 | 119 | // Check if pubkey is in database... 120 | pubkey := checkPubkey(config, pubHash) 121 | 122 | if pubkey == nil { 123 | break 124 | } 125 | 126 | outbox := localdb.GetBox(localdb.OUTBOX) 127 | for _, metamsg := range outbox { 128 | recvHash := objects.MakeHash([]byte(metamsg.Recipient)) 129 | if string(pubHash.GetBytes()) == string(recvHash.GetBytes()) { 130 | // Send message and move to sendbox 131 | msg, err := localdb.GetMessageDetail(metamsg.TxidHash) 132 | if err != nil { 133 | config.Log <- err.Error() 134 | break 135 | } 136 | msg.Encrypted = encryption.Encrypt(config.Log, pubkey, string(msg.Decrypted.GetBytes())) 137 | msg.MetaMessage.Timestamp = time.Now().Round(time.Second) 138 | err = localdb.AddUpdateMessage(msg, localdb.SENDBOX) 139 | if err != nil { 140 | config.Log <- err.Error() 141 | break 142 | } 143 | 144 | sendMsg := new(objects.Message) 145 | sendMsg.Timestamp = msg.MetaMessage.Timestamp 146 | sendMsg.TxidHash = msg.MetaMessage.TxidHash 147 | sendMsg.AddrHash = recvHash 148 | sendMsg.Content = *msg.Encrypted 149 | 150 | config.RecvQueue <- *objects.MakeFrame(objects.MSG, objects.BROADCAST, sendMsg) 151 | } 152 | } 153 | 154 | case message = <-config.MessageRegister: 155 | // If address is registered, store message in inbox 156 | detail, err := localdb.GetAddressDetail(message.AddrHash) 157 | if err != nil { 158 | config.Log <- "Message address not in database..." 159 | break 160 | } 161 | if !detail.IsRegistered { 162 | config.Log <- "Message not for registered address..." 163 | break 164 | } 165 | 166 | config.Log <- "Registering new encrypted message..." 167 | 168 | msg := new(objects.FullMessage) 169 | msg.MetaMessage.TxidHash = message.TxidHash 170 | msg.MetaMessage.Timestamp = message.Timestamp 171 | msg.MetaMessage.Recipient = detail.String 172 | msg.Encrypted = &message.Content 173 | 174 | err = localdb.AddUpdateMessage(msg, localdb.INBOX) 175 | if err != nil { 176 | config.Log <- err.Error() 177 | } 178 | case message = <-config.PubRegister: 179 | // If address is registered, store message in inbox 180 | detail, err := localdb.GetAddressDetail(message.AddrHash) 181 | if err != nil { 182 | config.Log <- "Message address not in database..." 183 | break 184 | } 185 | if !detail.IsSubscribed { 186 | config.Log <- "Not Subscribed to Address..." 187 | break 188 | } 189 | 190 | config.Log <- "Registering new publication..." 191 | 192 | msg := new(objects.FullMessage) 193 | msg.MetaMessage.TxidHash = message.TxidHash 194 | msg.MetaMessage.Timestamp = message.Timestamp 195 | msg.MetaMessage.Sender = detail.String 196 | msg.MetaMessage.Recipient = "" 197 | msg.Encrypted = &message.Content 198 | 199 | msg.Decrypted = new(objects.DecryptedMessage) 200 | msg.Decrypted.FromBytes(encryption.DecryptPub(config.Log, detail.Pubkey, msg.Encrypted)) 201 | 202 | err = localdb.AddUpdateMessage(msg, localdb.INBOX) 203 | if err != nil { 204 | config.Log <- err.Error() 205 | } 206 | case txid = <-config.PurgeRegister: 207 | // If Message in database, mark as purged 208 | detail, err := localdb.GetMessageDetail(objects.MakeHash(txid[:])) 209 | if err != nil { 210 | break 211 | } 212 | detail.MetaMessage.Purged = true 213 | err = localdb.AddUpdateMessage(detail, -1) 214 | if err != nil { 215 | config.Log <- fmt.Sprintf("Error registering purge: %s", err) 216 | } 217 | } // End select 218 | } // End for 219 | } // End register 220 | 221 | func checkPubkey(config *api.ApiConfig, addrHash objects.Hash) []byte { 222 | 223 | // First check local DB 224 | detail, err := localdb.GetAddressDetail(addrHash) 225 | if err != nil { 226 | // If not in database, won't be able to decrypt anyway! 227 | return nil 228 | } 229 | if len(detail.Pubkey) > 0 { 230 | if db.Contains(addrHash) != db.PUBKEY { 231 | enc := new(objects.EncryptedPubkey) 232 | 233 | enc.IV, enc.Payload, _ = encryption.SymmetricEncrypt(detail.Address, string(detail.Pubkey)) 234 | enc.AddrHash = objects.MakeHash(detail.Address) 235 | 236 | config.RecvQueue <- *objects.MakeFrame(objects.PUBKEY, objects.BROADCAST, enc) 237 | } 238 | return detail.Pubkey 239 | } 240 | 241 | // If not there, check local database 242 | if db.Contains(addrHash) == db.PUBKEY { 243 | enc := db.GetPubkey(config.Log, addrHash) 244 | 245 | pubkey := encryption.SymmetricDecrypt(enc.IV, detail.Address, enc.Payload) 246 | pubkey = pubkey[:65] 247 | 248 | // Check public Key 249 | x, y := encryption.UnmarshalPubkey(pubkey) 250 | if x == nil { 251 | config.Log <- "Decrypted Public Key Invalid" 252 | return nil 253 | } 254 | 255 | address2 := encryption.GetAddress(config.Log, x, y) 256 | if string(detail.Address) != string(address2) { 257 | config.Log <- "Decrypted Public Key doesn't match provided address!" 258 | return nil 259 | } 260 | 261 | detail.Pubkey = pubkey 262 | err := localdb.AddUpdateAddress(detail) 263 | if err != nil { 264 | config.Log <- "Error adding pubkey to local database!" 265 | return nil 266 | } 267 | 268 | return pubkey 269 | } 270 | 271 | // If not there, send a pubkey request 272 | config.RecvQueue <- *objects.MakeFrame(objects.PUBKEY_REQUEST, objects.BROADCAST, &addrHash) 273 | return nil 274 | } 275 | -------------------------------------------------------------------------------- /src/emp/local/localapi/rpcfunc.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package localapi 13 | 14 | import ( 15 | "emp/encryption" 16 | "emp/local/localdb" 17 | "emp/objects" 18 | "errors" 19 | "fmt" 20 | "net/http" 21 | "quibit" 22 | ) 23 | 24 | var logChan chan string 25 | 26 | func (service *EMPService) ForgetAddress(r *http.Request, args *string, reply *NilParam) error { 27 | if !basicAuth(service.Config, r) { 28 | service.Config.Log <- fmt.Sprintf("Unauthorized RPC Request from: %s", r.RemoteAddr) 29 | return errors.New("Unauthorized") 30 | } 31 | 32 | address := encryption.StringToAddress(*args) 33 | if len(address) != 25 { 34 | return errors.New(fmt.Sprintf("Invalid Address: %s", address)) 35 | } 36 | 37 | addrHash := objects.MakeHash(address) 38 | 39 | return localdb.DeleteAddress(&addrHash) 40 | } 41 | 42 | func (service *EMPService) ConnectionStatus(r *http.Request, args *NilParam, reply *int) error { 43 | if !basicAuth(service.Config, r) { 44 | service.Config.Log <- fmt.Sprintf("Unauthorized RPC Request from: %s", r.RemoteAddr) 45 | return errors.New("Unauthorized") 46 | } 47 | 48 | *reply = quibit.Status() 49 | return nil 50 | } 51 | 52 | func (service *EMPService) GetLabel(r *http.Request, args *string, reply *string) error { 53 | if !basicAuth(service.Config, r) { 54 | service.Config.Log <- fmt.Sprintf("Unauthorized RPC Request from: %s", r.RemoteAddr) 55 | return errors.New("Unauthorized") 56 | } 57 | 58 | var err error 59 | 60 | address := encryption.StringToAddress(*args) 61 | if len(address) != 25 { 62 | return errors.New(fmt.Sprintf("Invalid Address: %s", address)) 63 | } 64 | 65 | addrHash := objects.MakeHash(address) 66 | 67 | detail, err := localdb.GetAddressDetail(addrHash) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | if len(detail.Label) > 0 { 73 | *reply = detail.Label 74 | } else { 75 | *reply = *args 76 | } 77 | return nil 78 | } 79 | 80 | func (service *EMPService) CreateAddress(r *http.Request, args *NilParam, reply *objects.AddressDetail) error { 81 | if !basicAuth(service.Config, r) { 82 | service.Config.Log <- fmt.Sprintf("Unauthorized RPC Request from: %s", r.RemoteAddr) 83 | return errors.New("Unauthorized") 84 | } 85 | 86 | // Create Address 87 | 88 | priv, x, y := encryption.CreateKey(service.Config.Log) 89 | reply.Privkey = priv 90 | if x == nil { 91 | return errors.New("Key Pair Generation Error") 92 | } 93 | 94 | reply.Pubkey = encryption.MarshalPubkey(x, y) 95 | 96 | reply.IsRegistered = true 97 | 98 | reply.Address = encryption.GetAddress(service.Config.Log, x, y) 99 | 100 | if reply.Address == nil { 101 | return errors.New("Could not create address, function returned nil.") 102 | } 103 | 104 | reply.String = encryption.AddressToString(reply.Address) 105 | 106 | // Add Address to Database 107 | err := localdb.AddUpdateAddress(reply) 108 | if err != nil { 109 | service.Config.Log <- fmt.Sprintf("Error Adding Address: ", err) 110 | return err 111 | } 112 | 113 | // Send Pubkey to Network 114 | encPub := new(objects.EncryptedPubkey) 115 | 116 | encPub.AddrHash = objects.MakeHash(reply.Address) 117 | 118 | encPub.IV, encPub.Payload, err = encryption.SymmetricEncrypt(reply.Address, string(reply.Pubkey)) 119 | if err != nil { 120 | service.Config.Log <- fmt.Sprintf("Error Encrypting Pubkey: ", err) 121 | return nil 122 | } 123 | 124 | // Record Pubkey for Network 125 | service.Config.RecvQueue <- *objects.MakeFrame(objects.PUBKEY, objects.BROADCAST, encPub) 126 | return nil 127 | } 128 | 129 | func (service *EMPService) GetAddress(r *http.Request, args *string, reply *objects.AddressDetail) error { 130 | 131 | if !basicAuth(service.Config, r) { 132 | service.Config.Log <- fmt.Sprintf("Unauthorized RPC Request from: %s", r.RemoteAddr) 133 | return errors.New("Unauthorized") 134 | } 135 | 136 | var err error 137 | 138 | address := encryption.StringToAddress(*args) 139 | if len(address) != 25 { 140 | return errors.New(fmt.Sprintf("Invalid Address: %s", address)) 141 | } 142 | 143 | addrHash := objects.MakeHash(address) 144 | 145 | detail, err := localdb.GetAddressDetail(addrHash) 146 | if err != nil { 147 | return err 148 | } 149 | 150 | // Check for pubkey 151 | if len(detail.Pubkey) == 0 { 152 | detail.Pubkey = checkPubkey(service.Config, objects.MakeHash(detail.Address)) 153 | } 154 | 155 | *reply = *detail 156 | 157 | return nil 158 | } 159 | 160 | func (service *EMPService) AddUpdateAddress(r *http.Request, args *objects.AddressDetail, reply *NilParam) error { 161 | if !basicAuth(service.Config, r) { 162 | service.Config.Log <- fmt.Sprintf("Unauthorized RPC Request from: %s", r.RemoteAddr) 163 | return errors.New("Unauthorized") 164 | } 165 | 166 | err := localdb.AddUpdateAddress(args) 167 | if err != nil { 168 | return err 169 | } 170 | 171 | checkPubkey(service.Config, objects.MakeHash(args.Address)) 172 | 173 | return nil 174 | } 175 | 176 | func (service *EMPService) ListAddresses(r *http.Request, args *bool, reply *([][2]string)) error { 177 | if !basicAuth(service.Config, r) { 178 | service.Config.Log <- fmt.Sprintf("Unauthorized RPC Request from: %s", r.RemoteAddr) 179 | return errors.New("Unauthorized") 180 | } 181 | 182 | strs := localdb.ListAddresses(*args) 183 | *reply = strs 184 | return nil 185 | } 186 | -------------------------------------------------------------------------------- /src/emp/local/localdb/localdb.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | // Package localdb provdes a local SQLite3 Database for the EMPLocal client. 13 | package localdb 14 | 15 | import ( 16 | "emp/objects" 17 | "fmt" 18 | "github.com/mxk/go-sqlite/sqlite3" 19 | "sync" 20 | ) 21 | 22 | // Database Connection 23 | var LocalDB *sqlite3.Conn 24 | var localMutex *sync.Mutex 25 | 26 | // Initialize database with mutexes from file. 27 | func Initialize(log chan string, dbFile string) error { 28 | var err error 29 | if LocalDB != nil { 30 | return nil 31 | } 32 | 33 | localMutex = new(sync.Mutex) 34 | 35 | // Create Database Connection 36 | LocalDB, err = sqlite3.Open(dbFile) 37 | if err != nil || LocalDB == nil { 38 | log <- fmt.Sprintf("Error opening sqlite database at %s... %s", dbFile, err) 39 | LocalDB = nil 40 | return err 41 | } 42 | 43 | // Create Database Schema 44 | 45 | err = LocalDB.Exec("CREATE TABLE IF NOT EXISTS addressbook (hash BLOB NOT NULL UNIQUE, address BLOB NOT NULL UNIQUE, registered INTEGER NOT NULL, pubkey BLOB, privkey BLOB, label TEXT, subscribed INTEGER NOT NULL, PRIMARY KEY (hash) ON CONFLICT REPLACE)") 46 | if err != nil { 47 | log <- fmt.Sprintf("Error setting up addressbook schema... %s", err) 48 | LocalDB = nil 49 | return err 50 | } 51 | 52 | // Migration, Ignore error 53 | LocalDB.Exec("ALTER TABLE addressbook ADD COLUMN subscribed INTEGER NOT NULL DEFAULT 0") 54 | LocalDB.Exec("ALTER TABLE addressbook ADD COLUMN encprivkey BLOB") 55 | 56 | err = LocalDB.Exec("CREATE TABLE IF NOT EXISTS msg (txid_hash BLOB NOT NULL, recipient BLOB, timestamp INTEGER, box INTEGER, encrypted BLOB, decrypted BLOB, purged INTEGER, sender BLOB, PRIMARY KEY (txid_hash) ON CONFLICT REPLACE)") 57 | if err != nil { 58 | log <- fmt.Sprintf("Error setting up msg schema... %s", err) 59 | LocalDB = nil 60 | return err 61 | } 62 | 63 | if hashList == nil { 64 | hashList = make(map[string]int) 65 | return populateHashes() 66 | } 67 | 68 | if LocalDB == nil || hashList == nil { 69 | fmt.Println("ERROR! ERROR! WTF!!! SHOULD BE INITIALIZED!") 70 | } 71 | 72 | return nil 73 | } 74 | 75 | func populateHashes() error { 76 | for s, err := LocalDB.Query("SELECT hash FROM addressbook"); err == nil; err = s.Next() { 77 | var hash []byte 78 | s.Scan(&hash) // Assigns 1st column to rowid, the rest to row 79 | hashList[string(hash)] = ADDRESS 80 | } 81 | 82 | for s, err := LocalDB.Query("SELECT txid_hash, box FROM msg"); err == nil; err = s.Next() { 83 | var hash []byte 84 | var box int 85 | s.Scan(&hash, &box) // Assigns 1st column to rowid, the rest to row 86 | hashList[string(hash)] = box 87 | } 88 | 89 | return nil 90 | } 91 | 92 | // Close and Cleanup database. 93 | func Cleanup() { 94 | LocalDB.Close() 95 | LocalDB = nil 96 | hashList = nil 97 | } 98 | 99 | // Hash Types 100 | const ( 101 | INBOX = iota // Incoming Messages 102 | OUTBOX = iota // Outgoing, Unsent Messages 103 | SENDBOX = iota // Outgoing, Sent Messages 104 | ADDRESS = iota // EMP Addresses 105 | NOTFOUND = iota // Not Found in DB 106 | ) 107 | 108 | // Hash List 109 | var hashList map[string]int 110 | 111 | // Add to global Hash List. 112 | func Add(hashObj objects.Hash, hashType int) { 113 | hash := string(hashObj.GetBytes()) 114 | if hashList != nil { 115 | hashList[hash] = hashType 116 | } 117 | } 118 | 119 | // Delete hash from Hash List 120 | func Del(hashObj objects.Hash) { 121 | hash := string(hashObj.GetBytes()) 122 | if hashList != nil { 123 | delete(hashList, hash) 124 | } 125 | } 126 | 127 | // Get type of object in Hash List 128 | func Contains(hashObj objects.Hash) int { 129 | hash := string(hashObj.GetBytes()) 130 | if hashList != nil { 131 | hashType, ok := hashList[hash] 132 | if ok { 133 | return hashType 134 | } else { 135 | return NOTFOUND 136 | } 137 | } 138 | return NOTFOUND 139 | } 140 | -------------------------------------------------------------------------------- /src/emp/objects/address.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package objects 13 | 14 | type AddressDetail struct { 15 | String string `json:"address"` // String representation of address (33-35 characters) 16 | Address []byte `json:"address_bytes"` // Byte representation of address (25 bytes) 17 | IsRegistered bool `json:"registered"` // Whether EMPLocal is saving messages to this address. 18 | IsSubscribed bool `json:"subscribed"` // Whether EMPLocal is saving Publications from this address. 19 | Pubkey []byte `json:"public_key"` // Unencrypted 65-byte public key. 20 | Privkey []byte `json:"private_key"` // Unencrypted 32-byte private key. 21 | EncPrivkey []byte `json:"encrypted_privkey"` // Encrypted private key (any length). 22 | Label string `json:"address_label"` // Human-readable label for this address. 23 | } 24 | -------------------------------------------------------------------------------- /src/emp/objects/interface.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | // Package objects provides all serializeable data structures within the EMProtocol. 13 | package objects 14 | 15 | import ( 16 | "quibit" 17 | ) 18 | 19 | type Serializer interface { 20 | GetBytes() []byte 21 | FromBytes([]byte) error 22 | } 23 | 24 | type NilPayload bool 25 | 26 | func (n *NilPayload) GetBytes() []byte { 27 | return nil 28 | } 29 | 30 | func (n *NilPayload) FromBytes(b []byte) error { 31 | return nil 32 | } 33 | 34 | // Creates a new Quibit Frame reading for sending from a Serializeable Object. 35 | func MakeFrame(command, t uint8, payload Serializer) *quibit.Frame { 36 | frame := new(quibit.Frame) 37 | frame.Configure(payload.GetBytes(), command, t) 38 | frame.Peer = "" 39 | 40 | return frame 41 | } 42 | -------------------------------------------------------------------------------- /src/emp/objects/localmsg.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package objects 13 | 14 | import ( 15 | "bytes" 16 | "encoding/binary" 17 | "errors" 18 | "emp/encryption" 19 | "time" 20 | ) 21 | 22 | type MetaMessage struct { 23 | TxidHash Hash `json:"txid_hash"` // Hash of random identifier 24 | Timestamp time.Time `json:"sent"` // Time message was sent 25 | Purged bool `json:"read"` // Whether purge token has been received 26 | Sender string `json:"sender"` // String representation of sender's address, if available. 27 | Recipient string `json:"recipient"` // String representation of recipient's address, if available. 28 | } 29 | 30 | type FullMessage struct { 31 | MetaMessage MetaMessage `json:"info"` 32 | Decrypted *DecryptedMessage `json:"decrypted"` 33 | Encrypted *encryption.EncryptedMessage `json:"encrypted"` 34 | } 35 | 36 | type DecryptedMessage struct { 37 | Txid [16]byte // Randomly generated identifier and purge token. 38 | Pubkey [65]byte // Sender's 65-byte Public Key 39 | Subject string // Human-readable subject of this message 40 | MimeType string // Mime-Type of Content 41 | Length uint32 // Length of Content in bytes 42 | Content string // Content of message, could be any data. 43 | Signature [65]byte // Sender's Signature of entire message. 44 | } 45 | 46 | func (d *DecryptedMessage) GetBytes() []byte { 47 | if d == nil { 48 | return nil 49 | } 50 | 51 | ret := append(d.Txid[:], d.Pubkey[:]...) 52 | ret = append(ret, d.Subject...) 53 | ret = append(ret, 0) 54 | ret = append(ret, d.MimeType...) 55 | ret = append(ret, 0) 56 | 57 | leng := make([]byte, 4, 4) 58 | 59 | binary.BigEndian.PutUint32(leng, d.Length) 60 | 61 | ret = append(ret, leng...) 62 | ret = append(ret, d.Content...) 63 | ret = append(ret, d.Signature[:]...) 64 | 65 | return ret 66 | } 67 | 68 | func (d *DecryptedMessage) FromBytes(data []byte) error { 69 | if d == nil { 70 | return errors.New("Can't fill nil object!") 71 | } 72 | 73 | var err error 74 | 75 | buf := bytes.NewBuffer(data) 76 | copy(d.Txid[:], buf.Next(16)) 77 | copy(d.Pubkey[:], buf.Next(65)) 78 | d.Subject, err = buf.ReadString(0) 79 | if err != nil { 80 | return err 81 | } 82 | d.Subject = d.Subject[:len(d.Subject)-1] 83 | d.MimeType, err = buf.ReadString(0) 84 | if err != nil { 85 | return err 86 | } 87 | d.MimeType = d.MimeType[:len(d.MimeType)-1] 88 | 89 | d.Length = binary.BigEndian.Uint32(buf.Next(4)) 90 | 91 | d.Content = string(buf.Next(int(d.Length))) 92 | copy(d.Signature[:], buf.Next(65)) 93 | 94 | return nil 95 | } 96 | -------------------------------------------------------------------------------- /src/emp/objects/message.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package objects 13 | 14 | import ( 15 | "bytes" 16 | "encoding/binary" 17 | "errors" 18 | "emp/encryption" 19 | "time" 20 | ) 21 | 22 | type Message struct { 23 | AddrHash Hash // Hash of Recipient's Address 24 | TxidHash Hash // Hash of random identifier 25 | Timestamp time.Time // Time that message was first broadcast 26 | Content encryption.EncryptedMessage // see package encryption 27 | } 28 | 29 | const ( 30 | msgLen = 2*hashLen + 8 31 | ) 32 | 33 | // Message Command Types, See EMPv1 Specification 34 | const ( 35 | VERSION = iota 36 | PEER = iota 37 | OBJ = iota 38 | GETOBJ = iota 39 | 40 | PUBKEY_REQUEST = iota 41 | PUBKEY = iota 42 | MSG = iota 43 | PURGE = iota 44 | 45 | CHECKTXID = iota 46 | PUB = iota 47 | ) 48 | 49 | // Quibit Types, see package quibit. 50 | const ( 51 | BROADCAST = iota 52 | REQUEST = iota 53 | REPLY = iota 54 | ) 55 | 56 | func (m *Message) FromBytes(data []byte) error { 57 | if len(data) < msgLen { 58 | return errors.New("Data too short to create message!") 59 | } 60 | if m == nil { 61 | return errors.New("Can't fill nil Message object!") 62 | } 63 | buffer := bytes.NewBuffer(data) 64 | m.AddrHash.FromBytes(buffer.Next(hashLen)) 65 | m.TxidHash.FromBytes(buffer.Next(hashLen)) 66 | m.Timestamp = time.Unix(int64(binary.BigEndian.Uint64(buffer.Next(8))), 0) 67 | m.Content.FromBytes(buffer.Bytes()) 68 | 69 | return nil 70 | 71 | } 72 | 73 | func (m *Message) GetBytes() []byte { 74 | if m == nil { 75 | return nil 76 | } 77 | 78 | ret := make([]byte, 0, msgLen) 79 | ret = append(m.AddrHash.GetBytes(), m.TxidHash.GetBytes()...) 80 | time := make([]byte, 8, 8) 81 | binary.BigEndian.PutUint64(time, uint64(m.Timestamp.Unix())) 82 | ret = append(ret, time...) 83 | ret = append(ret, m.Content.GetBytes()...) 84 | return ret 85 | } 86 | -------------------------------------------------------------------------------- /src/emp/objects/node.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package objects 13 | 14 | import ( 15 | "bytes" 16 | "encoding/binary" 17 | "errors" 18 | "net" 19 | "strconv" 20 | "time" 21 | ) 22 | 23 | const ( 24 | nodeLen = 26 25 | ) 26 | 27 | type Node struct { 28 | IP net.IP // Public IPv6 or IPv4 Address 29 | Port uint16 // Port on which TCP Server is running 30 | LastSeen time.Time // Time of last connection to Node. 31 | Attempts uint8 // Number of reconnection attempt. Currently, node is forgotten after 3 failed attempts. 32 | } 33 | 34 | type NodeList struct { 35 | Nodes map[string]Node 36 | } 37 | 38 | func (n *Node) FromString(hostPort string) error { 39 | if n == nil { 40 | return errors.New("Can't fill nil object.") 41 | } 42 | 43 | ip, port, err := net.SplitHostPort(hostPort) 44 | if err != nil { 45 | return nil 46 | } 47 | 48 | n.IP = net.ParseIP(ip) 49 | prt, err := strconv.Atoi(port) 50 | if err != nil { 51 | return err 52 | } 53 | n.Port = uint16(prt) 54 | n.LastSeen = time.Now().Round(time.Second) 55 | n.Attempts = 0 56 | return nil 57 | } 58 | 59 | func (n *Node) String() string { 60 | if n == nil { 61 | return "" 62 | } 63 | return net.JoinHostPort(n.IP.String(), strconv.Itoa(int(n.Port))) 64 | } 65 | 66 | func (n *NodeList) GetBytes() []byte { 67 | if n == nil { 68 | return nil 69 | } 70 | if n.Nodes == nil { 71 | return nil 72 | } 73 | 74 | ret := make([]byte, 0, nodeLen*len(n.Nodes)) 75 | 76 | for _, node := range n.Nodes { 77 | nBytes := make([]byte, nodeLen, nodeLen) 78 | copy(nBytes, []byte(node.IP)) 79 | binary.BigEndian.PutUint16(nBytes[16:18], node.Port) 80 | binary.BigEndian.PutUint64(nBytes[18:26], uint64(node.LastSeen.Unix())) 81 | ret = append(ret, nBytes...) 82 | } 83 | 84 | return ret 85 | } 86 | 87 | func (n *NodeList) FromBytes(data []byte) error { 88 | if len(data)%nodeLen != 0 { 89 | return errors.New("Incorrect length for a Node List.") 90 | } 91 | if n == nil { 92 | return errors.New("Can't configure nil Node List") 93 | } 94 | if n.Nodes == nil { 95 | n.Nodes = make(map[string]Node) 96 | } 97 | 98 | for i := 0; i < len(data); i += nodeLen { 99 | b := bytes.NewBuffer(data[i : i+nodeLen]) 100 | node := new(Node) 101 | node.IP = net.IP(b.Next(16)) 102 | node.Port = binary.BigEndian.Uint16(b.Next(2)) 103 | node.LastSeen = time.Unix(int64(binary.BigEndian.Uint64(b.Next(8))), 0) 104 | node.Attempts = 0 105 | n.Nodes[node.String()] = *node 106 | } 107 | 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /src/emp/objects/obj.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package objects 13 | 14 | import ( 15 | "crypto/sha512" 16 | "errors" 17 | ) 18 | 19 | const ( 20 | hashLen = 48 21 | ) 22 | 23 | type Hash [hashLen]byte 24 | 25 | // Create a SHA-384 Hash of data. 26 | func MakeHash(data []byte) Hash { 27 | hashArr := sha512.Sum384(data) 28 | return Hash(hashArr) 29 | } 30 | 31 | func (h *Hash) GetBytes() []byte { 32 | if h == nil { 33 | return nil 34 | } 35 | hashArr := [hashLen]byte(*h) 36 | return hashArr[:] 37 | } 38 | 39 | func (h *Hash) FromBytes(data []byte) error { 40 | if h == nil { 41 | return errors.New("Can't fill nil Hash Object.") 42 | } 43 | if len(data) != hashLen { 44 | return errors.New("Invalid hash length.") 45 | } 46 | for i := 0; i < hashLen; i++ { 47 | (*h)[i] = data[i] 48 | } 49 | 50 | return nil 51 | } 52 | 53 | type Obj struct { 54 | HashList []Hash 55 | } 56 | 57 | func (o *Obj) GetBytes() []byte { 58 | if o == nil { 59 | return nil 60 | } 61 | if o.HashList == nil { 62 | return nil 63 | } 64 | 65 | ret := make([]byte, 0, hashLen*len(o.HashList)) 66 | for _, hash := range o.HashList { 67 | ret = append(ret, hash.GetBytes()...) 68 | } 69 | return ret 70 | } 71 | 72 | func (o *Obj) FromBytes(data []byte) error { 73 | if o == nil { 74 | return errors.New("Can't fill nil Obj Object.") 75 | } 76 | if len(data)%hashLen != 0 { 77 | return errors.New("Invalid hashlist Length!") 78 | } 79 | 80 | for i := 0; i < len(data); i += hashLen { 81 | h := new(Hash) 82 | err := h.FromBytes(data[i : i+hashLen]) 83 | if err != nil { 84 | return err 85 | } 86 | o.HashList = append(o.HashList, *h) 87 | } 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /src/emp/objects/objects_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package objects 13 | 14 | import ( 15 | "crypto/elliptic" 16 | "fmt" 17 | "net" 18 | "emp/encryption" 19 | "testing" 20 | "time" 21 | ) 22 | 23 | func TestVersion(t *testing.T) { 24 | v := new(Version) 25 | v.Version = uint16(1) 26 | v.Timestamp = time.Unix(0, 0) 27 | v.IpAddress = net.ParseIP("1.2.3.4") 28 | v.Port = uint16(4444) 29 | v.UserAgent = "Hello World!" 30 | 31 | verBytes := v.GetBytes() 32 | if len(verBytes) != verLen+12 { 33 | fmt.Println("Incorrect Byte Length: ", verBytes) 34 | t.Fail() 35 | } 36 | 37 | v2 := new(Version) 38 | err := v2.FromBytes(verBytes) 39 | if err != nil { 40 | fmt.Println("Error Decoding: ", err) 41 | t.FailNow() 42 | } 43 | 44 | if v2.Version != 1 || v2.Timestamp != time.Unix(0, 0) || v2.IpAddress.String() != "1.2.3.4" || v2.Port != 4444 || v2.UserAgent != "Hello World!" { 45 | fmt.Println("Incorrect decoded version: ", v2) 46 | t.Fail() 47 | } 48 | } 49 | 50 | func TestNodes(t *testing.T) { 51 | n := new(NodeList) 52 | n.Nodes = make(map[string]Node) 53 | n2 := new(NodeList) 54 | n2.Nodes = make(map[string]Node) 55 | node1 := new(Node) 56 | node2 := new(Node) 57 | 58 | node1.IP = net.ParseIP("1.2.3.4") 59 | node1.Port = uint16(4444) 60 | node1.LastSeen = time.Now().Round(time.Second) 61 | 62 | node2.IP = net.ParseIP("5.6.7.8") 63 | node2.Port = uint16(5555) 64 | node2.LastSeen = time.Now().Round(time.Second) 65 | 66 | n.Nodes[node1.String()] = *node1 67 | n.Nodes[node2.String()] = *node2 68 | 69 | nBytes := n.GetBytes() 70 | if len(nBytes) != 2*nodeLen { 71 | fmt.Println("Byte Lengh Incorrect: ", nBytes) 72 | t.FailNow() 73 | } 74 | 75 | err := n2.FromBytes(nBytes) 76 | if err != nil { 77 | fmt.Println("Error Decoding: ", err) 78 | } 79 | 80 | for key, _ := range n.Nodes { 81 | if n2.Nodes[key].IP.String() != n.Nodes[key].IP.String() || n2.Nodes[key].Port != n.Nodes[key].Port { 82 | fmt.Println("Nodes don't match!", n2.Nodes) 83 | t.FailNow() 84 | } 85 | } 86 | } 87 | 88 | func TestObj(t *testing.T) { 89 | o := new(Obj) 90 | o.HashList = make([]Hash, 2, 2) 91 | 92 | o.HashList[0] = MakeHash([]byte{'a', 'b', 'c', 'd'}) 93 | o.HashList[1] = MakeHash([]byte{'e', 'f', 'g', 'h'}) 94 | 95 | o2 := new(Obj) 96 | 97 | oBytes := o.GetBytes() 98 | if len(oBytes) != 2*hashLen { 99 | fmt.Println("Incorrect Obj Length! ", oBytes) 100 | t.FailNow() 101 | } 102 | 103 | err := o2.FromBytes(oBytes) 104 | if err != nil { 105 | fmt.Println("Error while decoding obj: ", err) 106 | t.FailNow() 107 | } 108 | 109 | if string(o2.HashList[0].GetBytes()) != string(o.HashList[0].GetBytes()) || string(o2.HashList[1].GetBytes()) != string(o.HashList[1].GetBytes()) { 110 | fmt.Println("Incorrect decoding of obj: ", o2) 111 | t.Fail() 112 | } 113 | } 114 | 115 | func TestPubkey(t *testing.T) { 116 | p := new(EncryptedPubkey) 117 | var err error 118 | 119 | address := make([]byte, 25, 25) 120 | pubkey := [65]byte{'a'} 121 | p.AddrHash = MakeHash(address) 122 | p.IV, p.Payload, err = encryption.SymmetricEncrypt(address, string(pubkey[:])) 123 | if err != nil { 124 | fmt.Println("Could not encrypt pubkey: ", err) 125 | t.FailNow() 126 | } 127 | 128 | pBytes := p.GetBytes() 129 | if len(pBytes) != 144 { 130 | fmt.Println("Incorrect length for pubkey: ", pBytes) 131 | t.FailNow() 132 | } 133 | 134 | pubkey2 := new(EncryptedPubkey) 135 | err = pubkey2.FromBytes(pBytes) 136 | if err != nil { 137 | fmt.Println("Error decoding pubkey: ", err) 138 | t.Fail() 139 | } 140 | if string(pubkey2.AddrHash.GetBytes()) != string(p.AddrHash.GetBytes()) { 141 | fmt.Println("Incorrect Address Hash: ", pubkey2.AddrHash) 142 | t.FailNow() 143 | } 144 | 145 | pubkeyTest := encryption.SymmetricDecrypt(pubkey2.IV, address, pubkey2.Payload) 146 | if string(pubkeyTest[:65]) != string(pubkey[:]) { 147 | fmt.Println("Incorrect public key decryption: ", pubkeyTest) 148 | t.Fail() 149 | } 150 | } 151 | 152 | func TestPurge(t *testing.T) { 153 | p := new(Purge) 154 | p.Txid = [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 155 | pBytes := p.GetBytes() 156 | if len(pBytes) != 16 { 157 | fmt.Println("Error encoding purge: ", pBytes) 158 | t.FailNow() 159 | } 160 | 161 | p2 := new(Purge) 162 | p2.FromBytes(pBytes) 163 | if string(p2.Txid[:]) != string(p.Txid[:]) { 164 | fmt.Println("Incorrect decoding: ", p2.Txid) 165 | t.Fail() 166 | } 167 | } 168 | 169 | func TestMessage(t *testing.T) { 170 | log := make(chan string, 100) 171 | priv, x, y := encryption.CreateKey(log) 172 | pub := elliptic.Marshal(elliptic.P256(), x, y) 173 | address := encryption.GetAddress(log, x, y) 174 | 175 | msg := new(Message) 176 | msg.AddrHash = MakeHash(address) 177 | msg.TxidHash = MakeHash([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) 178 | msg.Timestamp = time.Now().Round(time.Second) 179 | msg.Content = *encryption.Encrypt(log, pub, "Hello World!") 180 | 181 | mBytes := msg.GetBytes() 182 | if mBytes == nil { 183 | fmt.Println("Error Encoding Message!") 184 | t.FailNow() 185 | } 186 | 187 | msg2 := new(Message) 188 | msg2.FromBytes(mBytes) 189 | if string(msg2.AddrHash.GetBytes()) != string(msg.AddrHash.GetBytes()) || string(msg2.TxidHash.GetBytes()) != string(msg.TxidHash.GetBytes()) || msg2.Timestamp.Unix() != msg.Timestamp.Unix() { 190 | fmt.Println("Message Header incorrect: ", msg2) 191 | t.FailNow() 192 | } 193 | 194 | if string(encryption.Decrypt(log, priv, &msg.Content)[:12]) != "Hello World!" { 195 | fmt.Println("Message content incorrect: ", string(encryption.Decrypt(log, priv, &msg.Content)[:12])) 196 | t.Fail() 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/emp/objects/pubkey.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package objects 13 | 14 | import ( 15 | "bytes" 16 | "errors" 17 | ) 18 | 19 | const ( 20 | encPubLen = 144 21 | ) 22 | 23 | type EncryptedPubkey struct { 24 | AddrHash Hash // Hash of address that own this public key. 25 | IV [16]byte // IV for AES-256 encryption of public key 26 | Payload []byte // Public key encrypted with AES-256. The Address is the key. 27 | } 28 | 29 | func (e *EncryptedPubkey) GetBytes() []byte { 30 | if e == nil { 31 | return nil 32 | } 33 | 34 | ret := make([]byte, hashLen, encPubLen) 35 | 36 | copy(ret, e.AddrHash.GetBytes()) 37 | ret = append(ret, e.IV[:]...) 38 | ret = append(ret, e.Payload[:]...) 39 | return ret 40 | } 41 | 42 | func (e *EncryptedPubkey) FromBytes(data []byte) error { 43 | if e == nil { 44 | return errors.New("Can't fill nil EncryptedPubkey Object.") 45 | } 46 | if len(data) < encPubLen { 47 | return errors.New("Data too short for encrypted public key.") 48 | } 49 | 50 | b := bytes.NewBuffer(data) 51 | e.AddrHash.FromBytes(b.Next(hashLen)) 52 | copy(e.IV[:], b.Next(16)) 53 | e.Payload = append(e.Payload, b.Next(80)...) 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /src/emp/objects/purge.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package objects 13 | 14 | import "errors" 15 | 16 | const ( 17 | purgeLen = 16 18 | ) 19 | 20 | type Purge struct { 21 | Txid [16]byte // Random message identifier. 22 | } 23 | 24 | func (p *Purge) GetBytes() []byte { 25 | if p == nil { 26 | return nil 27 | } 28 | 29 | ret := make([]byte, 0, purgeLen) 30 | 31 | ret = append(ret, p.Txid[:]...) 32 | 33 | return ret 34 | } 35 | 36 | func (p *Purge) FromBytes(data []byte) error { 37 | if p == nil { 38 | return errors.New("Can't fill nil Purge Object.") 39 | } 40 | if len(data) != purgeLen { 41 | return errors.New("Data too short for encrypted public key.") 42 | } 43 | 44 | copy(p.Txid[:], data) 45 | 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /src/emp/objects/version.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2014 JARST, LLC. 3 | 4 | This file is part of EMP. 5 | 6 | EMP is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the included 9 | LICENSE file for more details. 10 | **/ 11 | 12 | package objects 13 | 14 | import ( 15 | "bytes" 16 | "encoding/binary" 17 | "errors" 18 | "net" 19 | "time" 20 | ) 21 | 22 | const ( 23 | LOCAL_VERSION = 1 24 | LOCAL_USER = "emp v0.1" 25 | verLen = 28 26 | ) 27 | 28 | type Version struct { 29 | Version uint16 `json:"version"` // Protocol Version, currently 1 30 | Timestamp time.Time `json:"timestamp"` // Current server timestamp, should be within 5 minutes to connect. 31 | IpAddress net.IP `json:"ip_address"` // Public IPv6 or IPv4 Address 32 | Port uint16 `json:"port"` // Public-facing port with running TCP server 33 | UserAgent string `json:"user_agent"` // Node-provided (spoofable) User agent. 34 | } 35 | 36 | func (v *Version) FromBytes(data []byte) error { 37 | if len(data) < verLen { 38 | return errors.New("Data too short!") 39 | } 40 | if v == nil { 41 | return errors.New("Could not load nil version.") 42 | } 43 | 44 | buffer := bytes.NewBuffer(data) 45 | 46 | v.Version = binary.BigEndian.Uint16(buffer.Next(2)) 47 | v.Timestamp = time.Unix(int64(binary.BigEndian.Uint64(buffer.Next(8))), 0) 48 | v.IpAddress = net.IP(buffer.Next(16)) 49 | v.Port = binary.BigEndian.Uint16(buffer.Next(2)) 50 | v.UserAgent = buffer.String() 51 | return nil 52 | } 53 | 54 | func (v *Version) GetBytes() []byte { 55 | if v == nil { 56 | return nil 57 | } 58 | 59 | ret := make([]byte, verLen, verLen) 60 | 61 | binary.BigEndian.PutUint16(ret[:2], v.Version) 62 | binary.BigEndian.PutUint64(ret[2:10], uint64(v.Timestamp.Unix())) 63 | copy(ret[10:26], []byte(v.IpAddress)) 64 | binary.BigEndian.PutUint16(ret[26:28], v.Port) 65 | ret = append(ret, v.UserAgent...) 66 | 67 | return ret 68 | } 69 | --------------------------------------------------------------------------------