mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-01 21:52:02 +08:00
Attestation allows Teleport admins a manner of control over the devices used for MFA. Attestation support for Webauthn works similarly to U2F attestation, but provides both allow and deny lists for device CAs. The device's attestation certificate must clear both lists before registration is complete. Previously-registered devices are not affected (similarly to U2F). * Better error messages on webauthn/httpserver * Make mocku2f attestation cert public * Implement attestation for Webauthn
200 lines
5.4 KiB
HTML
200 lines
5.4 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>WebAuthn Playground</title>
|
|
</head>
|
|
|
|
<body>
|
|
<form>
|
|
<p>Login and Registration</p>
|
|
|
|
<label for="user">User:</label>
|
|
<input type="text" id="user" name="user"/>
|
|
<br/>
|
|
|
|
<label for="pass">Password:</label>
|
|
<input type="password" id="pass" name="pass"/>
|
|
<br/>
|
|
</form>
|
|
<form>
|
|
<p>Registration</p>
|
|
|
|
<label for="devName">Device name:</label>
|
|
<input type="text" id="devName" name="devName"/>
|
|
<br/>
|
|
|
|
<label for="totpCode">OTP code:</label>
|
|
<input type="text" id="totpCode" name="totpCode"/>
|
|
<br/>
|
|
</form>
|
|
<button onclick="login()">Login</button>
|
|
<button onclick="register()">Register</button>
|
|
</body>
|
|
|
|
<script>
|
|
// URLBase64 to ArrayBuffer.
|
|
function bufferDecode(value) {
|
|
return Uint8Array.from(atob(value), c => c.charCodeAt(0));
|
|
}
|
|
|
|
// ArrayBuffer to URLBase64.
|
|
function bufferEncode(value) {
|
|
return btoa(String.fromCharCode.apply(null, new Uint8Array(value)))
|
|
.replace(/\+/g, "-")
|
|
.replace(/\//g, "_")
|
|
.replace(/=/g, "");
|
|
}
|
|
|
|
function register() {
|
|
const user = document.getElementById("user").value;
|
|
const pass = document.getElementById("pass").value;
|
|
const devName = document.getElementById("devName").value;
|
|
const totpCode = document.getElementById("totpCode").value;
|
|
|
|
if (user === "" || pass === "") {
|
|
alert("user and password required");
|
|
return;
|
|
}
|
|
if (devName === "" || totpCode === "") {
|
|
alert("device information required");
|
|
return;
|
|
}
|
|
|
|
const body = JSON.stringify({
|
|
"user": user,
|
|
"pass": pass,
|
|
"dev_name": devName,
|
|
"totp_code": totpCode,
|
|
})
|
|
return fetch("/register/1", {
|
|
method: "POST",
|
|
headers: {"Content-Type": "application/json"},
|
|
body: body
|
|
})
|
|
.then(res => {
|
|
if (res.status === 200) {
|
|
return res;
|
|
}
|
|
throw res;
|
|
})
|
|
.then(res => res.json())
|
|
.then(res => {
|
|
res.publicKey.challenge = bufferDecode(res.publicKey.challenge);
|
|
res.publicKey.user.id = bufferDecode(res.publicKey.user.id);
|
|
if (res.publicKey.excludeCredentials) {
|
|
res.publicKey.excludeCredentials.forEach(function (x) {
|
|
x.id = bufferDecode(x.id);
|
|
})
|
|
}
|
|
console.log("CredentialCreation", res);
|
|
return res;
|
|
})
|
|
.then(res => navigator.credentials.create({publicKey: res.publicKey}))
|
|
.then(credential => {
|
|
const body = JSON.stringify({
|
|
id: credential.id,
|
|
rawId: bufferEncode(credential.rawId),
|
|
type: credential.type,
|
|
response: {
|
|
attestationObject: bufferEncode(credential.response.attestationObject),
|
|
clientDataJSON: bufferEncode(credential.response.clientDataJSON),
|
|
},
|
|
});
|
|
console.log("CredentialCreationResponse", body);
|
|
return fetch("/register/2", {
|
|
method: "POST",
|
|
headers: {"Content-Type": "application/json"},
|
|
body: body,
|
|
})
|
|
})
|
|
.then(res => {
|
|
if (res.status === 200) {
|
|
alert("registration successful");
|
|
} else {
|
|
throw res;
|
|
}
|
|
})
|
|
.catch(res => {
|
|
res.text().then(body => {
|
|
alert("Registration failed with HTTP " + res.status + "/" + res.statusMessage + ": " + body);
|
|
})
|
|
});
|
|
}
|
|
|
|
function login() {
|
|
const user = document.getElementById("user").value;
|
|
const pass = document.getElementById("pass").value;
|
|
|
|
if (user === "" || pass === "") {
|
|
alert("user and password required");
|
|
return;
|
|
}
|
|
|
|
const body = JSON.stringify({
|
|
"user": user,
|
|
"pass": pass,
|
|
})
|
|
return fetch("/login/1", {
|
|
method: "POST",
|
|
headers: {"Content-Type": "application/json"},
|
|
body: body
|
|
})
|
|
.then(res => {
|
|
if (res.status === 200) {
|
|
return res;
|
|
}
|
|
throw res;
|
|
})
|
|
.then(res => res.json())
|
|
.then(res => {
|
|
res.publicKey.challenge = bufferDecode(res.publicKey.challenge);
|
|
res.publicKey.allowCredentials.forEach(function (x) {
|
|
x.id = bufferDecode(x.id);
|
|
});
|
|
console.log("CredentialAssertion", res);
|
|
return res;
|
|
})
|
|
.then(res => navigator.credentials.get({publicKey: res.publicKey}))
|
|
.then(credential => {
|
|
const body = JSON.stringify({
|
|
user: user,
|
|
id: credential.id,
|
|
rawId: bufferEncode(credential.rawId),
|
|
type: credential.type,
|
|
response: {
|
|
authenticatorData: bufferEncode(credential.response.authenticatorData),
|
|
clientDataJSON: bufferEncode(credential.response.clientDataJSON),
|
|
signature: bufferEncode(credential.response.signature),
|
|
userHandle: bufferEncode(credential.response.userHandle),
|
|
},
|
|
});
|
|
console.log("CredentialAssertionResponse", body);
|
|
return fetch("/login/2", {
|
|
method: "POST",
|
|
headers: {"Content-Type": "application/json"},
|
|
body: body,
|
|
});
|
|
})
|
|
.then(res => {
|
|
if (res.status === 200) {
|
|
alert("login successful");
|
|
} else {
|
|
throw res;
|
|
}
|
|
})
|
|
.catch(res => {
|
|
res.text().then(body => {
|
|
alert("Login failed with HTTP " + res.status + "/" + res.statusMessage + ": " + body);
|
|
})
|
|
});
|
|
}
|
|
|
|
// Is WebAuthn supported?
|
|
if (!window.PublicKeyCredential) {
|
|
alert("Error: this browser does not support WebAuthn");
|
|
}
|
|
</script>
|
|
</html>
|