[openssh-commits] [openssh] 01/01: missing files for webauthn/sshsig unit test

git+noreply at mindrot.org git+noreply at mindrot.org
Mon Jun 22 16:54:20 AEST 2020


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

commit 5098b3b6230852a80ac6cef5d53a785c789a5a56
Author: Damien Miller <djm at mindrot.org>
Date:   Mon Jun 22 16:54:02 2020 +1000

    missing files for webauthn/sshsig unit test
---
 .../sshsig/testdata/ecdsa_sk_webauthn.pub          |   1 +
 .../sshsig/testdata/ecdsa_sk_webauthn.sig          |  13 +
 regress/unittests/sshsig/webauthn.html             | 692 +++++++++++++++++++++
 3 files changed, 706 insertions(+)

diff --git a/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.pub b/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.pub
new file mode 100644
index 00000000..1597302c
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.pub
@@ -0,0 +1 @@
+sk-ecdsa-sha2-nistp256 at openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBBRGwDjs4HhJFcn4tJ5Gr72KcmRmCS1OirETxaXvnsNApgoOLF1a/7rxldfSMHm73eT1nhHe97W8qicPPEAKDJQAAAALbWluZHJvdC5vcmc=
diff --git a/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.sig b/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.sig
new file mode 100644
index 00000000..4bdd8edc
--- /dev/null
+++ b/regress/unittests/sshsig/testdata/ecdsa_sk_webauthn.sig
@@ -0,0 +1,13 @@
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAAIYAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBvcGVuc3NoLmNvbQ
+AAAAhuaXN0cDI1NgAAAEEEFEbAOOzgeEkVyfi0nkavvYpyZGYJLU6KsRPFpe+ew0CmCg4s
+XVr/uvGV19Iwebvd5PWeEd73tbyqJw88QAoMlAAAAAttaW5kcm90Lm9yZwAAAAh1bml0dG
+VzdAAAAAAAAAAGc2hhNTEyAAABhwAAACt3ZWJhdXRobi1zay1lY2RzYS1zaGEyLW5pc3Rw
+MjU2QG9wZW5zc2guY29tAAAASQAAACBj2oMT9tb5wRXe6mdmf4/lgAO8wrgr95ouozwNg4
+itnQAAACEAtU9g5wz3HchUiLfLD6plr9T4TiJ32lVCrATSjpiy0SMBAAADHwAAABdodHRw
+czovL3d3dy5taW5kcm90Lm9yZwAAAON7InR5cGUiOiJ3ZWJhdXRobi5nZXQiLCJjaGFsbG
+VuZ2UiOiJVMU5JVTBsSEFBQUFDSFZ1YVhSMFpYTjBBQUFBQUFBQUFBWnphR0UxTVRJQUFB
+QkFMTHU4WmdjU3h0Nk1zRlV6dWlaZ0c2R3dNZEo5ZDd4ZUU3WW9SSXcwZzlpSEpfd3NGRD
+cxbzRXbHllenZGV0VqYnFRMHFDN0Z3R3Bqa2pVUVAtTmQ2dyIsIm9yaWdpbiI6Imh0dHBz
+Oi8vd3d3Lm1pbmRyb3Qub3JnIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQAAAAA=
+-----END SSH SIGNATURE-----
diff --git a/regress/unittests/sshsig/webauthn.html b/regress/unittests/sshsig/webauthn.html
new file mode 100644
index 00000000..953041e6
--- /dev/null
+++ b/regress/unittests/sshsig/webauthn.html
@@ -0,0 +1,692 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>webauthn test</title>
+</head>
+<body onload="init()">
+<h1>webauthn test</h1>
+<p>
+This is a demo/test page for generating FIDO keys and signatures in SSH
+formats. The page initially displays a form to generate a FIDO key and
+convert it to a SSH public key.
+</p>
+<p>
+Once a key has been generated, an additional form will be displayed to
+allow signing of data using the just-generated key. The data may be signed
+as either a raw SSH signature or wrapped in a sshsig message (the latter is
+easier to test using command-line tools.
+</p>
+<p>
+Lots of debugging is printed along the way.
+</p>
+<h2>Enroll</h2>
+<span id="error" style="color: #800; font-weight: bold; font-size: 150%;"></span>
+<form id="enrollform">
+<table>
+<tr>
+<td><b>Username:</b></td>
+<td><input id="username" type="text" size="20" name="user" value="test" /></td>
+</tr>
+<tr><td></td><td><input id="assertsubmit" type="submit" value="submit" /></td></tr>
+</table>
+</form>
+<span id="enrollresult" style="visibility: hidden;">
+<h2>clientData</h2>
+<pre id="enrollresultjson" style="color: #008; font-family: monospace;"></pre>
+<h2>attestationObject raw</h2>
+<pre id="enrollresultraw" style="color: #008; font-family: monospace;"></pre>
+<h2>attestationObject</h2>
+<pre id="enrollresultattestobj" style="color: #008; font-family: monospace;"></pre>
+<h2>authData raw</h2>
+<pre id="enrollresultauthdataraw" style="color: #008; font-family: monospace;"></pre>
+<h2>authData</h2>
+<pre id="enrollresultauthdata" style="color: #008; font-family: monospace;"></pre>
+<h2>SSH pubkey blob</h2>
+<pre id="enrollresultpkblob" style="color: #008; font-family: monospace;"></pre>
+<h2>SSH pubkey string</h2>
+<pre id="enrollresultpk" style="color: #008; font-family: monospace;"></pre>
+</span>
+<span id="assertsection" style="visibility: hidden;">
+<h2>Assert</h2>
+<form id="assertform">
+<span id="asserterror" style="color: #800; font-weight: bold;"></span>
+<table>
+<tr>
+<td><b>Data to sign:</b></td>
+<td><input id="message" type="text" size="20" name="message" value="test" /></td>
+</tr>
+<tr>
+<td><input id="message_sshsig" type="checkbox" checked /> use sshsig format</td>
+</tr>
+<tr>
+<td><b>Signature namespace:</b></td>
+<td><input id="message_namespace" type="text" size="20" name="namespace" value="test" /></td>
+</tr>
+<tr><td></td><td><input type="submit" value="submit" /></td></tr>
+</table>
+</form>
+</span>
+<span id="assertresult" style="visibility: hidden;">
+<h2>clientData</h2>
+<pre id="assertresultjson" style="color: #008; font-family: monospace;"></pre>
+<h2>signature raw</h2>
+<pre id="assertresultsigraw" style="color: #008; font-family: monospace;"></pre>
+<h2>authenticatorData raw</h2>
+<pre id="assertresultauthdataraw" style="color: #008; font-family: monospace;"></pre>
+<h2>authenticatorData</h2>
+<pre id="assertresultauthdata" style="color: #008; font-family: monospace;"></pre>
+<h2>signature in SSH format</h2>
+<pre id="assertresultsshsigraw" style="color: #008; font-family: monospace;"></pre>
+<h2>signature in SSH format (base64 encoded)</h2>
+<pre id="assertresultsshsigb64" style="color: #008; font-family: monospace;"></pre>
+</span>
+</body>
+<script>
+// ------------------------------------------------------------------
+// a crappy CBOR decoder - 20200401 djm at openbsd.org
+
+var CBORDecode = function(buffer) {
+	this.buf = buffer
+	this.v = new DataView(buffer)
+	this.offset = 0
+}
+
+CBORDecode.prototype.empty = function() {
+	return this.offset >= this.buf.byteLength
+}
+
+CBORDecode.prototype.getU8 = function() {
+	let r = this.v.getUint8(this.offset)
+	this.offset += 1
+	return r
+}
+
+CBORDecode.prototype.getU16 = function() {
+	let r = this.v.getUint16(this.offset)
+	this.offset += 2
+	return r
+}
+
+CBORDecode.prototype.getU32 = function() {
+	let r = this.v.getUint32(this.offset)
+	this.offset += 4
+	return r
+}
+
+CBORDecode.prototype.getU64 = function() {
+	let r = this.v.getUint64(this.offset)
+	this.offset += 8
+	return r
+}
+
+CBORDecode.prototype.getCBORTypeLen = function() {
+	let tl, t, l
+	tl = this.getU8()
+	t = (tl & 0xe0) >> 5
+	l = tl & 0x1f
+	return [t, this.decodeInteger(l)]
+}
+
+CBORDecode.prototype.decodeInteger = function(len) {
+	switch (len) {
+	case 0x18: return this.getU8()
+	case 0x19: return this.getU16()
+	case 0x20: return this.getU32()
+	case 0x21: return this.getU64()
+	default:
+		if (len <= 23) {
+			return len
+		}
+		throw new Error("Unsupported int type 0x" + len.toString(16))
+	}
+}
+
+CBORDecode.prototype.decodeNegint = function(len) {
+	let r = -(this.decodeInteger(len) + 1)
+	return r
+}
+
+CBORDecode.prototype.decodeByteString = function(len) {
+	let r = this.buf.slice(this.offset, this.offset + len)
+	this.offset += len
+	return r
+}
+
+CBORDecode.prototype.decodeTextString = function(len) {
+	let u8dec = new TextDecoder('utf-8')
+	r = u8dec.decode(this.decodeByteString(len))
+	return r
+}
+
+CBORDecode.prototype.decodeArray = function(len, level) {
+	let r = []
+	for (let i = 0; i < len; i++) {
+		let v = this.decodeInternal(level)
+		r.push(v)
+		// console.log("decodeArray level " + level.toString() + " index " + i.toString() + " value " + JSON.stringify(v))
+	}
+	return r
+}
+
+CBORDecode.prototype.decodeMap = function(len, level) {
+	let r = {}
+	for (let i = 0; i < len; i++) {
+		let k = this.decodeInternal(level)
+		let v = this.decodeInternal(level)
+		r[k] = v
+		// console.log("decodeMap level " + level.toString() + " key " + k.toString() + " value " + JSON.stringify(v))
+		// XXX check string keys, duplicates
+	}
+	return r
+}
+
+CBORDecode.prototype.decodePrimitive = function(t) {
+	switch (t) {
+	case 20: return false
+	case 21: return true
+	case 22: return null
+	case 23: return undefined
+	default:
+		throw new Error("Unsupported primitive 0x" + t.toString(2))
+	}
+}
+
+CBORDecode.prototype.decodeInternal = function(level) {
+	if (level > 256) {
+		throw new Error("CBOR nesting too deep")
+	}
+	let t, l, r
+	[t, l] = this.getCBORTypeLen()
+	// console.log("decode level " + level.toString() + " type " + t.toString() + " len " + l.toString())
+	switch (t) {
+		case 0:
+			r = this.decodeInteger(l)
+			break
+		case 1:
+			r = this.decodeNegint(l)
+			break
+		case 2:
+			r = this.decodeByteString(l)
+			break
+		case 3:
+			r = this.decodeTextString(l)
+			break
+		case 4:
+			r = this.decodeArray(l, level + 1)
+			break
+		case 5:
+			r = this.decodeMap(l, level + 1)
+			break
+		case 6:
+			console.log("XXX ignored semantic tag " + this.decodeInteger(l).toString())
+			break;
+		case 7:
+			r = this.decodePrimitive(l)
+			break
+		default:
+			throw new Error("Unsupported type 0x" + t.toString(2) + " len " + l.toString())
+	}
+	// console.log("decode level " + level.toString() + " value " + JSON.stringify(r))
+	return r
+}
+
+CBORDecode.prototype.decode = function() {
+	return this.decodeInternal(0)
+}
+
+// ------------------------------------------------------------------
+// a crappy SSH message packer - 20200401 djm at openbsd.org
+
+var SSHMSG = function() {
+	this.r = []
+}
+
+SSHMSG.prototype.serialise = function() {
+	let len = 0
+	for (buf of this.r) {
+		len += buf.length
+	}
+	let r = new ArrayBuffer(len)
+	let v = new Uint8Array(r)
+	let offset = 0
+	for (buf of this.r) {
+		v.set(buf, offset)
+		offset += buf.length
+	}
+	if (offset != r.byteLength) {
+		throw new Error("djm can't count")
+	}
+	return r
+}
+
+SSHMSG.prototype.serialiseBase64 = function(v) {
+	let b = this.serialise()
+	return btoa(String.fromCharCode(...new Uint8Array(b)));
+}
+
+SSHMSG.prototype.putU8 = function(v) {
+	this.r.push(new Uint8Array([v]))
+}
+
+SSHMSG.prototype.putU32 = function(v) {
+	this.r.push(new Uint8Array([
+		(v >> 24) & 0xff,
+		(v >> 16) & 0xff,
+		(v >> 8) & 0xff,
+		(v & 0xff)
+	]))
+}
+
+SSHMSG.prototype.put = function(v) {
+	this.r.push(new Uint8Array(v))
+}
+
+SSHMSG.prototype.putString = function(v) {
+	let enc = new TextEncoder();
+	let venc = enc.encode(v)
+	this.putU32(venc.length)
+	this.put(venc)
+}
+
+SSHMSG.prototype.putSSHMSG = function(v) {
+	let msg = v.serialise()
+	this.putU32(msg.byteLength)
+	this.put(msg)
+}
+
+SSHMSG.prototype.putBytes = function(v) {
+	this.putU32(v.byteLength)
+	this.put(v)
+}
+
+SSHMSG.prototype.putECPoint = function(x, y) {
+	let x8 = new Uint8Array(x)
+	let y8 = new Uint8Array(y)
+	this.putU32(1 + x8.length + y8.length)
+	this.putU8(0x04) // Uncompressed point format.
+	this.put(x8)
+	this.put(y8)
+}
+
+// ------------------------------------------------------------------
+// webauthn to SSH glue - djm at openbsd.org 20200408
+
+function error(msg, ...args) {
+	document.getElementById("error").innerText = msg
+	console.log(msg)
+	for (const arg of args) {
+		console.dir(arg)
+	}
+}
+function hexdump(buf) {
+	const hex = Array.from(new Uint8Array(buf)).map(
+		b => b.toString(16).padStart(2, "0"))
+	const fmt = new Array()
+	for (let i = 0; i < hex.length; i++) {
+		if ((i % 16) == 0) {
+			// Prepend length every 16 bytes.
+			fmt.push(i.toString(16).padStart(4, "0"))
+			fmt.push("  ")
+		}
+		fmt.push(hex[i])
+		fmt.push(" ")
+		if ((i % 16) == 15) {
+			fmt.push("\n")
+		}
+	}
+	return fmt.join("")
+}
+function enrollform_submit(event) {
+	event.preventDefault();
+	console.log("submitted")
+	username = event.target.elements.username.value
+	if (username === "") {
+		error("no username specified")
+		return false
+	}
+	enrollStart(username)
+}
+function enrollStart(username) {
+	let challenge = new Uint8Array(32)
+	window.crypto.getRandomValues(challenge)
+	let userid = new Uint8Array(8)
+	window.crypto.getRandomValues(userid)
+
+	console.log("challenge:" + btoa(challenge))
+	console.log("userid:" + btoa(userid))
+
+	let pkopts = {
+		challenge: challenge,
+		rp: {
+			name: "mindrot.org",
+			id: "mindrot.org",
+		},
+		user: {
+			id: userid,
+			name: username,
+			displayName: username,
+		},
+		authenticatorSelection: {
+			authenticatorAttachment: "cross-platform",
+			userVerification: "discouraged",
+		},
+		pubKeyCredParams: [{alg: -7, type: "public-key"}], // ES256
+		timeout: 30 * 1000,
+	};
+	console.dir(pkopts)
+	window.enrollOpts = pkopts
+	let credpromise = navigator.credentials.create({ publicKey: pkopts });
+	credpromise.then(enrollSuccess, enrollFailure)
+}
+function enrollFailure(result) {
+	error("Enroll failed", result)
+}
+function enrollSuccess(result) {
+	console.log("Enroll succeeded")
+	console.dir(result)
+	window.enrollResult = result
+	document.getElementById("enrollresult").style.visibility = "visible"
+
+	// Show the clientData
+	let u8dec = new TextDecoder('utf-8')
+	clientData = u8dec.decode(result.response.clientDataJSON)
+	document.getElementById("enrollresultjson").innerText = clientData
+
+	// Decode and show the attestationObject
+	document.getElementById("enrollresultraw").innerText = hexdump(result.response.attestationObject)
+	let aod = new CBORDecode(result.response.attestationObject)
+	let attestationObject = aod.decode()
+	console.log("attestationObject")
+	console.dir(attestationObject)
+	document.getElementById("enrollresultattestobj").innerText = JSON.stringify(attestationObject)
+
+	// Decode and show the authData
+	document.getElementById("enrollresultauthdataraw").innerText = hexdump(attestationObject.authData)
+	let authData = decodeAuthenticatorData(attestationObject.authData, true)
+	console.log("authData")
+	console.dir(authData)
+	window.enrollAuthData = authData
+	document.getElementById("enrollresultauthdata").innerText = JSON.stringify(authData)
+
+	// Reformat the pubkey as a SSH key for easy verification
+	window.rawKey = reformatPubkey(authData.attestedCredentialData.credentialPublicKey, window.enrollOpts.rp.id)
+	console.log("SSH pubkey blob")
+	console.dir(window.rawKey)
+	document.getElementById("enrollresultpkblob").innerText = hexdump(window.rawKey)
+	let pk64 = btoa(String.fromCharCode(...new Uint8Array(window.rawKey)));
+	let pk = "sk-ecdsa-sha2-nistp256 at openssh.com " + pk64
+	document.getElementById("enrollresultpk").innerText = pk
+
+	// Success: show the assertion form.
+	document.getElementById("assertsection").style.visibility = "visible"
+}
+
+function decodeAuthenticatorData(authData, expectCred) {
+	let r = new Object()
+	let v = new DataView(authData)
+
+	r.rpIdHash = authData.slice(0, 32)
+	r.flags = v.getUint8(32)
+	r.signCount = v.getUint32(33)
+
+	// Decode attestedCredentialData if present.
+	let offset = 37
+	let acd = new Object()
+	if (expectCred) {
+		acd.aaguid = authData.slice(offset, offset+16)
+		offset += 16
+		let credentialIdLength = v.getUint16(offset)     
+		offset += 2
+		acd.credentialIdLength = credentialIdLength
+		acd.credentialId = authData.slice(offset, offset+credentialIdLength)
+		offset += credentialIdLength
+		r.attestedCredentialData = acd
+	}
+	console.log("XXXXX " + offset.toString())
+	let pubkeyrest = authData.slice(offset, authData.byteLength)
+	let pkdecode = new CBORDecode(pubkeyrest)
+	if (expectCred) {
+		// XXX unsafe: doesn't mandate COSE canonical format.
+		acd.credentialPublicKey = pkdecode.decode()
+	}
+	if (!pkdecode.empty()) {
+		// Decode extensions if present.
+		r.extensions = pkdecode.decode()
+	}
+	return r
+}
+
+function reformatPubkey(pk, rpid) {
+	// pk is in COSE format. We only care about a tiny subset.
+	if (pk[1] != 2) {
+		console.dir(pk)
+		throw new Error("pubkey is not EC")
+	}
+	if (pk[-1] != 1) {
+		throw new Error("pubkey is not in P256")
+	}
+	if (pk[3] != -7) {
+		throw new Error("pubkey is not ES256")
+	}
+	if (pk[-2].byteLength != 32 || pk[-3].byteLength != 32) {
+		throw new Error("pubkey EC coords have bad length")
+	}
+	let msg = new SSHMSG()
+	msg.putString("sk-ecdsa-sha2-nistp256 at openssh.com")	// Key type
+	msg.putString("nistp256")				// Key curve
+	msg.putECPoint(pk[-2], pk[-3])				// EC key
+	msg.putString(rpid)					// RP ID
+	return msg.serialise()
+}
+
+async function assertform_submit(event) {
+	event.preventDefault();
+	console.log("submitted")
+	message = event.target.elements.message.value
+	if (message === "") {
+		error("no message specified")
+		return false
+	}
+	let enc = new TextEncoder()
+	let encmsg = enc.encode(message)
+	window.assertSignRaw = !event.target.elements.message_sshsig.checked
+	console.log("using sshsig ", !window.assertSignRaw)
+	if (window.assertSignRaw) {
+		assertStart(encmsg)
+		return
+	}
+	// Format a sshsig-style message.
+	window.sigHashAlg = "sha512"
+	let msghash = await crypto.subtle.digest("SHA-512", encmsg);
+	console.log("raw message hash")
+	console.dir(msghash)
+	window.sigNamespace = event.target.elements.message_namespace.value
+	let sigbuf = new SSHMSG()
+	sigbuf.put(enc.encode("SSHSIG"))
+	sigbuf.putString(window.sigNamespace)
+	sigbuf.putU32(0) // Reserved string
+	sigbuf.putString(window.sigHashAlg)
+	sigbuf.putBytes(msghash)
+	let msg = sigbuf.serialise()
+	console.log("sigbuf")
+	console.dir(msg)
+	assertStart(msg)
+}
+
+function assertStart(message) {
+	let assertReqOpts = {
+		challenge: message,
+		rpId: "mindrot.org",
+		allowCredentials: [{
+			type: 'public-key',
+			id: window.enrollResult.rawId,
+		}],
+		userVerification: "discouraged",
+		timeout: (30 * 1000),
+	}
+	console.log("assertReqOpts")
+	console.dir(assertReqOpts)
+	window.assertReqOpts = assertReqOpts
+	let assertpromise = navigator.credentials.get({
+		publicKey: assertReqOpts
+	});
+	assertpromise.then(assertSuccess, assertFailure)
+}
+function assertFailure(result) {
+	error("Assertion failed", result)
+}
+function linewrap(s) {
+	const linelen = 70
+	let ret = ""
+	for (let i = 0; i < s.length; i += linelen) {
+		end = i + linelen
+		if (end > s.length) {
+			end = s.length
+		}
+		if (i > 0) {
+			ret += "\n"
+		}
+		ret += s.slice(i, end)
+	}
+	return ret + "\n"
+}
+function assertSuccess(result) {
+	console.log("Assertion succeeded")
+	console.dir(result)
+	window.assertResult = result
+	document.getElementById("assertresult").style.visibility = "visible"
+
+	// show the clientData.
+	let u8dec = new TextDecoder('utf-8')
+	clientData = u8dec.decode(result.response.clientDataJSON)
+	document.getElementById("assertresultjson").innerText = clientData
+
+	// show the signature.
+	document.getElementById("assertresultsigraw").innerText = hexdump(result.response.signature)
+	
+	// decode and show the authData.
+	document.getElementById("assertresultauthdataraw").innerText = hexdump(result.response.authenticatorData)
+	authData = decodeAuthenticatorData(result.response.authenticatorData, false)
+	document.getElementById("assertresultauthdata").innerText = JSON.stringify(authData)
+
+	// Parse and reformat the signature to an SSH style signature.
+	let sshsig = reformatSignature(result.response.signature, clientData, authData)
+	document.getElementById("assertresultsshsigraw").innerText = hexdump(sshsig)
+	let sig64 = btoa(String.fromCharCode(...new Uint8Array(sshsig)));
+	if (window.assertSignRaw) {
+		document.getElementById("assertresultsshsigb64").innerText = sig64
+	} else {
+		document.getElementById("assertresultsshsigb64").innerText =
+		    "-----BEGIN SSH SIGNATURE-----\n" + linewrap(sig64) +
+		    "-----END SSH SIGNATURE-----\n";
+	}
+}
+
+function reformatSignature(sig, clientData, authData) {
+	if (sig.byteLength < 2) {
+		throw new Error("signature is too short")
+	}
+	let offset = 0
+	let v = new DataView(sig)
+	// Expect an ASN.1 SEQUENCE that exactly spans the signature.
+	if (v.getUint8(offset) != 0x30) {
+		throw new Error("signature not an ASN.1 sequence")
+	}
+	offset++
+	let seqlen = v.getUint8(offset)
+	offset++
+	if ((seqlen & 0x80) != 0 || seqlen != sig.byteLength - offset) {
+		throw new Error("signature has unexpected length " + seqlen.toString() + " vs expected " + (sig.byteLength - offset).toString())
+	}
+
+	// Parse 'r' INTEGER value.
+	if (v.getUint8(offset) != 0x02) {
+		throw new Error("signature r not an ASN.1 integer")
+	}
+	offset++
+	let rlen = v.getUint8(offset)
+	offset++
+	if ((rlen & 0x80) != 0 || rlen > sig.byteLength - offset) {
+		throw new Error("signature r has unexpected length " + rlen.toString() + " vs buffer " + (sig.byteLength - offset).toString())
+	}
+	let r = sig.slice(offset, offset + rlen)
+	offset += rlen
+	console.log("sig_r")
+	console.dir(r)
+
+	// Parse 's' INTEGER value.
+	if (v.getUint8(offset) != 0x02) {
+		throw new Error("signature r not an ASN.1 integer")
+	}
+	offset++
+	let slen = v.getUint8(offset)
+	offset++
+	if ((slen & 0x80) != 0 || slen > sig.byteLength - offset) {
+		throw new Error("signature s has unexpected length " + slen.toString() + " vs buffer " + (sig.byteLength - offset).toString())
+	}
+	let s = sig.slice(offset, offset + slen)
+	console.log("sig_s")
+	console.dir(s)
+	offset += slen
+
+	if (offset != sig.byteLength) {
+		throw new Error("unexpected final offset during signature parsing " + offset.toString() + " expected " + sig.byteLength.toString())
+	}
+	
+	// Reformat as an SSH signature.
+	let clientDataParsed = JSON.parse(clientData)
+	let innersig = new SSHMSG()
+	innersig.putBytes(r)
+	innersig.putBytes(s)
+
+	let rawsshsig = new SSHMSG()
+	rawsshsig.putString("webauthn-sk-ecdsa-sha2-nistp256 at openssh.com")
+	rawsshsig.putSSHMSG(innersig)
+	rawsshsig.putU8(authData.flags)
+	rawsshsig.putU32(authData.signCount)
+	rawsshsig.putString(clientDataParsed.origin)
+	rawsshsig.putString(clientData)
+	if (authData.extensions == undefined) {
+		rawsshsig.putU32(0)
+	} else {
+		rawsshsig.putBytes(authData.extensions)
+	}
+
+	if (window.assertSignRaw) {
+		return rawsshsig.serialise()
+	}
+	// Format as SSHSIG.
+	let enc = new TextEncoder()
+	let sshsig = new SSHMSG()
+	sshsig.put(enc.encode("SSHSIG"))
+	sshsig.putU32(0x01) // Signature version.
+	sshsig.putBytes(window.rawKey)
+	sshsig.putString(window.sigNamespace)
+	sshsig.putU32(0) // Reserved string
+	sshsig.putString(window.sigHashAlg)
+	sshsig.putBytes(rawsshsig.serialise())
+	return sshsig.serialise()
+}
+
+function toggleNamespaceVisibility() {
+	const assertsigtype = document.getElementById('message_sshsig');
+	const assertsignamespace = document.getElementById('message_namespace');
+	assertsignamespace.disabled = !assertsigtype.checked;
+}
+
+function init() {
+	if (document.location.protocol != "https:") {
+		error("This page must be loaded via https")
+		const assertsubmit = document.getElementById('assertsubmit')
+		assertsubmit.disabled = true
+	}
+	const enrollform = document.getElementById('enrollform');
+	enrollform.addEventListener('submit', enrollform_submit);
+	const assertform = document.getElementById('assertform');
+	assertform.addEventListener('submit', assertform_submit);
+	const assertsigtype = document.getElementById('message_sshsig');
+	assertsigtype.onclick = toggleNamespaceVisibility;
+}
+</script>
+
+</html>

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list