Skip to content

Instantly share code, notes, and snippets.

@brdrcol
Created June 19, 2018 14:39
Show Gist options
  • Save brdrcol/842ac886a35459aa0a6c0f1e8082db87 to your computer and use it in GitHub Desktop.
Save brdrcol/842ac886a35459aa0a6c0f1e8082db87 to your computer and use it in GitHub Desktop.
Sloppy demo of PTV API request signing using web crypto
<!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&amp;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