Last active
March 24, 2023 13:52
-
-
Save kgaughan/e15ea8373da8303d3eabd66abfad4a06 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
{ | |
"detail": { | |
"attributes": { | |
"hideResponseInput": true, | |
"img": "static/img/FIDO-U2F-Security-Key-444x444.png", | |
"webAuthnSignRequest": { | |
"allowCredentials": [ | |
{ | |
"id": "nPr77L4_***", | |
"transports": [ | |
"usb" | |
], | |
"type": "public-key" | |
} | |
], | |
"challenge": "ZvNo***", | |
"rpId": "local.gaughan.ie", | |
"timeout": 60000, | |
"userVerification": "preferred" | |
} | |
}, | |
"client_mode": "webauthn", | |
"image": "static/img/FIDO-U2F-Security-Key-444x444.png", | |
"message": "Please confirm with your WebAuthn token (Yubico U2F EE Serial *********)", | |
"messages": [ | |
"Please confirm with your WebAuthn token (Yubico U2F EE Serial *********)" | |
], | |
"multi_challenge": [...], | |
"serial": "WAN0000C6D7", | |
"threadid": 140139447605376, | |
"transaction_id": "06514302233648181969", | |
"transaction_ids": [ | |
"06514302233648181969" | |
], | |
"type": "webauthn", | |
"preferred_client_mode": "webauthn" | |
}, | |
"id": 2, | |
"jsonrpc": "2.0", | |
"result": { | |
"authentication": "CHALLENGE", | |
"status": true, | |
"value": false | |
}, | |
"time": 1679603318.2812126, | |
"version": "privacyIDEA 3.8.1", | |
"versionnumber": "3.8.1", | |
"signature": "rsa_sha256_pss:***" | |
} |
This file contains hidden or 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
{ | |
"detail": { | |
"message": "Response did not match the challenge.", | |
"serial": "WAN0000C6D7", | |
"threadid": 140139447605376, | |
"type": "webauthn" | |
}, | |
"id": 2, | |
"jsonrpc": "2.0", | |
"result": { | |
"authentication": "REJECT", | |
"status": true, | |
"value": false | |
}, | |
"time": 1679603320.8933861, | |
"version": "privacyIDEA 3.8.1", | |
"versionnumber": "3.8.1", | |
"signature": "rsa_sha256_pss:***" | |
} |
This file contains hidden or 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
{ | |
"action": { | |
"webauthn_relying_party_id": "local.gaughan.ie", | |
"webauthn_relying_party_name": "Keith Gaughan", | |
}, | |
"active": True, | |
"name": "enrollment", | |
"priority": 1, | |
"realm": ["users"], | |
"resolver": ["LDAP"], | |
"scope": "enrollment", | |
} | |
{ | |
"action": { | |
"auditlog": True, | |
"delete": True, | |
"disable": True, | |
"enrollU2F": True, | |
"enrollWEBAUTHN": True, | |
"reset": True, | |
"revoke": True, | |
"setdescription": True, | |
}, | |
"active": True, | |
"name": "selfservice", | |
"priority": 1, | |
"realm": ["users"], | |
"resolver": ["LDAP"], | |
"scope": "user", | |
} | |
{ | |
"action": { | |
"u2f_facets": "pi.local.gaughan.ie auth.local.gaughan.ie", | |
"webauthn_allowed_transports": "usb", | |
}, | |
"active": True, | |
"name": "sso", | |
"priority": 1, | |
"scope": "authentication", | |
} |
This file contains hidden or 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
POST /validate/check HTTP/1.1 | |
Accept-Encoding: identity | |
Content-Type: application/x-www-form-urlencoded | |
Content-Length: 515 | |
Host: pi.local.gaughan.ie | |
User-Agent: Python-urllib/3.11 | |
Accept: application/json | |
Connection: close | |
user=keith.gaughan | |
pass= | |
transaction_id=06514302233648181969 | |
credentialid=nPr77L4_*** | |
clientdata=eyJ*** | |
signaturedata=MEYC*** | |
authenticatordata=biZW-*** |
This file contains hidden or 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
import json | |
import logging | |
import ssl | |
from urllib.request import Request, build_opener, HTTPSHandler | |
from urllib.parse import urlencode | |
from bottle import Bottle, request, run, static_file, template | |
PI_HOST = "pi.local.gaughan.ie" | |
USERNAME_FORM = r"""<!DOCTYPE html> | |
<html> | |
<head> | |
<title>PrivacyIDEA minimal</title> | |
</head> | |
<body> | |
<h1>PrivacyIDEA minimal client</h1> | |
<form action="/" method="POST"> | |
<label>Username:<br><input type="text" name="user"></label><br> | |
<input type="submit" value="Trigger check"> | |
</form> | |
</body> | |
</html> | |
""" | |
SIGN_FORM = r"""<!DOCTYPE html> | |
<html> | |
<head> | |
<title>PrivacyIDEA minimal</title> | |
<script type="text/javascript" src="/pi-webauthn.js"></script> | |
<script type="text/javascript" defer> | |
window.addEventListener('DOMContentLoaded', () => { | |
'use strict'; | |
pi_webauthn.sign({{! sign_req }}).then((response) => { | |
const frm = document.forms.validate; | |
for (const key in response) { | |
frm.elements[key].value = response[key]; | |
} | |
frm.submit(); | |
}).catch((error) => { | |
alert(error); | |
}); | |
}); | |
</script> | |
</head> | |
<body> | |
<form action="/validate" method="POST" name="validate"> | |
<input type="hidden" name="user" value="{{ user }}"> | |
<input type="hidden" name="transaction_id" value="{{ transaction_id }}"> | |
<label>Credential ID:<br><input type="text" name="credentialid" value=""></label><br> | |
<label>Client data:<br><input type="text" name="clientdata" value=""></label><br> | |
<label>Authenticator data:<br><input type="text" name="authenticatordata" value=""></label><br> | |
<label>Signature data:<br><input type="text" name="signaturedata" value=""></label><br> | |
<label>User handle:<br><input type="text" name="userhandle" value=""></label><br> | |
<label>Assertion client extensions:<br><input type="text" name="assertionclientextensions" value=""></label><br> | |
<input type="submit" value="Trigger check"> | |
</form> | |
</body> | |
</html> | |
""" | |
FINAL = r"""<!DOCTYPE html> | |
<html> | |
<head> | |
</head> | |
<body> | |
<pre>{{ result }}</pre> | |
</body> | |
</html> | |
""" | |
def send_check(args): | |
req = Request( | |
f"https://{PI_HOST}/validate/check", | |
data=urlencode(args).encode("ascii"), | |
headers={"Accept": "application/json"}, | |
) | |
opener = build_opener( | |
HTTPSHandler(debuglevel=1, context=ssl.create_default_context()), | |
) | |
response = opener.open(req) | |
return json.load(response) | |
def get_challenge(user): | |
return send_check({"user": user, "pass": ""}) | |
def validate_challenge( | |
user, | |
transaction_id, | |
credential_id, | |
client_data, | |
signature_data, | |
authenticator_data, | |
user_handle, | |
assertion_client_extensions, | |
): | |
req = { | |
"user": user, | |
"pass": "", | |
"transaction_id": transaction_id, | |
"credentialid": credential_id, | |
"clientdata": client_data, | |
"signaturedata": signature_data, | |
"authenticatordata": authenticator_data, | |
} | |
if user_handle != "": | |
req["userhandle"] = user_handle | |
if assertion_client_extensions != "": | |
req["assertionclientextensions"] = assertion_client_extensions | |
return send_check(req) | |
app = Bottle() | |
@app.get("/") | |
def index(): | |
return USERNAME_FORM | |
@app.post("/") | |
def do_sign(): | |
challenge_res = get_challenge(request.forms.get("user")) | |
detail = challenge_res["detail"] | |
print(challenge_res) | |
return template( | |
SIGN_FORM, | |
user=request.forms.get("user"), | |
transaction_id=detail["transaction_id"], | |
sign_req=json.dumps(detail["attributes"]["webAuthnSignRequest"]), | |
) | |
@app.post("/validate") | |
def do_validate(): | |
validation_res = validate_challenge( | |
user=request.forms.get("user"), | |
transaction_id=request.forms.get("transaction_id"), | |
credential_id=request.forms.get("credentialid"), | |
client_data=request.forms.get("clientdata"), | |
signature_data=request.forms.get("signaturedata"), | |
authenticator_data=request.forms.get("authenticatordata"), | |
user_handle=request.forms.get("userhandle"), | |
assertion_client_extensions=request.forms.get("assertionclientextensions"), | |
) | |
return template( | |
FINAL, | |
result=json.dumps(validation_res, indent=2), | |
) | |
@app.get("/pi-webauthn.js") | |
def js(): | |
return static_file("pi-webauthn.js", root="") | |
if __name__ == "__main__": | |
logging.basicConfig(format="%(asctime)s - %(message)s", level=logging.DEBUG) | |
run( | |
app=app, | |
server="cheroot", | |
host="127.0.0.1", | |
port=443, | |
certfile="cert.pem", | |
keyfile="key.pem", | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For anyone looking at this, it relates to this post on the PrivacyIDEA community forums. The root cause of the issues I was having is that PrivacyIDEA expects the Origin header to be sent in the API requests to
/validate/check
. Setting that to a domain under the relying party resolves the issue.