Unverified Commit cc63248c authored by Nadim Kobeissi's avatar Nadim Kobeissi 💾 Committed by GitHub
Browse files

Merge pull request #35 from georgio/master

Resolves #4: WASM Support
parents f4bb9cc3 9310c39a
......@@ -2,7 +2,7 @@
## Version 0.3, based on Noise Protocol Revision 34.
### Overview
The Noise Explorer command-line tool can parse Noise Handshake Patterns according to the original specification. It can generate cryptographic models for formal verification, including security queries, top-level processes and malicious principals, for testing against an active or passive attacker. Noise Explorer can also generate fully functional discrete implementations for any Noise Handshake Pattern, written in the [Go](https://golang.org) and [Rust](https://www.rust-lang.org/) programming languages.
The Noise Explorer command-line tool can parse Noise Handshake Patterns according to the original specification. It can generate cryptographic models for formal verification, including security queries, top-level processes and malicious principals, for testing against an active or passive attacker. Noise Explorer can also generate fully functional discrete implementations for any Noise Handshake Pattern, written in the [Go](https://golang.org) and [Rust](https://www.rust-lang.org) programming languages, as well as [WebAssembly](https://webassembly.org) binaries.
Noise Explorer can also render results from the ProVerif output into an elegant and easy to read HTML format: the pattern results that can be explored on [Noise Explorer](https://noiseexplorer.com) were generated using the Noise Explorer command-line tool.
......@@ -12,6 +12,7 @@ Noise Explorer can also render results from the ProVerif output into an elegant
| ProVerif | ✔️ | ✔️ | ✔️ |
| Go | ✔️ | ✔️ | ✔️ |
| Rust | ✔️ | ✔️ | ✔️ |
| WASM | ✔️ | ✔️ | ✔️ |
### Usage
......@@ -21,7 +22,7 @@ Noise Explorer version 0.3 (specification revision 34)
Noise Explorer has three individual modes: generation, rendering and web interface.
Generation:
--generate=(json|pv|go|rs): Specify output format.
--generate=(json|pv|go|rs|wasm): Specify output format.
--pattern=[file]: Specify input pattern file (required).
--attacker=(active|passive): Specify ProVerif attacker type (default: active).
......@@ -43,6 +44,7 @@ Help:
1. [Node](https://nodejs.org) is required for running Noise Explorer locally.
2. [ProVerif](http://prosecco.gforge.inria.fr/personal/bblanche/proverif/) is required for verifying generated models.
2. [Go](https://golang.org) and [Rust](https://www.rust-lang.org) are required for running generated implementations.
3. [Google Chrome](https://chrome.google.com) is required for testing Wasm implementations.
### Preparation
1. `cd src`
......@@ -56,7 +58,14 @@ Help:
To quickly translate all Noise handshake patterns in the `patterns` folder to ProVerif models, simply run `make models` after completing the steps outlined in the Preparation section of this document. The models will be available in the `models` folder.
### Implementation Generation
To quickly translate all Noise handshake patterns in the `patterns` folder to Go and Rust implementations, simply run `make implementations` after completing the steps outlined in the Preparation section of this document. The software will be available in the `implementations` folder. Running `make tests` will verify these implementations against test vectors obtained from [Cacophony](https://github.com/centromere/cacophony), a Haskell implementation of the Noise Protocol Framework.
To quickly translate all Noise handshake patterns in the `patterns` folder to Go and Rust implementations, simply run `make implementations` after completing the steps outlined in the Preparation section of this document. The software will be available in the `implementations` folder. Note that the implementations found under `implementations/wasm` and the ones found under `implementations/rust` use different cryptographic libraries for compaltibility purposes.
### WebAssembly Binaries
Wasm binaries and relevant helper files are found under `implementations/wasm/*/pkg`. To re-compile the Wasm binaries run `make wasm` after generating implementations using `make implementations`.
### Implementation Testing
Running `make tests` will verify these implementations against test vectors obtained from [Cacophony](https://github.com/centromere/cacophony), a Haskell implementation of the Noise Protocol Framework.
Testing Wasm implementations will utilize [Google Chrome](https://chrome.google.com) in headless mode.
### Implementation Documentation
To view the documentation of a generated Rust implementation, navigate to the directory of the desired pattern and run `cargo doc --open --no-deps`.
......
......@@ -38,7 +38,7 @@
Noise Explorer has three individual modes: generation, rendering and web interface.
Generation:
--generate=(json|pv|go|rs): Specify output format.
--generate=(json|pv|go|rs|wasm): Specify output format.
--pattern=[file]: Specify input pattern file (required).
--attacker=(active|passive): Specify ProVerif attacker type (default: active).
......
......@@ -14,6 +14,7 @@
<script src="res/js/parser/noise2Pv.js"></script>
<script src="res/js/parser/noise2Go.js"></script>
<script src="res/js/parser/noise2Rs.js"></script>
<script src="res/js/parser/noise2Wasm.js"></script>
<script src="res/js/parser/noiseReader.js"></script>
<script src="res/js/jszip.js"></script>
<script src="res/js/noiseWebIde.js"></script>
......@@ -38,6 +39,9 @@
$('rsLink').addEventListener('click', (event) => {
rsGen($('patternInput').value, 'rsLink', true);
});
$('wasmLink').addEventListener('click', (event) => {
wasmGen($('patternInput').value, 'wasmLink', true);
});
$('patternInput').focus();
processPatternInput($('patternInput').value);
});
......@@ -58,14 +62,14 @@
<a href="https://eprint.iacr.org/2018/766">Scientific Paper</a>
</div>
</div>
<div class="results" style="height: 770px;">
<div class="results" style="height: 870px;">
<div class="arrowsFrame">
<h1 id="patternName">IKpsk2</h1>
<div class="arrows" style="height: 770px;">
<svg id="patternArrows" class="noColor" style="height: 770px;"></svg>
<div class="arrows" style="height: 870px;">
<svg id="patternArrows" class="noColor" style="height: 870px;"></svg>
</div>
</div>
<div class="resultsExplanation" style="height: 770px;">
<div class="resultsExplanation" style="height: 870px;">
<h2>Design and Explore Noise Handshake Patterns</h2>
<p>
Noise Explorer is an online engine for reasoning about <a href="http://noiseprotocol.org/" target="_blank">Noise Protocol Framework</a> (revision 34) Handshake Patterns. Noise Explorer allows you to:
......@@ -111,6 +115,13 @@ IKpsk2:
<span class="modelType">written in rust</span>
</a>
</p>
<h3>Generate WASM-Friendly Secure Protocol Implementation Code</h3>
<p>
<a href="#" id="wasmLink">
Get Implementation
<span class="modelType">written in rust</span>
</a>
</p>
</div>
</div>
<div class="footer">
......
......@@ -361,7 +361,8 @@ textarea#patternInput.parseInvalid {
a#pvActiveLink,
a#pvPassiveLink,
a#goLink,
a#rsLink {
a#rsLink,
a#wasmLink {
display: inline-block;
padding: 10px;
border: 1px solid var(--noiseDarkGray);
......@@ -385,7 +386,8 @@ a#pvPassiveLink {
}
a#goLink,
a#rsLink {
a#rsLink,
a#wasmLink {
background-image: url('/res/img/implementations.svg');
background-size: 45px;
}
......@@ -393,7 +395,8 @@ a#rsLink {
a#pvActiveLink.parseInvalid,
a#pvPassiveLink.parseInvalid,
a#goLink.parseInvalid,
a#rsLink.parseInvalid {
a#rsLink.parseInvalid,
a#wasmLink.parseInvalid {
background-color: var(--noiseRed) !important;
}
......
......@@ -4,7 +4,8 @@ let genReady = {
passive: false
},
go: false,
rs: false
rs: false,
wasm: false
};
let $ = (id) => { return document.getElementById(id) };
......@@ -54,6 +55,16 @@ let rsRender = (patternInput, parsedPattern, rs) => {
return rs;
};
let wasmRender = (patternInput, parsedPattern, wasm) => {
let parsedWasm = NOISE2WASM.parse(parsedPattern);
wasm[0] = wasm[0].replace('/* $NOISE2WASM_N$ */', `/*\n${patternInput}\n*/`);
wasm[5] = wasm[5].replace('/* $NOISE2WASM_I$ */', parsedWasm.i);
wasm[5] = wasm[5].replace('/* $NOISE2WASM_W$ */', parsedWasm.w);
wasm[5] = wasm[5].replace('/* $NOISE2WASM_R$ */', parsedWasm.r);
wasm[6] = wasm[6].replace('/* $NOISE2WASM_P$ */', parsedWasm.p);
return wasm;
};
let getPv = (patternInput, parsedPattern, passive, cb) => {
let pvTemplates = [
'0params',
......@@ -163,6 +174,43 @@ let getRs = (patternInput, parsedPattern, cb) => {
});
};
let getWasm = (patternInput, parsedPattern, cb) => {
let wasmTemplates = [
'0params',
'1types',
'2consts',
'3utils',
'4prims',
'5state',
'6processes',
'7error',
'8macros'
];
let wasm = ['', '', '', '', '', '', '', ''];
wasmTemplates.forEach((templateFile, i) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', `/res/wasm/${templateFile}.rs`);
xhr.onreadystatechange = () => {
if (
(xhr.readyState !== 4) ||
(xhr.status !== 200)
) {
return false;
}
wasm[i] = xhr.responseText;
let full = 0;
wasm.forEach((slot) => {
slot.length? full++ : full;
});
if (full === wasm.length) {
let output = wasmRender(patternInput, parsedPattern, wasm);
cb(output);
}
};
xhr.send();
});
};
let processPatternInput = (patternInput) => {
let parsedPattern = {};
genReady.pv.active = false;
......@@ -297,3 +345,53 @@ let rsGen = (patternInput, aId, autoClick) => {
return false;
};
let wasmGen = (patternInput, aId, autoClick) => {
let parsedPattern = {};
if (genReady.wasm) {
return true;
}
try {
parsedPattern = peg$parse(patternInput);
} catch (e) {
alert('Please first ensure that your Noise pattern is valid.');
return false;
}
getWasm(patternInput, parsedPattern, (wasm) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', `/res/wasm/Cargo.toml`);
xhr.onreadystatechange = () => {
if (
(xhr.readyState !== 4) ||
(xhr.status !== 200)
) {
return false;
}
let cargo = xhr.responseText
.replace('$NOISE2WASM_N$', parsedPattern.name.toLowerCase());
let zip = new JSZip();
zip.file('Cargo.toml', cargo);
let src = zip.folder('src');
src.file('lib.rs', wasm[0]);
src.file('types.rs', wasm[1]);
src.file('consts.rs', wasm[2]);
src.file('utils.rs', wasm[3]);
src.file('prims.rs', wasm[4]);
src.file('state.rs', wasm[5]);
src.file('noisesession.rs', wasm[6]);
src.file('error.rs', wasm[7]);
src.file('macros.rs', wasm[8]);
zip.generateAsync({
type:'blob'
}).then((blob) => {
let wasmBlob = window.URL.createObjectURL(blob);
genReady.wasm = true;
$(aId).href = wasmBlob;
$(aId).download = `${parsedPattern.name}.noise.wasm.zip`;
autoClick? $(aId).click() : false;
});
};
xhr.send();
});
return false;
};
../../src/wasm
\ No newline at end of file
[package]
name = "noiseexplorer_i1k"
version = "0.1.2"
authors = ["Symbolic Software <georgio@symbolic.software>"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
blake2-rfc = "0.2.18"
constant_time_eq = "0.1.3"
chacha20-poly1305-aead = {version = "0.1.2", git = "https://github.com/georgio/chacha20-poly1305-aead"}
getrandom = "0.1.3"
hex = "0.3.2"
rust-crypto-wasm = "0.3.1"
zeroize = "0.6.0"
wasm-bindgen = "0.2.46"
[dev-dependencies]
wasm-bindgen-test = "0.2"
[package.metadata.wasm-pack.profile.dev.wasm-bindgen]
debug-js-glue = true
demangle-name-section = true
dwarf-debug-info = false
[package.metadata.wasm-pack.profile.profiling.wasm-bindgen]
debug-js-glue = false
demangle-name-section = true
dwarf-debug-info = false
[package.metadata.wasm-pack.profile.release.wasm-bindgen]
debug-js-glue = false
demangle-name-section = true
dwarf-debug-info = false
\ No newline at end of file
/* tslint:disable */
/**
*/
export class Key {
free(): void;
}
/**
*/
export class Keypair {
free(): void;
}
/**
* A `NoiseSession` object is used to keep track of the states of both local
* and remote parties before, during, and after a handshake.
*
* It contains:
* - `hs`: Keeps track of the local party\'s state while a handshake is being
* performed.
* - `h`: Stores the handshake hash output after a successful handshake in a
* Hash object. Is initialized as array of 0 bytes.
* - `cs1`: Keeps track of the local party\'s post-handshake state. Contains a
* cryptographic key and a nonce.
* - `cs2`: Keeps track of the remote party\'s post-handshake state. Contains a
* cryptographic key and a nonce.
* - `mc`: Keeps track of the total number of incoming and outgoing messages,
* including those sent during a handshake.
* - `i`: `bool` value that indicates whether this session corresponds to the
* local or remote party.
* - `is_transport`: `bool` value that indicates whether a handshake has been
* performed succesfully with a remote session and the session is in transport mode.
*/
export class NoiseSession {
free(): void;
}
/**
*/
export class PrivateKey {
free(): void;
}
/**
*/
export class Psk {
free(): void;
}
/**
*/
export class PublicKey {
free(): void;
}
/**
* If `module_or_path` is {RequestInfo}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {RequestInfo | BufferSource | WebAssembly.Module} module_or_path
*
* @returns {Promise<any>}
*/
export default function init (module_or_path?: RequestInfo | BufferSource | WebAssembly.Module): Promise<any>;
\ No newline at end of file
let wasm;
let cachedTextDecoder = new TextDecoder('utf-8');
let cachegetUint8Memory = null;
function getUint8Memory() {
if (cachegetUint8Memory === null || cachegetUint8Memory.buffer !== wasm.memory.buffer) {
cachegetUint8Memory = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory;
}
function getStringFromWasm(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory().subarray(ptr, ptr + len));
}
/**
*/
export class Key {
free() {
const ptr = this.ptr;
this.ptr = 0;
wasm.__wbg_key_free(ptr);
}
}
/**
*/
export class Keypair {
free() {
const ptr = this.ptr;
this.ptr = 0;
wasm.__wbg_keypair_free(ptr);
}
}
/**
* A `NoiseSession` object is used to keep track of the states of both local
* and remote parties before, during, and after a handshake.
*
* It contains:
* - `hs`: Keeps track of the local party\'s state while a handshake is being
* performed.
* - `h`: Stores the handshake hash output after a successful handshake in a
* Hash object. Is initialized as array of 0 bytes.
* - `cs1`: Keeps track of the local party\'s post-handshake state. Contains a
* cryptographic key and a nonce.
* - `cs2`: Keeps track of the remote party\'s post-handshake state. Contains a
* cryptographic key and a nonce.
* - `mc`: Keeps track of the total number of incoming and outgoing messages,
* including those sent during a handshake.
* - `i`: `bool` value that indicates whether this session corresponds to the
* local or remote party.
* - `is_transport`: `bool` value that indicates whether a handshake has been
* performed succesfully with a remote session and the session is in transport mode.
*/
export class NoiseSession {
free() {
const ptr = this.ptr;
this.ptr = 0;
wasm.__wbg_noisesession_free(ptr);
}
}
/**
*/
export class PrivateKey {
free() {
const ptr = this.ptr;
this.ptr = 0;
wasm.__wbg_privatekey_free(ptr);
}
}
/**
*/
export class Psk {
free() {
const ptr = this.ptr;
this.ptr = 0;
wasm.__wbg_psk_free(ptr);
}
}
/**
*/
export class PublicKey {
free() {
const ptr = this.ptr;
this.ptr = 0;
wasm.__wbg_publickey_free(ptr);
}
}
function init(module) {
if (typeof module === 'undefined') {
module = import.meta.url.replace(/\.js$/, '_bg.wasm');
}
let result;
const imports = {};
imports.wbg = {};
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
let varg0 = getStringFromWasm(arg0, arg1);
throw new Error(varg0);
};
if (module instanceof URL || typeof module === 'string' || module instanceof Request) {
const response = fetch(module);
if (typeof WebAssembly.instantiateStreaming === 'function') {
result = WebAssembly.instantiateStreaming(response, imports)
.catch(e => {
console.warn("`WebAssembly.instantiateStreaming` failed. Assuming this is because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
return response
.then(r => r.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports));
});
} else {
result = response
.then(r => r.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports));
}
} else {
result = WebAssembly.instantiate(module, imports)
.then(result => {
if (result instanceof WebAssembly.Instance) {
return { instance: result, module };
} else {
return result;
}
});
}
return result.then(({instance, module}) => {
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
return wasm;
});
}
export default init;
/* tslint:disable */
export const memory: WebAssembly.Memory;
export function __wbg_key_free(a: number): void;
export function __wbg_keypair_free(a: number): void;
export function __wbg_psk_free(a: number): void;
export function __wbg_privatekey_free(a: number): void;
export function __wbg_publickey_free(a: number): void;
export function __wbg_noisesession_free(a: number): void;
{
"name": "@noiseexplorer_ikpsk2/noiseexplorer_i1k",
"collaborators": [
"Symbolic Software <georgio@symbolic.software>"
],
"version": "0.1.2",
"files": [
"noiseexplorer_i1k_bg.wasm",
"noiseexplorer_i1k.js",
"noiseexplorer_i1k.d.ts"
],
"module": "noiseexplorer_i1k.js",
"types": "noiseexplorer_i1k.d.ts",
"sideEffects": "false"
}
\ No newline at end of file
/* ---------------------------------------------------------------- *
* CONSTANTS *
* ---------------------------------------------------------------- */
#![allow(non_snake_case, non_upper_case_globals)]
pub const DHLEN: usize = 32_usize;
pub(crate) const HASHLEN: usize = 32_usize;
pub(crate) const BLOCKLEN: usize = 64_usize;
pub(crate) const EMPTY_HASH: [u8; DHLEN] = [0_u8; HASHLEN];
pub(crate) const EMPTY_KEY: [u8; DHLEN] = [0_u8; DHLEN];
pub const MAC_LENGTH: usize = 16_usize;
pub(crate) const MAX_MESSAGE: usize = 0xFFFF;
pub(crate) const MAX_NONCE: u64 = u64::max_value();
pub(crate) const NONCE_LENGTH: usize = 12_usize;
pub(crate) const ZEROLEN: [u8; 0] = [0_u8; 0];
\ No newline at end of file
#[derive(Debug)]
pub enum NoiseError {
DecryptionError,
UnsupportedMessageLengthError,
ExhaustedNonceError,
InvalidKeyError,
InvalidPublicKeyError,
EmptyKeyError,
InvalidInputError,
DerivePublicKeyFromEmptyKeyError,
Hex(hex::FromHexError),
MissingnsError,
MissingneError,
MissingHsMacError,
MissingrsError,
MissingreError
}
impl std::fmt::Display for NoiseError {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
NoiseError::DecryptionError => write!(f, "Unsuccesful decryption."),
NoiseError::UnsupportedMessageLengthError => write!(f, "Unsupported Message Length."),
NoiseError::ExhaustedNonceError => write!(f, "Reached maximum number of messages that can be sent for this session."),
NoiseError::DerivePublicKeyFromEmptyKeyError => write!(f, "Unable to derive PublicKey."),
NoiseError::InvalidKeyError => write!(f, "Invalid Key."),
NoiseError::InvalidPublicKeyError => write!(f, "Invalid Public Key."),
NoiseError::EmptyKeyError => write!(f, "Empty Key."),
NoiseError::InvalidInputError => write!(f, "Invalid input length."),
NoiseError::MissingnsError => write!(f, "Invalid message length."),
NoiseError::MissingHsMacError => write!(f, "Invalid message length."),
NoiseError::MissingneError => write!(f, "Invalid message length."),
NoiseError::MissingrsError => write!(f, "Invalid message length."),
NoiseError::MissingreError => write!(f, "Invalid message length."),
NoiseError::Hex(ref e) => e.fmt(f),
}
}
}
impl std::error::Error for NoiseError {
fn description(&self) -> &str {