Created
June 19, 2018 14:39
-
-
Save brdrcol/842ac886a35459aa0a6c0f1e8082db87 to your computer and use it in GitHub Desktop.
Sloppy demo of PTV API request signing using web crypto
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>PTV API WebCrypto demo</title> | |
<style> | |
main {width: 100%; max-width: 26rem; margin: 1rem auto; font-family: sans-serif;} | |
label, input, button, a { display: block; width: 100%; padding: 0.2rem; } | |
label {padding-top: 0.25rem;} | |
a {margin: 0.25rem 0; overflow-wrap: break-word} | |
#reset {margin-top: 1rem;} | |
h1 {font-size: 1rem; margin: 0.2rem;} | |
.hint {font-size: 0.7rem; color: #444;} | |
.important {font-weight: bold;} | |
</style> | |
</head> | |
<body> | |
<main> | |
<h1>PTV API request signing demo using WebCrypto</h1> | |
<label for="devid" class="important">Developer ID</label> | |
<input type="text" id="devid"/> | |
<label for="key" class="important">Key goes here</label> | |
<input type="text" id="key"/> | |
<label for="base-url">PTV base URL</label> | |
<input type="text" id="base-url" value=""/> | |
<label for="route">URL /version/route?opts (no devid/signature)</label> | |
<input type="text" id="route" value=""/> | |
<a href="#" target="_blank" id="output"></a> | |
<span class="hint">May work from filesystem, may work from 127.0.0.1 (some browsers have restrictions on the connection types for WebCrypto usage). Try <code>python2 -m SimpleHTTPServer</code> or <code>python3 -m http.server</code> for Q&D $PWD serving.</span> | |
<button id="reset">Reset</button> | |
</main> | |
<script> | |
// References to elements | |
const ui = { | |
devId: document.getElementById('devid'), | |
keyInput: document.getElementById('key'), | |
output: document.getElementById('output'), | |
baseUrl: document.getElementById('base-url'), | |
route: document.getElementById('route'), | |
reset: document.getElementById('reset') | |
} | |
// WebCrypto key initialisation rubbish | |
let key = null | |
const crypto = { | |
type: 'raw', | |
key: null, | |
alg: { | |
name: 'HMAC', | |
hash: 'SHA-1' | |
}, | |
extractable: false, | |
methods: ['sign'] | |
} | |
// Encodes strings to UTF-8 ByteArrays | |
const te = new TextEncoder('utf-8') | |
// Handy spell from stack overflow... turns numbers into 0..F characters | |
function buf2hex(buffer) { | |
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('').toUpperCase() | |
} | |
// Regenerate the WebCrypto key | |
function rekey() { | |
console.log('rekeying') | |
crypto.key = te.encode(ui.keyInput.value) | |
window.crypto.subtle.importKey( | |
crypto.type, | |
crypto.key, | |
crypto.alg, | |
crypto.extractable, | |
crypto.methods | |
) | |
.then((newKey) => { | |
key = newKey | |
sign() | |
}) | |
.catch((err) => console.error(err)) | |
} | |
function sign() { | |
// Request url starts at /v3/ and ends at devid=xxxxxx | |
let request = ui.route.value | |
if (!request.includes('?')) { | |
request += '?' | |
} | |
request += 'devid=' + ui.devId.value | |
console.log('request', request) | |
// Turn request into bytearray | |
let msg = te.encode(request) | |
// Async signing | |
window.crypto.subtle.sign({name: "HMAC"}, key, msg) | |
.then(sig => { | |
// Signature created, parse into 0..F format | |
sig = buf2hex(sig) | |
console.log('signature', sig) | |
// Construct and display full URL | |
request = ui.baseUrl.value + request + '&signature=' + sig | |
console.log('req', request) | |
ui.output.href = request | |
ui.output.textContent = request | |
}) | |
.catch(err => { | |
console.error(err) | |
}) | |
} | |
// Reset ui inputs | |
function reset() { | |
ui.keyInput.value = 'a1b2c3d4-12ab-34cd-56ef-9f8e7d6c5b4a' | |
ui.devId.value = '1000000' | |
ui.baseUrl.value = 'http://timetableapi.ptv.vic.gov.au' | |
ui.route.value = '/v3/routes' | |
rekey() | |
} | |
// Set up event listeners, do initial keying | |
ui.devId.addEventListener('input', rekey) | |
ui.keyInput.addEventListener('input', rekey) | |
ui.baseUrl.addEventListener('input', sign) | |
ui.route.addEventListener('input', sign) | |
ui.reset.addEventListener('click', reset) | |
reset() | |
</script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment