Skip to content

Commit

Permalink
Move stuff around.
Browse files Browse the repository at this point in the history
- Rename fido2/pyu2f -> fido2/_pyu2f
- Partial docstrings, with type information.
- Rewrote server example.
  • Loading branch information
dainnilsson committed Jul 3, 2018
1 parent 7b2b5ba commit 1edb0d9
Show file tree
Hide file tree
Showing 35 changed files with 1,307 additions and 293 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
build/
dist/
.eggs/
.idea/
.ropeproject/
ChangeLog
man/*.1
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ repos:
hooks:
- id: flake8
- id: double-quote-string-fixer
exclude: '^(fido2|test)/pyu2f/.*'
exclude: '^(fido2|test)/_pyu2f/.*'
2 changes: 1 addition & 1 deletion README.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
== fido2
== python-fido2
image:https://travis-ci.org/Yubico/python-fido2.svg?branch=master["Travis CI Status", link="https://travis-ci.org/Yubico/python-fido2"]
image:https://ci.appveyor.com/api/projects/status/8orx9nbdfq52w47s/branch/master?svg=true["Appveyor Status", link="https://ci.appveyor.com/project/Yubico53275/python-fido-host/branch/master"]

Expand Down
255 changes: 79 additions & 176 deletions examples/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,193 +38,96 @@
from __future__ import print_function, absolute_import, unicode_literals

from fido2.client import ClientData
from fido2.server import Fido2Server
from fido2.ctap2 import AttestationObject, AuthenticatorData
from flask import Flask, request
from fido2 import cbor
from flask import Flask, session, request, redirect, abort

import os


HTML = """
<html>
<head><title>Fido 2.0 webauthn demo</title></head>
<body>
<h1>Webauthn demo</h1>
<p>
<strong>This demo requires a browser supporting the WebAuthn API!</strong>
</p>
<hr>
{content}
</body>
</html>
"""
app = Flask(__name__, static_url_path='')
app.secret_key = os.urandom(32) # Used for session.

server = Fido2Server('localhost')
rp = {
'id': 'localhost',
'name': 'Demo server'
}

INDEX_HTML = HTML.format(content="""
<a href="/register">Register</a><br>
<a href="/authenticate">Authenticate</a><br>
""")

REGISTER_HTML = HTML.format(content="""
<h2>Register a credential</h2>
<p>Touch your authenticator device now...</p>
<script>
navigator.credentials.create({{
publicKey: {{
rp: {{
id: document.domain,
name: 'Demo server'
}},
user: {{
id: {user_id},
name: 'a_user',
displayName: 'A. User',
icon: 'https://example.com/image.png'
}},
challenge: {challenge},
pubKeyCredParams: [
{{
alg: -7,
type: 'public-key'
}}
],
excludeCredentials: [],
attestation: 'direct',
timeout: 60000
}}
}}).then(function(attestation) {{
console.log(attestation);
console.log(JSON.stringify({{
attestationObject: Array.from(new Uint8Array(attestation.response.attestationObject)),
clientData: Array.from(new Uint8Array(attestation.response.clientDataJSON))
}}));
fetch('/register', {{
method: 'POST',
body: JSON.stringify({{
attestationObject: Array.from(new Uint8Array(attestation.response.attestationObject)),
clientData: Array.from(new Uint8Array(attestation.response.clientDataJSON))
}})
}}).then(function() {{
alert('Registration successful. More details in server log...');
window.location = '/';
}});
}}, function(reason) {{
console.log('Failed', reason);
}});
</script>
""") # noqa


AUTH_HTML = HTML.format(content="""
<h2>Authenticate using a credential</h2>
<p>Touch your authenticator device now...</p>
<script>
navigator.credentials.get({{
publicKey: {{
rpId: document.domain,
challenge: {challenge},
allowCredentials: [
{{
type: 'public-key',
id: {credential_id}
}}
],
timeout: 60000
}}
}}).then(function(attestation) {{
console.log(attestation);
fetch('/authenticate', {{
method: 'POST',
body: JSON.stringify({{
authenticatorData: Array.from(new Uint8Array(attestation.response.authenticatorData)),
clientData: Array.from(new Uint8Array(attestation.response.clientDataJSON)),
signature: Array.from(new Uint8Array(attestation.response.signature))
}})
}}).then(function() {{
alert('Authentication successful. More details in server log...');
window.location = '/';
}});
}}, function(reason) {{
console.log('Failed', reason);
}});
</script>
""") # noqa


def to_js_array(value):
return 'new Uint8Array(%r)' % list(bytearray(value))


def from_js_array(value):
return bytes(bytearray(value))


app = Flask(__name__)

global credential, last_challenge
credential, last_challenge = None, None

# Registered credentials are stored globally, in memory only. Single user
# support, state is lost when the server terminates.
credentials = []


@app.route('/')
def index():
return INDEX_HTML


@app.route('/register', methods=['GET', 'POST'])
def register():
global credential, last_challenge
if request.method == 'POST':
data = request.get_json(force=True)
client_data = ClientData(from_js_array(data['clientData']))
att_obj = AttestationObject(from_js_array(data['attestationObject']))
print('clientData', client_data)
print('AttestationObject:', att_obj)

# Verify the challenge
if client_data.challenge != last_challenge:
raise ValueError('Challenge mismatch!')

# Verify the signature
att_obj.verify(client_data.hash)
credential = att_obj.auth_data.credential_data
print('REGISTERED CREDENTIAL:', credential)
return 'OK'

last_challenge = os.urandom(32)
return REGISTER_HTML.format(
user_id=to_js_array(b'user_id'),
challenge=to_js_array(last_challenge)
)


@app.route('/authenticate', methods=['GET', 'POST'])
def authenticate():
global credential, last_challenge
if not credential:
return HTML.format(content='No credential registered!')

if request.method == 'POST':
data = request.get_json(force=True)
client_data = ClientData(from_js_array(data['clientData']))
auth_data = AuthenticatorData(from_js_array(data['authenticatorData']))
signature = from_js_array(data['signature'])
print('clientData', client_data)
print('AuthenticatorData', auth_data)

# Verify the challenge
if client_data.challenge != last_challenge:
raise ValueError('Challenge mismatch!')

# Verify the signature
credential.public_key.verify(auth_data + client_data.hash, signature)
print('ASSERTION OK')
return 'OK'

last_challenge = os.urandom(32)
return AUTH_HTML.format(
challenge=to_js_array(last_challenge),
credential_id=to_js_array(credential.credential_id)
)
return redirect('/index.html')


@app.route('/api/register/begin', methods=['POST'])
def register_begin():
registration_data = server.register_begin(rp, {
'id': b'user_id',
'name': 'a_user',
'displayName': 'A. User',
'icon': 'https://example.com/image.png'
}, credentials)

session['challenge'] = registration_data['publicKey']['challenge']
print('\n\n\n\n')
print(registration_data)
print('\n\n\n\n')
return cbor.dumps(registration_data)


@app.route('/api/register/complete', methods=['POST'])
def register_complete():
data = cbor.loads(request.get_data())[0]
client_data = ClientData(data['clientDataJSON'])
att_obj = AttestationObject(data['attestationObject'])
print('clientData', client_data)
print('AttestationObject:', att_obj)

auth_data = server.register_complete(
session['challenge'], client_data, att_obj)

credentials.append(auth_data.credential_data)
print('REGISTERED CREDENTIAL:', auth_data.credential_data)
return cbor.dumps({'status': 'OK'})


@app.route('/api/authenticate/begin', methods=['POST'])
def authenticate_begin():
if not credentials:
abort(404)

auth_data = server.authenticate_begin(rp['id'], credentials)
session['challenge'] = auth_data['publicKey']['challenge']
return cbor.dumps(auth_data)


@app.route('/api/authenticate/complete', methods=['POST'])
def authenticate_complete():
if not credentials:
abort(404)

data = cbor.loads(request.get_data())[0]
credential_id = data['credentialId']
client_data = ClientData(data['clientDataJSON'])
auth_data = AuthenticatorData(data['authenticatorData'])
signature = data['signature']
print('clientData', client_data)
print('AuthenticatorData', auth_data)

server.authenticate_complete(credentials, credential_id,
session.pop('challenge'), client_data,
auth_data, signature)
print('ASSERTION OK')
return cbor.dumps({'status': 'OK'})


if __name__ == '__main__':
print(__doc__)

app.run(ssl_context='adhoc', debug=True)
47 changes: 47 additions & 0 deletions examples/server/static/authenticate.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<html>
<head>
<title>Fido 2.0 webauthn demo</title>
<script src="/cbor.js"></script>
</head>
<body>
<h1>Webauthn demo</h1>
<p>
<strong>This demo requires a browser supporting the WebAuthn API!</strong>
</p>
<hr>

<h2>Authenticate using a credential</h2>
<p>Touch your authenticator device now...</p>
<script>
fetch('/api/authenticate/begin', {
method: 'POST',
}).then(function(response) {
return response.arrayBuffer();
}).then(function(data) {
return CBOR.decode(data);
}).then(function(options) {
navigator.credentials.get(options).then(function(assertion) {
console.log(assertion);
fetch('/api/authenticate/complete', {
method: 'POST',
headers: {'Content-Type': 'application/cbor'},
body: CBOR.encode({
"credentialId": new Uint8Array(assertion.rawId),
"authenticatorData": new Uint8Array(assertion.response.authenticatorData),
"clientDataJSON": new Uint8Array(assertion.response.clientDataJSON),
"signature": new Uint8Array(assertion.response.signature)
})
}).then(function() {
alert('Authentication successful. More details in server log...');
window.location = '/index.html';
});
}, function(reason) {
console.log('Failed', reason);
alert(reason);
window.location = '/index.html';
});
});
</script>

</body>
</html>
Loading

0 comments on commit 1edb0d9

Please sign in to comment.