Vsevolod Rodionov, tech cutup
@jabher
let's create new in-browser app
no, no, no. with per-user storage
no, no, no. It will be private keys. No server-level storage
no, no, no. It should be for mobile browsers too
Btw, similar API got hacked yesterday by 3rd party plugin
interface Platform {
sensitive: never;
getKeys (): Key[];
sign (Data, Key): Signature;
}
interface API extends Platform {
verify (Data, Signature): Key;
}
enum Features {
OFFLINE,
CONFIRMATIONS,
MOBILE_SUPPORT,
MITM_PROTECTION,
SERVER_HIJACK_DETECT,
BAD_PLUGIN_PROTECTION
}
interface Platform {
requirements: Possible,
browsers:
| Chrome | Firefox
| Edge | Safari,
platform:
| Windows | MacOS | Linux
| iOS<{jailbroken: false}>
| Android<{rooted: false}>
}
<iframe
src="https://our.app/sign?0xDEADBEEF">
<!doctype html>
<script src="/handle.js"
integrity="sha384-..."/>
<script>
window.parent.postMessage(
await handle(location)
)
</script>
</iframe>
Disclaimer:
<iframe
src="https://our.app/sign?0xDEADBEEF">
<!doctype html>
<script src="/handle.js"
integrity="sha384-..."/>
<script>
window.parent.postMessage(
await handle(location, localStorage)
)</script></iframe>
const frame = createIframe(frameUrl)
const mo = new MutationObserver(
mutations => throw new Error()
)
mo.observe(iframe, {
attributes: true
})
document.body.appendChild(frame)
const {publicKey, privateKey} = createKeys()
const blobFrameUrl = URL.createObjectURL(new Blob([`
<iframe src=${iframeUrl}/>
<script>
window.onmessage = ({data}) =>
window.parent.postMessage(
encrypt(data, ${publicKey})
)
</script>
`], {
type: 'text/html'
}))
const {ok, headers} = await fetch(frameUrl, {
integrity: 'sha384-...',
redirect: 'manual',
cache: 'force-cache',
mode: 'cors'
})
assert(
ok,
headers.get('Cache-Control') === 'max-age=84600',
headers.has('pragma') === false
)
injectIframe(frameUrl)
//iframe.html
await navigator.serviceWorker.register('sw.js')
//sw.js
self.addEventListener('fetch', event =>
event.respondWith(new Response(`
<!doctype html>
<script>
window.parent.postMessage(
await handle(location)
)
</script>
`)))))
//iframe.html
await navigator.serviceWorker.register('sw.js')
//sw.js
self.addEventListener('fetch', event =>
event.respondWith(new Response(`
<!doctype html>
<script>
window.parent.postMessage(
await handle(location)
)
</script>
`)))))
only allow-same-origin should be required for SW interception within a sandboxed iframe
By the time the request gets to fetch, it doesn't know it came from a sandboxed iframe
doSomethingImportant = async (...args) => {
...
await notification('confirm?', {
requireInteraction: true,
data: args,
actions: [
{action: 'res', title: 'yes'},
{action: 'rej', title: 'no'}
]
})
...
}
// sw.js
self.addEventListener(
'notificationclick',
({notification, action}) => {
notification.close()
actions.callback(
notification.data,
action
)
})
self.addEventListener(
'fetch',
async event =>
event.respondWith(new Response(`
<script>
window.parent.postMessage(
${
JSON.stringify(
await handle(event.request.url)
)
})`)))
event.respondWith(Response.redirect(
'data:text/html,' +
encodeURIComponent(`
<script>parent.postMessage(${
JSON.stringify(
await handle(request.url)
)
})`))))
const response = await sendTo(
`wss://${query.remote}`,
await handle(query.args)
)
return 'data:text/html,' +
encodeURIComponent(`
<script>parent.postMessage(${
JSON.stringify(response)
})`)
Vsevolod Rodionov, tech cutup
@jabher