fido2-0.9.1/ 0000755 0001750 0001750 00000000000 14006463366 012404 5 ustar dain dain 0000000 0000000 fido2-0.9.1/COPYING 0000644 0001750 0001750 00000002430 13275566137 013445 0 ustar dain dain 0000000 0000000 Copyright (c) 2018 Yubico AB All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. fido2-0.9.1/COPYING.APLv2 0000644 0001750 0001750 00000026136 13275566136 014340 0 ustar dain dain 0000000 0000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. fido2-0.9.1/COPYING.MPLv2 0000644 0001750 0001750 00000040526 13275566136 014353 0 ustar dain dain 0000000 0000000 Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. fido2-0.9.1/examples/ 0000755 0001750 0001750 00000000000 14006463366 014222 5 ustar dain dain 0000000 0000000 fido2-0.9.1/examples/acr122u.py 0000644 0001750 0001750 00000004464 14002014354 015744 0 ustar dain dain 0000000 0000000 from fido2.pcsc import CtapPcscDevice import time class Acr122uPcscDevice(object): def __init__(self, pcsc_device): self.pcsc = pcsc_device def reader_version(self): """ Get reader's version from reader :return: string. Reader's version """ try: result, sw1, sw2 = self.pcsc.apdu_exchange(b"\xff\x00\x48\x00\x00") if len(result) > 0: str_result = result + bytes([sw1]) + bytes([sw2]) str_result = str_result.decode("utf-8") return str_result except Exception as e: print("Get version error:", e) return "n/a" def led_control( self, red=False, green=False, blink_count=0, red_end_blink=False, green_end_blink=False, ): """ Reader's led control :param red: boolean. red led on :param green: boolean. green let on :param blink_count: int. if needs to blink value > 0. blinks count :param red_end_blink: boolean. state of red led at the end of blinking :param green_end_blink: boolean. state of green led at the end of blinking :return: """ try: if blink_count > 0: cbyte = ( 0b00001100 + (0b01 if red_end_blink else 0b00) + (0b10 if green_end_blink else 0b00) ) cbyte |= (0b01000000 if red else 0b00000000) + ( 0b10000000 if green else 0b00000000 ) else: cbyte = 0b00001100 + (0b01 if red else 0b00) + (0b10 if green else 0b00) apdu = ( b"\xff\x00\x40" + bytes([cbyte & 0xFF]) + b"\4" + b"\5\3" + bytes([blink_count]) + b"\0" ) self.pcsc.apdu_exchange(apdu) except Exception as e: print("LED control error:", e) dev = next(CtapPcscDevice.list_devices()) print("CONNECT: %s" % dev) pcsc_device = Acr122uPcscDevice(dev) pcsc_device.led_control(False, True, 0) print("version: %s" % pcsc_device.reader_version()) pcsc_device.led_control(True, False, 0) time.sleep(1) pcsc_device.led_control(False, True, 3) fido2-0.9.1/examples/acr122usam.py 0000644 0001750 0001750 00000024644 14002014354 016447 0 ustar dain dain 0000000 0000000 # Copyright (c) 2019 Yubico AB # Copyright (c) 2019 Oleg Moiseenko # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Sample work with reader: ACR-122U-SAM or touchatag drivers and manual link: www.acs.com.hk/en/driver/100/acr122u-nfc-reader-with-sam-slot-proprietary/ """ import time import six from fido2.utils import sha256 from fido2.ctap1 import CTAP1 from smartcard.Exceptions import CardConnectionException from fido2.pcsc import CtapPcscDevice class Acr122uSamPcscDevice(CtapPcscDevice): def __init__(self, connection, name): self.ats = b"" self.vparity = False self.max_block_len = 29 try: super().__init__(connection, name) except (CardConnectionException, ValueError): pass except Exception as e: print(e.__class__) # setup reader if not self.set_auto_iso14443_4_activation(): raise Exception("Set automatic iso-14443-4 activation error") if not self.set_default_retry_timeout(): raise Exception("Set default retry timeout error") self.ats = self.get_ats() if self.ats == b"": raise Exception("No card in field") self._select() def apdu_plain(self, apdu, protocol=None): """Exchange data with reader. :param apdu: byte string. data to exchange with card :param protocol: protocol to exchange with card. usually set by default :return: byte string. response from card """ # print('>> %s' % b2a_hex(apdu)) resp, sw1, sw2 = self._conn.transmit(list(six.iterbytes(apdu)), protocol) response = bytes(bytearray(resp)) # print('<< [0x%04x] %s' % (sw1 * 0x100 + sw2, b2a_hex(response))) return response, sw1, sw2 def pseudo_apdu_ex(self, apdu, protocol=None): req = b"\xff\x00\x00\x00" + bytes([len(apdu) & 0xFF]) + apdu resp, sw1, sw2 = self.apdu_plain(req, protocol) if sw1 != 0x61: return resp, sw1, sw2 return self.apdu_plain(b"\xff\xc0\x00\x00" + bytes([sw2]), protocol) # override base method # commands in PN 532 User manual (UM0701-02) # page 178. 7.4.5 DEP chaining mechanism # page 136. 7.3.9 InCommunicateThru # chaining ISO 14443-4:2001 # page 20. 7.5.2 Chaining def apdu_exchange(self, apdu, protocol=None): all_response = b"" alen = 0 while True: vapdu = apdu[alen : alen + self.max_block_len] # input chaining chaining = alen + len(vapdu) < len(apdu) vb = 0x02 | (0x01 if self.vparity else 0x00) | (0x10 if chaining else 0x00) # 7.3.9 InCommunicateThru resp, sw1, sw2 = self.pseudo_apdu_ex( b"\xd4\x42" + bytes([vb]) + vapdu, protocol ) self.vparity = not self.vparity if len(resp) > 2 and resp[2] > 0: print("Error: 0x%02x" % resp[2]) return b"", 0x6F, resp[2] if sw1 != 0x90 or len(resp) < 3 or resp[0] != 0xD5 or resp[1] != 0x43: return b"", 0x67, 0x00 alen += len(vapdu) if not chaining: break if len(resp) > 3: if resp[3] & 0x10 == 0: return resp[4:-2], resp[-2], resp[-1] else: if resp[3] != 0xF2: all_response = resp[4:] else: return b"", 0x90, 0x00 while True: if len(resp) > 3 and resp[3] == 0xF2: # WTX answer = resp[3:5] else: # ACK answer = bytes([0xA2 | (0x01 if self.vparity else 0x00)]) self.vparity = not self.vparity # 7.3.9 InCommunicateThru resp, sw1, sw2 = self.pseudo_apdu_ex(b"\xd4\x42" + answer, protocol) if len(resp) > 2 and resp[2] > 0: print("Error: 0x%02x" % resp[2]) return b"", 0x6F, resp[2] if sw1 != 0x90 or len(resp) < 3 or resp[0] != 0xD5 or resp[1] != 0x43: return b"", 0x67, 0x00 response_chaining = len(resp) > 3 and resp[3] & 0x10 != 0 # if I block if len(resp) > 3 and resp[3] & 0xE0 == 0x00: all_response += resp[4:] if not response_chaining: break return all_response[:-2], resp[-2], resp[-1] def get_ats(self, verbose=False): self.field_reset() self.ats = b"" resp, sw1, sw2 = self.pseudo_apdu_ex(b"\xd4\x4a\x01\x00") if sw1 == 0x90 and len(resp) > 8 and resp[2] > 0x00: if verbose: print("ATQA 0x%02x%02x" % (resp[4], resp[5])) print("SAK 0x%02x" % resp[6]) uid_len = resp[7] if verbose: print("UID [%d] %s" % (uid_len, resp[8 : 8 + uid_len].hex())) self.ats = resp[8 + uid_len :] if verbose: print("ATS [%d] %s" % (len(self.ats), self.ats.hex())) self.vparity = False return self.ats return b"" def set_default_retry_timeout(self): result, sw1, sw2 = self.pseudo_apdu_ex(b"\xd4\x32\x05\x00\x00\x00") if sw1 != 0x90 or sw2 != 0x00 or result != b"\xd5\x33": print("set default retry time error") return False # 14443 timeout. UM0701-02 PN432 user manual. page 101. # RFU, fATR_RES_Timeout, fRetryTimeout # 0b 102ms, 0c - 204ms, 0d - 409ms, 0f - 1.6s result, sw1, sw2 = self.pseudo_apdu_ex(b"\xd4\x32\x02\x00\x0c\x0f") if sw1 != 0x90 or sw2 != 0x00 or result != b"\xd5\x33": print("set fRetryTimeout error") return False return True def set_auto_iso14443_4_activation(self, activate=True): result, sw1, sw2 = self.pseudo_apdu_ex( b"\xd4\x12" + bytes([0x34 if activate else 0x24]) ) if sw1 != 0x90 or sw2 != 0x00 or result != b"\xd5\x13": print("set automatic iso-14443-4 activation error") return False return True def field_control(self, field_on=True): result, sw1, sw2 = self.pseudo_apdu_ex( b"\xd4\x32\x01" + bytes([0x01 if field_on else 0x00]) ) if sw1 != 0x90 or sw2 != 0x00 or result != b"\xd5\x33": print("set field state error") return False return True def field_reset(self): self.led_control(True, False) result = self.field_control(False) time.sleep(0.2) result |= self.field_control(True) self.led_control() return result def reader_version(self): """ Get reader's version from reader :return: string. Reader's version """ try: result, sw1, sw2 = self.apdu_plain(b"\xff\x00\x48\x00\x00") if len(result) > 0: str_result = result + bytes([sw1]) + bytes([sw2]) str_result = str_result.decode("utf-8") return str_result except Exception as e: print("Get version error:", e) return "n/a" def led_control( self, red=False, green=False, blink_count=0, red_end_blink=False, green_end_blink=False, ): """ Reader's led control :param red: boolean. red led on :param green: boolean. green let on :param blink_count: int. if needs to blink value > 0. blinks count :param red_end_blink: boolean. state of red led at the end of blinking :param green_end_blink: boolean. state of green led at the end of blinking :return: """ try: if blink_count > 0: cbyte = ( 0b00001100 + (0b01 if red_end_blink else 0b00) + (0b10 if green_end_blink else 0b00) ) cbyte |= (0b01000000 if red else 0b00000000) + ( 0b10000000 if green else 0b00000000 ) else: cbyte = 0b00001100 + (0b01 if red else 0b00) + (0b10 if green else 0b00) apdu = ( b"\xff\x00\x40" + bytes([cbyte & 0xFF]) + b"\4" + b"\5\3" + bytes([blink_count]) + b"\0" ) self.apdu_plain(apdu) except Exception as e: print("LED control error:", e) dev = next(Acr122uSamPcscDevice.list_devices()) print("CONNECT: %s" % dev) print("version: %s" % dev.reader_version()) print("atr: %s" % bytes(dev.get_atr()).hex()) print("ats: %s" % dev.ats.hex()) # uncomment if you want to see parameters from card's selection # dev.get_ats(True) # dev._select() dev.led_control(False, True, 0) chal = sha256(b"AAA") appid = sha256(b"BBB") ctap1 = CTAP1(dev) print("ctap1 version:", ctap1.get_version()) reg = ctap1.register(chal, appid) print("u2f register:", reg) reg.verify(appid, chal) print("Register message verify OK") auth = ctap1.authenticate(chal, appid, reg.key_handle) print("u2f authenticate: ", auth) res = auth.verify(appid, chal, reg.public_key) print("Authenticate message verify OK") dev.led_control() fido2-0.9.1/examples/acr1252u.py 0000644 0001750 0001750 00000013045 14002014354 016024 0 ustar dain dain 0000000 0000000 from fido2.pcsc import CtapPcscDevice import time # control codes: # 3225264 - magic number!!! # 0x42000000 + 3500 - cross platform way C_CODE = 3225264 class Acr1252uPcscDevice(object): def __init__(self, pcsc_device): self.pcsc = pcsc_device def reader_version(self): try: res = self.pcsc.control_exchange(C_CODE, b"\xe0\x00\x00\x18\x00") if len(res) > 0 and res.find(b"\xe1\x00\x00\x00") == 0: reslen = res[4] if reslen == len(res) - 5: strres = res[5 : 5 + reslen].decode("utf-8") return strres except Exception as e: print("Get version error:", e) return "n/a" def reader_serial_number(self): try: res = self.pcsc.control_exchange(C_CODE, b"\xe0\x00\x00\x33\x00") if len(res) > 0 and res.find(b"\xe1\x00\x00\x00") == 0: reslen = res[4] if reslen == len(res) - 5: strres = res[5 : 5 + reslen].decode("utf-8") return strres except Exception as e: print("Get serial number error:", e) return "n/a" def led_control(self, red=False, green=False): try: cbyte = (0b01 if red else 0b00) + (0b10 if green else 0b00) result = self.pcsc.control_exchange( C_CODE, b"\xe0\x00\x00\x29\x01" + bytes([cbyte]) ) if len(result) > 0 and result.find(b"\xe1\x00\x00\x00") == 0: result_length = result[4] if result_length == 1: ex_red = bool(result[5] & 0b01) ex_green = bool(result[5] & 0b10) return True, ex_red, ex_green except Exception as e: print("LED control error:", e) return False, False, False def led_status(self): try: result = self.pcsc.control_exchange(C_CODE, b"\xe0\x00\x00\x29\x00") if len(result) > 0 and result.find(b"\xe1\x00\x00\x00") == 0: result_length = result[4] if result_length == 1: ex_red = bool(result[5] & 0b01) ex_green = bool(result[5] & 0b10) return True, ex_red, ex_green except Exception as e: print("LED status error:", e) return False, False, False def get_polling_settings(self): try: res = self.pcsc.control_exchange(C_CODE, b"\xe0\x00\x00\x23\x00") if len(res) > 0 and res.find(b"\xe1\x00\x00\x00") == 0: reslen = res[4] if reslen == 1: return True, res[5] except Exception as e: print("Get polling settings error:", e) return False, 0 def set_polling_settings(self, settings): try: res = self.pcsc.control_exchange( C_CODE, b"\xe0\x00\x00\x23\x01" + bytes([settings & 0xFF]) ) if len(res) > 0 and res.find(b"\xe1\x00\x00\x00") == 0: reslen = res[4] if reslen == 1: return True, res[5] except Exception as e: print("Set polling settings error:", e) return False, 0 def get_picc_operation_parameter(self): try: res = self.pcsc.control_exchange(C_CODE, b"\xe0\x00\x00\x20\x00") if len(res) > 0 and res.find(b"\xe1\x00\x00\x00") == 0: reslen = res[4] if reslen == 1: return True, res[5] except Exception as e: print("Get PICC Operating Parameter error:", e) return False, 0 def set_picc_operation_parameter(self, param): try: res = self.pcsc.control_exchange( C_CODE, b"\xe0\x00\x00\x20\x01" + bytes([param]) ) if len(res) > 0 and res.find(b"\xe1\x00\x00\x00") == 0: reslen = res[4] if reslen == 1: return True, res[5] except Exception as e: print("Set PICC Operating Parameter error:", e) return False, 0 dev = next(CtapPcscDevice.list_devices()) print("CONNECT: %s" % dev) pcsc_device = Acr1252uPcscDevice(dev) if pcsc_device is not None: print("version: %s" % pcsc_device.reader_version()) print("serial number: %s" % pcsc_device.reader_serial_number()) print("") result, settings = pcsc_device.set_polling_settings(0x8B) print("write polling settings: %r 0x%x" % (result, settings)) result, settings = pcsc_device.get_polling_settings() print("polling settings: %r 0x%x" % (result, settings)) set_desc = [ [0, "Auto PICC Polling"], [1, "Turn off Antenna Field if no PICC is found"], [2, "Turn off Antenna Field if the PICC is inactive"], [3, "Activate the PICC when detected"], [7, "Enforce ISO 14443-A Part 4"], ] for x in set_desc: print(x[1], "on" if settings & (1 << x[0]) else "off") interval_desc = [250, 500, 1000, 2500] print("PICC Poll Interval for PICC", interval_desc[(settings >> 4) & 0b11], "ms") print("") print( "PICC operation parameter: %r 0x%x" % pcsc_device.get_picc_operation_parameter() ) print("") result, red, green = pcsc_device.led_control(True, False) print("led control result:", result, "red:", red, "green:", green) result, red, green = pcsc_device.led_status() print("led state result:", result, "red:", red, "green:", green) time.sleep(1) pcsc_device.led_control(False, False) fido2-0.9.1/examples/bio_enrollment.py 0000644 0001750 0001750 00000005557 14006451303 017604 0 ustar dain dain 0000000 0000000 # Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found over USB, and attempts to enroll a new fingerprint. This requires that a PIN is already set. NOTE: This uses a draft bio enrollment specification which is not yet final. Consider this highly experimental. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice from fido2.ctap2 import Ctap2, ClientPin, FPBioEnrollment, CaptureError from getpass import getpass import sys pin = None uv = "discouraged" for dev in CtapHidDevice.list_devices(): try: ctap = Ctap2(dev) if "bioEnroll" in ctap.info.options: break except Exception: # nosec continue else: print("No Authenticator supporting bioEnroll found") sys.exit(1) if not ctap.info.options.get("clientPin"): print("PIN not set for the device!") sys.exit(1) # Authenticate with PIN print("Preparing to enroll a new fingerprint.") pin = getpass("Please enter PIN: ") client_pin = ClientPin(ctap) pin_token = client_pin.get_pin_token(pin, ClientPin.PERMISSION.BIO_ENROLL) bio = FPBioEnrollment(ctap, client_pin.protocol, pin_token) print(bio.enumerate_enrollments()) # Start enrollment enroller = bio.enroll() template_id = None while template_id is None: print("Press your fingerprint against the sensor now...") try: template_id = enroller.capture() print(enroller.remaining, "more scans needed.") except CaptureError as e: print(e) print("Fingerprint registered successfully with ID:", template_id) fido2-0.9.1/examples/credential.py 0000644 0001750 0001750 00000011006 14002014354 016665 0 ustar dain dain 0000000 0000000 # Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found (starts from USB, then looks into NFC), creates a new credential for it, and authenticates the credential. This works with both FIDO 2.0 devices as well as with U2F devices. On Windows, the native WebAuthn API will be used. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice from fido2.client import Fido2Client, WindowsClient from fido2.server import Fido2Server from getpass import getpass import sys import ctypes use_prompt = False pin = None uv = "discouraged" if WindowsClient.is_available() and not ctypes.windll.shell32.IsUserAnAdmin(): # Use the Windows WebAuthn API if available, and we're not running as admin client = WindowsClient("https://example.com") else: # Locate a device dev = next(CtapHidDevice.list_devices(), None) if dev is not None: print("Use USB HID channel.") use_prompt = True else: try: from fido2.pcsc import CtapPcscDevice dev = next(CtapPcscDevice.list_devices(), None) print("Use NFC channel.") except Exception as e: print("NFC channel search error:", e) if not dev: print("No FIDO device found") sys.exit(1) # Set up a FIDO 2 client using the origin https://example.com client = Fido2Client(dev, "https://example.com") # Prefer UV if supported if client.info.options.get("uv"): uv = "preferred" print("Authenticator supports User Verification") elif client.info.options.get("clientPin"): # Prompt for PIN if needed pin = getpass("Please enter PIN: ") else: print("PIN not set, won't use") server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="direct") user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, user_verification=uv, authenticator_attachment="cross-platform" ) # Create a credential if use_prompt: print("\nTouch your authenticator device now...\n") result = client.make_credential(create_options["publicKey"], pin=pin) # Complete registration auth_data = server.register_complete( state, result.client_data, result.attestation_object ) credentials = [auth_data.credential_data] print("New credential created!") print("CLIENT DATA:", result.client_data) print("ATTESTATION OBJECT:", result.attestation_object) print() print("CREDENTIAL DATA:", auth_data.credential_data) # Prepare parameters for getAssertion request_options, state = server.authenticate_begin(credentials, user_verification=uv) # Authenticate the credential if use_prompt: print("\nTouch your authenticator device now...\n") # Only one cred in allowCredentials, only one response. result = client.get_assertion(request_options["publicKey"], pin=pin).get_response(0) # Complete authenticator server.authenticate_complete( state, credentials, result.credential_id, result.client_data, result.authenticator_data, result.signature, ) print("Credential authenticated!") print("CLIENT DATA:", result.client_data) print() print("AUTH DATA:", result.authenticator_data) fido2-0.9.1/examples/cred_blob.py 0000644 0001750 0001750 00000010031 14002014354 016463 0 ustar dain dain 0000000 0000000 # Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found which supports the CredBlob extension, creates a new credential for it with the extension enabled, and stores some data. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice from fido2.client import Fido2Client from fido2.server import Fido2Server from getpass import getpass import sys import os try: from fido2.pcsc import CtapPcscDevice except ImportError: CtapPcscDevice = None def enumerate_devices(): for dev in CtapHidDevice.list_devices(): yield dev if CtapPcscDevice: for dev in CtapPcscDevice.list_devices(): yield dev # Locate a device for dev in enumerate_devices(): client = Fido2Client(dev, "https://example.com") if "credBlob" in client.info.extensions: break else: print("No Authenticator with the CredBlob extension found!") sys.exit(1) use_nfc = CtapPcscDevice and isinstance(dev, CtapPcscDevice) # Prepare parameters for makeCredential server = Fido2Server({"id": "example.com", "name": "Example RP"}) user = {"id": b"user_id", "name": "A. User"} create_options, state = server.register_begin(user, resident_key=True) # Add CredBlob extension, attach data blob = os.urandom(32) # 32 random bytes create_options["publicKey"]["extensions"] = {"credBlob": blob} # Prompt for PIN if needed pin = None if client.info.options.get("clientPin"): pin = getpass("Please enter PIN:") else: print("no pin") # Create a credential if not use_nfc: print("\nTouch your authenticator device now...\n") result = client.make_credential(create_options["publicKey"], pin=pin) # Complete registration auth_data = server.register_complete( state, result.client_data, result.attestation_object ) credentials = [auth_data.credential_data] # CredBlob result: if not auth_data.extensions.get("credBlob"): print("Credential was registered, but credBlob was NOT saved.") sys.exit(1) print("New credential created, with the CredBlob extension.") # Prepare parameters for getAssertion request_options, state = server.authenticate_begin() request_options["publicKey"]["extensions"] = { "getCredBlob": True, } # Authenticate the credential if not use_nfc: print("\nTouch your authenticator device now...\n") # Only one cred in allowCredentials, only one response. result = client.get_assertion(request_options["publicKey"], pin=pin).get_response(0) blob_res = result.authenticator_data.extensions.get("credBlob") if blob == blob_res: print("Authenticated, got correct blob:", blob.hex()) else: print( "Authenticated, got incorrect blob! (was %s, expected %s)" % (blob_res.hex(), blob.hex()) ) sys.exit(1) fido2-0.9.1/examples/get_info.py 0000644 0001750 0001750 00000004651 14006451303 016360 0 ustar dain dain 0000000 0000000 # Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to each attached FIDO device, and: 1. If the device supports CBOR commands, perform a getInfo command. 2. If the device supports WINK, perform the wink command. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice, CAPABILITY from fido2.ctap2 import CTAP2 try: from fido2.pcsc import CtapPcscDevice except ImportError: CtapPcscDevice = None def enumerate_devices(): for dev in CtapHidDevice.list_devices(): yield dev if CtapPcscDevice: for dev in CtapPcscDevice.list_devices(): yield dev for dev in enumerate_devices(): print("CONNECT: %s" % dev) print("CTAPHID protocol version: %d" % dev.version) if dev.capabilities & CAPABILITY.CBOR: ctap2 = CTAP2(dev) info = ctap2.get_info() print("DEVICE INFO: %s" % info) else: print("Device does not support CBOR") if dev.capabilities & CAPABILITY.WINK: dev.wink() print("WINK sent!") else: print("Device does not support WINK") dev.close() fido2-0.9.1/examples/hmac_secret.py 0000644 0001750 0001750 00000011447 14002014354 017041 0 ustar dain dain 0000000 0000000 # Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found which supports the HmacSecret extension, creates a new credential for it with the extension enabled, and uses it to derive two separate secrets. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice from fido2.client import Fido2Client from getpass import getpass from binascii import b2a_hex import sys import os try: from fido2.pcsc import CtapPcscDevice except ImportError: CtapPcscDevice = None def enumerate_devices(): for dev in CtapHidDevice.list_devices(): yield dev if CtapPcscDevice: for dev in CtapPcscDevice.list_devices(): yield dev # Locate a device for dev in enumerate_devices(): client = Fido2Client(dev, "https://example.com") if "hmac-secret" in client.info.extensions: break else: print("No Authenticator with the HmacSecret extension found!") sys.exit(1) use_nfc = CtapPcscDevice and isinstance(dev, CtapPcscDevice) # Prepare parameters for makeCredential rp = {"id": "example.com", "name": "Example RP"} user = {"id": b"user_id", "name": "A. User"} challenge = b"Y2hhbGxlbmdl" # Prompt for PIN if needed pin = None if client.info.options.get("clientPin"): pin = getpass("Please enter PIN:") else: print("no pin") # Create a credential with a HmacSecret if not use_nfc: print("\nTouch your authenticator device now...\n") result = client.make_credential( { "rp": rp, "user": user, "challenge": challenge, "pubKeyCredParams": [{"type": "public-key", "alg": -7}], "extensions": {"hmacCreateSecret": True}, }, pin=pin, ) # HmacSecret result: if not result.extension_results.get("hmacCreateSecret"): print("Failed to create credential with HmacSecret") sys.exit(1) credential = result.attestation_object.auth_data.credential_data print("New credential created, with the HmacSecret extension.") # Prepare parameters for getAssertion challenge = b"Q0hBTExFTkdF" # Use a new challenge for each call. allow_list = [{"type": "public-key", "id": credential.credential_id}] # Generate a salt for HmacSecret: salt = os.urandom(32) print("Authenticate with salt:", b2a_hex(salt)) # Authenticate the credential if not use_nfc: print("\nTouch your authenticator device now...\n") result = client.get_assertion( { "rpId": rp["id"], "challenge": challenge, "allowCredentials": allow_list, "extensions": {"hmacGetSecret": {"salt1": salt}}, }, pin=pin, ).get_response( 0 ) # Only one cred in allowList, only one response. output1 = result.extension_results["hmacGetSecret"]["output1"] print("Authenticated, secret:", b2a_hex(output1)) # Authenticate again, using two salts to generate two secrets: # Generate a second salt for HmacSecret: salt2 = os.urandom(32) print("Authenticate with second salt:", b2a_hex(salt2)) if not use_nfc: print("\nTouch your authenticator device now...\n") # The first salt is reused, which should result in the same secret. result = client.get_assertion( { "rpId": rp["id"], "challenge": challenge, "allowCredentials": allow_list, "extensions": {"hmacGetSecret": {"salt1": salt, "salt2": salt2}}, }, pin=pin, ).get_response( 0 ) # One cred in allowCredentials, single response. output = result.extension_results["hmacGetSecret"] print("Old secret:", b2a_hex(output["output1"])) print("New secret:", b2a_hex(output["output2"])) fido2-0.9.1/examples/large_blobs.py 0000644 0001750 0001750 00000012000 14006451303 017024 0 ustar dain dain 0000000 0000000 # Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found (starts from USB, then looks into NFC), creates a new credential for it, and authenticates the credential. This works with both FIDO 2.0 devices as well as with U2F devices. On Windows, the native WebAuthn API will be used. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice from fido2.ctap2 import ClientPin, LargeBlobs from fido2.client import Fido2Client from fido2.server import Fido2Server from getpass import getpass import sys try: from fido2.pcsc import CtapPcscDevice except ImportError: CtapPcscDevice = None def enumerate_devices(): for dev in CtapHidDevice.list_devices(): yield dev if CtapPcscDevice: for dev in CtapPcscDevice.list_devices(): yield dev # Locate a device for dev in enumerate_devices(): client = Fido2Client(dev, "https://example.com") if "largeBlobKey" in client.info.extensions: break else: print("No Authenticator with the largeBlobKey extension found!") sys.exit(1) use_nfc = CtapPcscDevice and isinstance(dev, CtapPcscDevice) pin = None uv = "discouraged" if not client.info.options.get("largeBlobs"): print("Authenticator does not support large blobs!") sys.exit(1) # Prefer UV token if supported if client.info.options.get("pinUvAuthToken") and client.info.options.get("uv"): uv = "preferred" print("Authenticator supports UV token") elif client.info.options.get("clientPin"): # Prompt for PIN if needed pin = getpass("Please enter PIN: ") else: print("PIN not set, won't use") server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="direct") user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, resident_key=True, user_verification=uv, authenticator_attachment="cross-platform", ) # Enable largeBlobKey options = create_options["publicKey"] options.extensions = {"largeBlobKey": True} # Create a credential print("\nTouch your authenticator device now...\n") result = client.make_credential(options, pin=pin) key = result.attestation_object.large_blob_key # Complete registration auth_data = server.register_complete( state, result.client_data, result.attestation_object ) credentials = [auth_data.credential_data] print("New credential created!") print("Large Blob Key:", key) client_pin = ClientPin(client.ctap2) if pin: token = client_pin.get_pin_token(pin, ClientPin.PERMISSION.LARGE_BLOB_WRITE) else: token = client_pin.get_uv_token(ClientPin.PERMISSION.LARGE_BLOB_WRITE) large_blobs = LargeBlobs(client.ctap2, client_pin.protocol, token) # Write a large blob print("Writing a large blob...") large_blobs.put_blob(key, b"Here is some data to store!") # Prepare parameters for getAssertion request_options, state = server.authenticate_begin(user_verification=uv) # Enable largeBlobKey options = request_options["publicKey"] options.extensions = {"largeBlobKey": True} # Authenticate the credential print("\nTouch your authenticator device now...\n") selection = client.get_assertion(options, pin=pin) # Only one cred in allowCredentials, only one response. assertion = selection.get_assertions()[0] # This should match the key from MakeCredential. key = assertion.large_blob_key # Get a fresh PIN token if pin: token = client_pin.get_pin_token(pin, ClientPin.PERMISSION.LARGE_BLOB_WRITE) else: token = client_pin.get_uv_token(ClientPin.PERMISSION.LARGE_BLOB_WRITE) large_blobs = LargeBlobs(client.ctap2, client_pin.protocol, token) blob = large_blobs.get_blob(key) print("Read blob", blob) # Clean up large_blobs.delete_blob(key) fido2-0.9.1/examples/min_pin_length.py 0000644 0001750 0001750 00000006403 13746236541 017574 0 ustar dain dain 0000000 0000000 # Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found which supports the CredBlob extension, creates a new credential for it with the extension enabled, and stores some data. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice from fido2.client import Fido2Client from fido2.server import Fido2Server from fido2.extensions import MinPinLengthExtension from getpass import getpass import sys try: from fido2.pcsc import CtapPcscDevice except ImportError: CtapPcscDevice = None def enumerate_devices(): for dev in CtapHidDevice.list_devices(): yield dev if CtapPcscDevice: for dev in CtapPcscDevice.list_devices(): yield dev # Locate a device for dev in enumerate_devices(): client = Fido2Client(dev, "https://example.com") if MinPinLengthExtension.NAME in client.info.extensions: break else: print("No Authenticator with the MinPinLength extension found!") sys.exit(1) use_nfc = CtapPcscDevice and isinstance(dev, CtapPcscDevice) # Prepare parameters for makeCredential server = Fido2Server({"id": "example.com", "name": "Example RP"}) user = {"id": b"user_id", "name": "A. User"} create_options, state = server.register_begin(user) # Add MinPinLength extension, attach data min_pin = MinPinLengthExtension() create_options["publicKey"]["extensions"] = min_pin.create_dict() # Prompt for PIN if needed pin = None if client.info.options.get("clientPin"): pin = getpass("Please enter PIN:") else: print("no pin") # Create a credential if not use_nfc: print("\nTouch your authenticator device now...\n") attestation_object, client_data = client.make_credential( create_options["publicKey"], pin=pin ) # Complete registration auth_data = server.register_complete(state, client_data, attestation_object) min_pin_length = min_pin.results_for(auth_data) print("Credential registered, min PIN length: ", min_pin_length) fido2-0.9.1/examples/multi_device.py 0000644 0001750 0001750 00000006453 14002014354 017236 0 ustar dain dain 0000000 0000000 # Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to each FIDO device found, and causes them all to blink until the user triggers one to select it. A new credential is created for that authenticator, and the operation is cancelled for the others. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice, STATUS from fido2.client import Fido2Client, ClientError from threading import Event, Thread import sys # Locate a device devs = list(CtapHidDevice.list_devices()) if not devs: print("No FIDO device found") sys.exit(1) clients = [Fido2Client(d, "https://example.com") for d in devs] # Prepare parameters for makeCredential rp = {"id": "example.com", "name": "Example RP"} user = {"id": b"user_id", "name": "A. User"} challenge = b"Y2hhbGxlbmdl" cancel = Event() attestation, client_data = None, None has_prompted = False def on_keepalive(status): global has_prompted # Don't prompt for each device. if status == STATUS.UPNEEDED and not has_prompted: print("\nTouch your authenticator device now...\n") has_prompted = True def work(client): global attestation, client_data try: attestation, client_data = client.make_credential( { "rp": rp, "user": user, "challenge": challenge, "pubKeyCredParams": [{"type": "public-key", "alg": -7}], }, event=cancel, on_keepalive=on_keepalive, ) except ClientError as e: if e.code != ClientError.ERR.TIMEOUT: raise else: return cancel.set() print("New credential created!") print("ATTESTATION OBJECT:", attestation) print() print("CREDENTIAL DATA:", attestation.auth_data.credential_data) threads = [] for client in clients: t = Thread(target=work, args=(client,)) threads.append(t) t.start() for t in threads: t.join() if not cancel.is_set(): print("Operation timed out!") fido2-0.9.1/examples/resident_key.py 0000644 0001750 0001750 00000011170 14002014354 017242 0 ustar dain dain 0000000 0000000 # Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found (starts from USB, then looks into NFC), creates a new credential for it, and authenticates the credential. This works with both FIDO 2.0 devices as well as with U2F devices. On Windows, the native WebAuthn API will be used. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice from fido2.client import Fido2Client, WindowsClient from fido2.server import Fido2Server from getpass import getpass import sys import ctypes try: from fido2.pcsc import CtapPcscDevice except ImportError: CtapPcscDevice = None def enumerate_devices(): for dev in CtapHidDevice.list_devices(): yield dev if CtapPcscDevice: for dev in CtapPcscDevice.list_devices(): yield dev use_prompt = False pin = None uv = "discouraged" if WindowsClient.is_available() and not ctypes.windll.shell32.IsUserAnAdmin(): # Use the Windows WebAuthn API if available, and we're not running as admin client = WindowsClient("https://example.com") else: # Locate a device for dev in enumerate_devices(): client = Fido2Client(dev, "https://example.com") if client.info.options.get("rk"): use_prompt = not (CtapPcscDevice and isinstance(dev, CtapPcscDevice)) break else: print("No Authenticator with support for resident key found!") sys.exit(1) # Prefer UV if supported if client.info.options.get("uv"): uv = "preferred" print("Authenticator supports User Verification") elif client.info.options.get("clientPin"): # Prompt for PIN if needed pin = getpass("Please enter PIN: ") else: print("PIN not set, won't use") server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="direct") user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, resident_key=True, user_verification=uv, authenticator_attachment="cross-platform", ) # Create a credential if use_prompt: print("\nTouch your authenticator device now...\n") result = client.make_credential(create_options["publicKey"], pin=pin) # Complete registration auth_data = server.register_complete( state, result.client_data, result.attestation_object ) credentials = [auth_data.credential_data] print("New credential created!") print("CLIENT DATA:", result.client_data) print("ATTESTATION OBJECT:", result.attestation_object) print() print("CREDENTIAL DATA:", auth_data.credential_data) # Prepare parameters for getAssertion request_options, state = server.authenticate_begin(user_verification=uv) # Authenticate the credential if use_prompt: print("\nTouch your authenticator device now...\n") selection = client.get_assertion(request_options["publicKey"], pin=pin) result = selection.get_response(0) # There may be multiple responses, get the first. print("USER ID:", result.user_handle) # Complete authenticator server.authenticate_complete( state, credentials, result.credential_id, result.client_data, result.authenticator_data, result.signature, ) print("Credential authenticated!") print("CLIENT DATA:", result.client_data) print() print("AUTHENTICATOR DATA:", result.authenticator_data) fido2-0.9.1/examples/server/ 0000755 0001750 0001750 00000000000 14006463366 015530 5 ustar dain dain 0000000 0000000 fido2-0.9.1/examples/server/Pipfile 0000644 0001750 0001750 00000000356 14006451303 017033 0 ustar dain dain 0000000 0000000 [[source]] verify_ssl = true name = "pypi" url = "https://pypi.org/simple" [packages] flask = "*" pyOpenSSL = "*" "5448283" = {editable = true, path = "./../.."} [scripts] server = "python server.py" server-u2f = "python server-u2f.py" fido2-0.9.1/examples/server/Pipfile.lock 0000644 0001750 0001750 00000026261 14006451303 017765 0 ustar dain dain 0000000 0000000 { "_meta": { "hash": { "sha256": "bf2fa9b63243b7172b84ae28c7fc3abc291ed1bb91f4919f2b2c47bfc0ccdd4d" }, "pipfile-spec": 6, "requires": {}, "sources": [ { "name": "pypi", "url": "https://pypi.org/simple", "verify_ssl": true } ] }, "default": { "5448283": { "editable": true, "path": "./../.." }, "cffi": { "hashes": [ "sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d", "sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b", "sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4", "sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f", "sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3", "sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579", "sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537", "sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e", "sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05", "sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171", "sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca", "sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522", "sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c", "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc", "sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d", "sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808", "sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828", "sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869", "sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d", "sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9", "sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0", "sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc", "sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15", "sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c", "sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a", "sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3", "sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1", "sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768", "sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d", "sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b", "sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e", "sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d", "sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730", "sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394", "sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1", "sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591" ], "version": "==1.14.3" }, "click": { "hashes": [ "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" ], "version": "==7.1.2" }, "cryptography": { "hashes": [ "sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538", "sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f", "sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77", "sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b", "sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33", "sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e", "sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb", "sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e", "sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7", "sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297", "sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d", "sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7", "sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b", "sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7", "sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4", "sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8", "sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b", "sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851", "sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13", "sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b", "sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3", "sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df" ], "version": "==3.2.1" }, "flask": { "hashes": [ "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" ], "index": "pypi", "version": "==1.1.2" }, "itsdangerous": { "hashes": [ "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" ], "version": "==1.1.0" }, "jinja2": { "hashes": [ "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" ], "version": "==2.11.2" }, "markupsafe": { "hashes": [ "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" ], "version": "==1.1.1" }, "pycparser": { "hashes": [ "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" ], "version": "==2.20" }, "pyopenssl": { "hashes": [ "sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504", "sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507" ], "index": "pypi", "version": "==19.1.0" }, "six": { "hashes": [ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], "version": "==1.15.0" }, "werkzeug": { "hashes": [ "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" ], "version": "==1.0.1" } }, "develop": {} } fido2-0.9.1/examples/server/README.adoc 0000644 0001750 0001750 00000004571 14006451303 017310 0 ustar dain dain 0000000 0000000 == WebAuthn Server Example This example shows a minimal website that uses python-fido2 to implement WebAuthn credential registration, and use. === Running To run this sample, you will need `pipenv`. For instructions on installing `pipenv`, see https://docs.pipenv.org. Run the following command in the `examples/server` directory to set up the example: $ pipenv install Once the environment has been created, you can run the server by running: $ pipenv run server When the server is running, use a browser supporting WebAuthn and open https://localhost:5000 to access the website. NOTE: As this server uses a self-signed certificate, you will get warnings in your browser about the connection not being secure. This is expected, and you can safely proceed to the site. === Using the website The site allows you to register a WebAuthn credential, and to authenticate it. Credentials are only stored in memory, and stopping the server will cause it to "forget" any registered credentials. ==== Registration 1. Click on the `Register` link to begin credential registration. 2. If not already inserted, insert your U2F/FIDO2 Authenticator now. 3. Touch the button to activate the Authenticator. 4. A popup will indicate whether the registration was successful. Click `OK`. ==== Authentication NOTE: You must register a credential prior to authentication. 1. Click on the `Authenticate` link to begin authentication. 2. If not already inserted, insert your U2F/FIDO2 Authenticator now. 3. Touch the button to activate the Authenticator. 4. A popup will indicate whether the authentication was successful. Click `OK`. === Supporting existing U2F credentials If you have existing U2F credentials that you wish to support, this library offers a U2FFido2Server class which can help with this. This directory includes a slightly altered version of the example server which uses this class to authenticate U2F credentials as well as WebAuthn credentials. To run this version of the server, run: $ pipenv run server-u2f This version allows registration both using the newer WebAuthn APIs and by using the legacy U2F APIs, so that you can test authentication using both credential types. The source code for this version of the server is in `server-u2f.py`. NOTE: There should be no need to support registration of new U2F credentials as new registrations should be using the WebAuthn APIs, even for existing users. fido2-0.9.1/examples/server/server-u2f.py 0000644 0001750 0001750 00000014051 14006451303 020067 0 ustar dain dain 0000000 0000000 # Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Example demo server to use a supported web browser to call the WebAuthn APIs to register and use a credential. See the file README.adoc in this directory for details. Navigate to https://localhost:5000 in a supported web browser. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.webauthn import PublicKeyCredentialRpEntity from fido2.client import ClientData from fido2.server import U2FFido2Server from fido2.ctap2 import AttestationObject, AuthenticatorData from fido2.ctap1 import RegistrationData from fido2.utils import sha256, websafe_encode from fido2 import cbor from flask import Flask, session, request, redirect, abort import os app = Flask(__name__, static_url_path="") app.secret_key = os.urandom(32) # Used for session. rp = PublicKeyCredentialRpEntity("localhost", "Demo server") # By using the U2FFido2Server class, we can support existing credentials # registered by the legacy u2f.register API for an appId. server = U2FFido2Server("https://localhost:5000", rp) # Registered credentials are stored globally, in memory only. Single user # support, state is lost when the server terminates. credentials = [] @app.route("/") def index(): return redirect("/index-u2f.html") @app.route("/api/register/begin", methods=["POST"]) def register_begin(): registration_data, state = server.register_begin( { "id": b"user_id", "name": "a_user", "displayName": "A. User", "icon": "https://example.com/image.png", }, credentials, ) session["state"] = state print("\n\n\n\n") print(registration_data) print("\n\n\n\n") return cbor.encode(registration_data) @app.route("/api/register/complete", methods=["POST"]) def register_complete(): data = cbor.decode(request.get_data()) client_data = ClientData(data["clientDataJSON"]) att_obj = AttestationObject(data["attestationObject"]) print("clientData", client_data) print("AttestationObject:", att_obj) auth_data = server.register_complete(session["state"], client_data, att_obj) credentials.append(auth_data.credential_data) print("REGISTERED CREDENTIAL:", auth_data.credential_data) return cbor.encode({"status": "OK"}) @app.route("/api/authenticate/begin", methods=["POST"]) def authenticate_begin(): if not credentials: abort(404) auth_data, state = server.authenticate_begin(credentials) session["state"] = state return cbor.encode(auth_data) @app.route("/api/authenticate/complete", methods=["POST"]) def authenticate_complete(): if not credentials: abort(404) data = cbor.decode(request.get_data()) 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( session.pop("state"), credentials, credential_id, client_data, auth_data, signature, ) print("ASSERTION OK") return cbor.encode({"status": "OK"}) ############################################################################### # WARNING! # # The below functions allow the registration of legacy U2F credentials. # This is provided FOR TESTING PURPOSES ONLY. New credentials should be # registered using the WebAuthn APIs. ############################################################################### @app.route("/api/u2f/begin", methods=["POST"]) def u2f_begin(): registration_data, state = server.register_begin( { "id": b"user_id", "name": "a_user", "displayName": "A. User", "icon": "https://example.com/image.png", }, credentials, ) session["state"] = state print("\n\n\n\n") print(registration_data) print("\n\n\n\n") return cbor.encode(websafe_encode(registration_data["publicKey"]["challenge"])) @app.route("/api/u2f/complete", methods=["POST"]) def u2f_complete(): data = cbor.decode(request.get_data()) client_data = ClientData.from_b64(data["clientData"]) reg_data = RegistrationData.from_b64(data["registrationData"]) print("clientData", client_data) print("U2F RegistrationData:", reg_data) att_obj = AttestationObject.from_ctap1(sha256(b"https://localhost:5000"), reg_data) print("AttestationObject:", att_obj) auth_data = att_obj.auth_data credentials.append(auth_data.credential_data) print("REGISTERED U2F CREDENTIAL:", auth_data.credential_data) return cbor.encode({"status": "OK"}) if __name__ == "__main__": print(__doc__) app.run(ssl_context="adhoc", debug=False) fido2-0.9.1/examples/server/server.py 0000644 0001750 0001750 00000010643 14006451303 017400 0 ustar dain dain 0000000 0000000 # Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Example demo server to use a supported web browser to call the WebAuthn APIs to register and use a credential. See the file README.adoc in this directory for details. Navigate to https://localhost:5000 in a supported web browser. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.webauthn import PublicKeyCredentialRpEntity from fido2.client import ClientData from fido2.server import Fido2Server from fido2.ctap2 import AttestationObject, AuthenticatorData from fido2 import cbor from flask import Flask, session, request, redirect, abort import os app = Flask(__name__, static_url_path="") app.secret_key = os.urandom(32) # Used for session. rp = PublicKeyCredentialRpEntity("localhost", "Demo server") server = Fido2Server(rp) # Registered credentials are stored globally, in memory only. Single user # support, state is lost when the server terminates. credentials = [] @app.route("/") def index(): return redirect("/index.html") @app.route("/api/register/begin", methods=["POST"]) def register_begin(): registration_data, state = server.register_begin( { "id": b"user_id", "name": "a_user", "displayName": "A. User", "icon": "https://example.com/image.png", }, credentials, user_verification="discouraged", authenticator_attachment="cross-platform", ) session["state"] = state print("\n\n\n\n") print(registration_data) print("\n\n\n\n") return cbor.encode(registration_data) @app.route("/api/register/complete", methods=["POST"]) def register_complete(): data = cbor.decode(request.get_data()) client_data = ClientData(data["clientDataJSON"]) att_obj = AttestationObject(data["attestationObject"]) print("clientData", client_data) print("AttestationObject:", att_obj) auth_data = server.register_complete(session["state"], client_data, att_obj) credentials.append(auth_data.credential_data) print("REGISTERED CREDENTIAL:", auth_data.credential_data) return cbor.encode({"status": "OK"}) @app.route("/api/authenticate/begin", methods=["POST"]) def authenticate_begin(): if not credentials: abort(404) auth_data, state = server.authenticate_begin(credentials) session["state"] = state return cbor.encode(auth_data) @app.route("/api/authenticate/complete", methods=["POST"]) def authenticate_complete(): if not credentials: abort(404) data = cbor.decode(request.get_data()) 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( session.pop("state"), credentials, credential_id, client_data, auth_data, signature, ) print("ASSERTION OK") return cbor.encode({"status": "OK"}) if __name__ == "__main__": print(__doc__) app.run(ssl_context="adhoc", debug=False) fido2-0.9.1/examples/server/static/ 0000755 0001750 0001750 00000000000 14006463366 017017 5 ustar dain dain 0000000 0000000 fido2-0.9.1/examples/server/static/authenticate.html 0000644 0001750 0001750 00000003262 14006451303 022352 0 ustar dain dain 0000000 0000000
This demo requires a browser supporting the WebAuthn API!
Touch your authenticator device now...
Cancel fido2-0.9.1/examples/server/static/cbor.js 0000644 0001750 0001750 00000027617 14006451303 020303 0 ustar dain dain 0000000 0000000 /* * The MIT License (MIT) * * Copyright (c) 2014-2016 Patrick GanstererThis demo requires a browser supporting the WebAuthn API!
To allow the testing of authenticating with legacy U2F credentials, you can also register a U2F credential: Register U2F
fido2-0.9.1/examples/server/static/index.html 0000644 0001750 0001750 00000001134 14006451303 020777 0 ustar dain dain 0000000 0000000This demo requires a browser supporting the WebAuthn API!
This demo requires a browser supporting the WebAuthn API!
Touch your authenticator device now...
Cancel fido2-0.9.1/examples/server/static/u2f-api.js 0000644 0001750 0001750 00000050620 14006451303 020607 0 ustar dain dain 0000000 0000000 //Copyright 2014-2015 Google Inc. All rights reserved. //Use of this source code is governed by a BSD-style //license that can be found in the LICENSE file or at //https://developers.google.com/open-source/licenses/bsd /** * @fileoverview The U2F api. */ 'use strict'; /** * Namespace for the U2F api. * @type {Object} */ var u2f = u2f || {}; /** * FIDO U2F Javascript API Version * @number */ var js_api_version; /** * The U2F extension id * @const {string} */ // The Chrome packaged app extension ID. // Uncomment this if you want to deploy a server instance that uses // the package Chrome app and does not require installing the U2F Chrome extension. u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; // The U2F Chrome extension ID. // Uncomment this if you want to deploy a server instance that uses // the U2F Chrome extension to authenticate. // u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; /** * Message types for messsages to/from the extension * @const * @enum {string} */ u2f.MessageTypes = { 'U2F_REGISTER_REQUEST': 'u2f_register_request', 'U2F_REGISTER_RESPONSE': 'u2f_register_response', 'U2F_SIGN_REQUEST': 'u2f_sign_request', 'U2F_SIGN_RESPONSE': 'u2f_sign_response', 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request', 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response' }; /** * Response status codes * @const * @enum {number} */ u2f.ErrorCodes = { 'OK': 0, 'OTHER_ERROR': 1, 'BAD_REQUEST': 2, 'CONFIGURATION_UNSUPPORTED': 3, 'DEVICE_INELIGIBLE': 4, 'TIMEOUT': 5 }; /** * A message for registration requests * @typedef {{ * type: u2f.MessageTypes, * appId: ?string, * timeoutSeconds: ?number, * requestId: ?number * }} */ u2f.U2fRequest; /** * A message for registration responses * @typedef {{ * type: u2f.MessageTypes, * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), * requestId: ?number * }} */ u2f.U2fResponse; /** * An error object for responses * @typedef {{ * errorCode: u2f.ErrorCodes, * errorMessage: ?string * }} */ u2f.Error; /** * Data object for a single sign request. * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}} */ u2f.Transport; /** * Data object for a single sign request. * @typedef {ArrayThis demo requires a browser supporting the WebAuthn API!
Touch your authenticator device now...
Cancel fido2-0.9.1/examples/test_config.py 0000644 0001750 0001750 00000005047 14005771254 017103 0 ustar dain dain 0000000 0000000 # Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found which supports Authenticator Config. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice from fido2.ctap2 import Ctap2 from fido2.ctap2.pin import ClientPin, PERMISSIONS from fido2.ctap2.config import Config from getpass import getpass import sys try: from fido2.pcsc import CtapPcscDevice except ImportError: CtapPcscDevice = None def enumerate_devices(): for dev in CtapHidDevice.list_devices(): yield dev if CtapPcscDevice: for dev in CtapPcscDevice.list_devices(): yield dev # Locate a device for dev in enumerate_devices(): ctap = Ctap2(dev) if ctap.info.options.get("authnrCfg"): break else: print("No Authenticator with the MinPinLength extension found!") sys.exit(1) use_nfc = CtapPcscDevice and isinstance(dev, CtapPcscDevice) if not ctap.info.options.get("clientPin"): print("PIN must be configured") sys.exit(1) pin = getpass("Please enter PIN:") client_pin = ClientPin(ctap) token = client_pin.get_pin_token(pin, PERMISSIONS.AUTHENTICATOR_CFG) config = Config(ctap, client_pin.protocol, token) config.set_min_pin_length(8) fido2-0.9.1/examples/u2f_nfc.py 0000644 0001750 0001750 00000001742 14002014354 016103 0 ustar dain dain 0000000 0000000 from fido2.pcsc import CtapPcscDevice from fido2.utils import sha256 from fido2.ctap1 import CTAP1 import sys dev = next(CtapPcscDevice.list_devices(), None) if not dev: print("No NFC u2f device found") sys.exit(1) chal = sha256(b"AAA") appid = sha256(b"BBB") ctap1 = CTAP1(dev) print("version:", ctap1.get_version()) # True - make extended APDU and send it to key # ISO 7816-3:2006. page 33, 12.1.3 Decoding conventions for command APDUs # ISO 7816-3:2006. page 34, 12.2 Command-response pair transmission by T=0 # False - make group of short (less than 255 bytes length) APDU # and send them to key. ISO 7816-3:2005, page 9, 5.1.1.1 Command chaining dev.use_ext_apdu = False reg = ctap1.register(chal, appid) print("register:", reg) reg.verify(appid, chal) print("Register message verify OK") auth = ctap1.authenticate(chal, appid, reg.key_handle) print("authenticate result: ", auth) res = auth.verify(appid, chal, reg.public_key) print("Authenticate message verify OK") fido2-0.9.1/examples/verify_attestation.py 0000644 0001750 0001750 00000013151 14002014354 020501 0 ustar dain dain 0000000 0000000 # Copyright (c) 2021 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ This example shows how to use an AttestationVerifier to only allow credentials signed by a specific CA. It connects to the first FIDO device found (starts from USB, then looks into NFC), creates a new credential for it, and verifies that attestation is signed by the Yubico FIDO root CA (this will only work for Yubico devices). On Windows, the native WebAuthn API will be used. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice from fido2.client import Fido2Client, WindowsClient from fido2.server import Fido2Server, AttestationVerifier from base64 import b64decode from getpass import getpass from binascii import b2a_hex import sys import ctypes # Official Yubico root CA for FIDO Authenticators YUBICO_CA = b64decode( """ MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290 IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk 5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep 8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT 9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4 MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw== """ ) class YubicoAttestationVerifier(AttestationVerifier): """Example implementation of an AttestationVerifier. This simple example will attempt to verify all trust paths using the Yubico CA. A real implementation can use the information in the attestation result, or the authenticator data, to determine which CA should be used to verify the path. """ def ca_lookup(self, result, auth_data): return [YUBICO_CA] use_prompt = False pin = None uv = "discouraged" if WindowsClient.is_available() and not ctypes.windll.shell32.IsUserAnAdmin(): # Use the Windows WebAuthn API if available, and we're not running as admin client = WindowsClient("https://example.com") else: # Locate a device dev = next(CtapHidDevice.list_devices(), None) if dev is not None: print("Use USB HID channel.") use_prompt = True else: try: from fido2.pcsc import CtapPcscDevice dev = next(CtapPcscDevice.list_devices(), None) print("Use NFC channel.") except Exception as e: print("NFC channel search error:", e) if not dev: print("No FIDO device found") sys.exit(1) # Set up a FIDO 2 client using the origin https://example.com client = Fido2Client(dev, "https://example.com") # Prefer UV if supported if client.info.options.get("uv"): uv = "preferred" print("Authenticator supports User Verification") elif client.info.options.get("clientPin"): # Prompt for PIN if needed pin = getpass("Please enter PIN: ") else: print("PIN not set, won't use") server = Fido2Server( {"id": "example.com", "name": "Example RP"}, attestation="direct", verify_attestation=YubicoAttestationVerifier(), ) user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, user_verification=uv, authenticator_attachment="cross-platform" ) # Create a credential if use_prompt: print("\nTouch your authenticator device now...\n") result = client.make_credential(create_options["publicKey"], pin=pin) # Complete registration auth_data = server.register_complete( state, result.client_data, result.attestation_object ) credentials = [auth_data.credential_data] print("New credential created, attestation verified!") print("Yubico device AAGUID:", b2a_hex(auth_data.credential_data.aaguid)) fido2-0.9.1/fido2/ 0000755 0001750 0001750 00000000000 14006463366 013407 5 ustar dain dain 0000000 0000000 fido2-0.9.1/fido2/attestation/ 0000755 0001750 0001750 00000000000 14006463366 015746 5 ustar dain dain 0000000 0000000 fido2-0.9.1/fido2/attestation/android.py 0000644 0001750 0001750 00000006043 14006451303 017727 0 ustar dain dain 0000000 0000000 # Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals from .base import ( Attestation, AttestationType, AttestationResult, InvalidData, catch_builtins, ) from ..cose import CoseKey from ..utils import sha256, websafe_decode from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.constant_time import bytes_eq import json class AndroidSafetynetAttestation(Attestation): FORMAT = "android-safetynet" def __init__(self, allow_rooted=False): self.allow_rooted = allow_rooted @catch_builtins def verify(self, statement, auth_data, client_data_hash): jwt = statement["response"] header, payload, sig = (websafe_decode(x) for x in jwt.split(b".")) data = json.loads(payload.decode("utf8")) if not self.allow_rooted and data["ctsProfileMatch"] is not True: raise InvalidData("ctsProfileMatch must be true!") expected_nonce = sha256(auth_data + client_data_hash) if not bytes_eq(expected_nonce, websafe_decode(data["nonce"])): raise InvalidData("Nonce does not match!") data = json.loads(header.decode("utf8")) x5c = [websafe_decode(x) for x in data["x5c"]] cert = x509.load_der_x509_certificate(x5c[0], default_backend()) cn = cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME) if cn[0].value != "attest.android.com": raise InvalidData("Certificate not issued to attest.android.com!") CoseKey.for_name(data["alg"]).from_cryptography_key(cert.public_key()).verify( jwt.rsplit(b".", 1)[0], sig ) return AttestationResult(AttestationType.BASIC, x5c) fido2-0.9.1/fido2/attestation/apple.py 0000644 0001750 0001750 00000004565 14006451303 017417 0 ustar dain dain 0000000 0000000 # Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals from .base import ( Attestation, AttestationType, AttestationResult, InvalidData, catch_builtins, ) from ..utils import sha256 from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.constant_time import bytes_eq OID_APPLE = x509.ObjectIdentifier("1.2.840.113635.100.8.2") class AppleAttestation(Attestation): FORMAT = "apple" @catch_builtins def verify(self, statement, auth_data, client_data_hash): x5c = statement["x5c"] expected_nonce = sha256(auth_data + client_data_hash) cert = x509.load_der_x509_certificate(x5c[0], default_backend()) ext = cert.extensions.get_extension_for_oid(OID_APPLE) ext_nonce = ext.value.value[6:] # Sequence of single element of octet string if not bytes_eq(expected_nonce, ext_nonce): raise InvalidData("Nonce does not match!") return AttestationResult(AttestationType.ANON_CA, x5c) fido2-0.9.1/fido2/attestation/base.py 0000644 0001750 0001750 00000012032 14006451303 017214 0 ustar dain dain 0000000 0000000 # Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from enum import Enum, auto from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import padding, ec, rsa from cryptography.exceptions import InvalidSignature as _InvalidSignature from collections import namedtuple from functools import wraps import abc class InvalidAttestation(Exception): pass class InvalidData(InvalidAttestation): pass class InvalidSignature(InvalidAttestation): pass class UntrustedAttestation(InvalidAttestation): pass class UnsupportedType(InvalidAttestation): def __init__(self, auth_data, fmt=None): super(UnsupportedType, self).__init__( 'Attestation format "{}" is not supported'.format(fmt) if fmt else "This attestation format is not supported!" ) self.auth_data = auth_data self.fmt = fmt AttestationResult = namedtuple("AttestationResult", ["attestation_type", "trust_path"]) class AttestationType(Enum): BASIC = auto() SELF = auto() ATT_CA = auto() ANON_CA = auto() NONE = auto def catch_builtins(f): @wraps(f) def inner(*args, **kwargs): try: return f(*args, **kwargs) except (ValueError, KeyError, IndexError) as e: raise InvalidData(e) return inner @catch_builtins def verify_x509_chain(chain): certs = [x509.load_der_x509_certificate(der, default_backend()) for der in chain] cert = certs.pop(0) while certs: child = cert cert = certs.pop(0) pub = cert.public_key() try: if isinstance(pub, rsa.RSAPublicKey): pub.verify( child.signature, child.tbs_certificate_bytes, padding.PKCS1v15(), child.signature_hash_algorithm, ) elif isinstance(pub, ec.EllipticCurvePublicKey): pub.verify( child.signature, child.tbs_certificate_bytes, ec.ECDSA(child.signature_hash_algorithm), ) except _InvalidSignature: raise InvalidSignature() class Attestation(abc.ABC): @abc.abstractmethod def verify(self, statement, auth_data, client_data_hash): """Verifies attestation statement. :return: An AttestationResult if successful. """ @staticmethod def for_type(fmt): for cls in Attestation.__subclasses__(): if getattr(cls, "FORMAT", None) == fmt: return cls class TypedUnsupportedAttestation(UnsupportedAttestation): def __init__(self): super(TypedUnsupportedAttestation, self).__init__(fmt) return TypedUnsupportedAttestation class UnsupportedAttestation(Attestation): def __init__(self, fmt=None): self.fmt = fmt def verify(self, statement, auth_data, client_data_hash): raise UnsupportedType(auth_data, self.fmt) class NoneAttestation(Attestation): FORMAT = "none" def verify(self, statement, auth_data, client_data_hash): if statement != {}: raise InvalidData("None Attestation requires empty statement.") return AttestationResult(AttestationType.NONE, []) def _validate_cert_common(cert): if cert.version != x509.Version.v3: raise InvalidData("Attestation certificate must use version 3!") try: bc = cert.extensions.get_extension_for_class(x509.BasicConstraints) if bc.value.ca: raise InvalidData("Attestation certificate must have CA=false!") except x509.ExtensionNotFound: raise InvalidData("Attestation certificate must have Basic Constraints!") fido2-0.9.1/fido2/attestation/packed.py 0000644 0001750 0001750 00000010311 14006451303 017527 0 ustar dain dain 0000000 0000000 # Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals from .base import ( Attestation, AttestationType, AttestationResult, InvalidData, InvalidSignature, catch_builtins, _validate_cert_common, ) from ..cose import CoseKey from cryptography import x509 from cryptography.exceptions import InvalidSignature as _InvalidSignature from cryptography.hazmat.backends import default_backend OID_AAGUID = x509.ObjectIdentifier("1.3.6.1.4.1.45724.1.1.4") def _validate_packed_cert(cert, aaguid): # https://www.w3.org/TR/webauthn/#packed-attestation-cert-requirements _validate_cert_common(cert) c = cert.subject.get_attributes_for_oid(x509.NameOID.COUNTRY_NAME) if not c: raise InvalidData("Subject must have C set!") o = cert.subject.get_attributes_for_oid(x509.NameOID.ORGANIZATION_NAME) if not o: raise InvalidData("Subject must have O set!") ous = cert.subject.get_attributes_for_oid(x509.NameOID.ORGANIZATIONAL_UNIT_NAME) if not ous: raise InvalidData('Subject must have OU = "Authenticator Attestation"!') ou = ous[0] if ou.value != "Authenticator Attestation": raise InvalidData('Subject must have OU = "Authenticator Attestation"!') cn = cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME) if not cn: raise InvalidData("Subject must have CN set!") try: ext = cert.extensions.get_extension_for_oid(OID_AAGUID) if ext.critical: raise InvalidData("AAGUID extension must not be marked as critical") ext_aaguid = ext.value.value[2:] if ext_aaguid != aaguid: raise InvalidData( "AAGUID in Authenticator data does not " "match attestation certificate!" ) except x509.ExtensionNotFound: pass # If missing, ignore class PackedAttestation(Attestation): FORMAT = "packed" @catch_builtins def verify(self, statement, auth_data, client_data_hash): if "ecdaaKeyId" in statement: raise NotImplementedError("ECDAA not implemented") alg = statement["alg"] x5c = statement.get("x5c") if x5c: cert = x509.load_der_x509_certificate(x5c[0], default_backend()) _validate_packed_cert(cert, auth_data.credential_data.aaguid) pub_key = CoseKey.for_alg(alg).from_cryptography_key(cert.public_key()) att_type = AttestationType.BASIC else: pub_key = CoseKey.parse(auth_data.credential_data.public_key) if pub_key.ALGORITHM != alg: raise InvalidData("Wrong algorithm of public key!") att_type = AttestationType.SELF try: pub_key.verify(auth_data + client_data_hash, statement["sig"]) return AttestationResult(att_type, x5c or []) except _InvalidSignature: raise InvalidSignature() fido2-0.9.1/fido2/attestation/tpm.py 0000644 0001750 0001750 00000051636 14006451303 017117 0 ustar dain dain 0000000 0000000 # -*- coding: utf-8 -*- # Copyright (c) 2019 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals from .base import ( Attestation, AttestationType, AttestationResult, InvalidData, InvalidSignature, catch_builtins, _validate_cert_common, ) from ..cose import CoseKey from ..utils import bytes2int, ByteBuffer from enum import IntEnum from collections import namedtuple from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa, ec from cryptography.hazmat.primitives import hashes from cryptography import x509 from cryptography.exceptions import InvalidSignature as _InvalidSignature import struct import six if six.PY2: # Workaround for int max size on Python 2. from enum import Enum class _LongEnum(long, Enum): # noqa F821 """Like IntEnum, but supports larger values""" IntEnum = _LongEnum # Use instead of IntEnum # noqa F811 TPM_ALG_NULL = 0x0010 OID_AIK_CERTIFICATE = x509.ObjectIdentifier("2.23.133.8.3") class TpmRsaScheme(IntEnum): RSASSA = 0x0014 RSAPSS = 0x0016 OAEP = 0x0017 RSAES = 0x0015 class TpmAlgAsym(IntEnum): RSA = 0x0001 ECC = 0x0023 class TpmAlgHash(IntEnum): SHA1 = 0x0004 SHA256 = 0x000B SHA384 = 0x000C SHA512 = 0x000D def _hash_alg(self): if self == TpmAlgHash.SHA1: return hashes.SHA1() # nosec elif self == TpmAlgHash.SHA256: return hashes.SHA256() elif self == TpmAlgHash.SHA384: return hashes.SHA384() elif self == TpmAlgHash.SHA512: return hashes.SHA512() return NotImplementedError( "_hash_alg is not implemented for {0!r}".format(self) ) TpmsCertifyInfo = namedtuple("TpmsCertifyInfo", "name qualified_name") class TpmAttestationFormat(object): """the signature data is defined by [TPMv2-Part2] Section 10.12.8 (TPMS_ATTEST) as: TPM_GENERATED_VALUE (0xff544347 aka "\xffTCG") TPMI_ST_ATTEST - always TPM_ST_ATTEST_CERTIFY (0x8017) because signing procedure defines it should call TPM_Certify [TPMv2-Part3] Section 18.2 TPM2B_NAME size (uint16) name (size long) TPM2B_DATA size (uint16) name (size long) TPMS_CLOCK_INFO clock (uint64) resetCount (uint32) restartCount (uint32) safe (byte) 1 yes, 0 no firmwareVersion uint64 attested TPMS_CERTIFY_INFO (because TPM_ST_ATTEST_CERTIFY) name TPM2B_NAME qualified_name TPM2B_NAME See: https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-3-Commands-01.38.pdf """ TPM_GENERATED_VALUE = b"\xffTCG" TPM_ST_ATTEST_CERTIFY = b"\x80\x17" @classmethod def parse(cls, data): reader = ByteBuffer(data) generated_value = reader.read(4) # Verify that magic is set to TPM_GENERATED_VALUE. # see https://w3c.github.io/webauthn/#sctn-tpm-attestation # verification procedure if generated_value != cls.TPM_GENERATED_VALUE: raise ValueError("generated value field is invalid") # Verify that type is set to TPM_ST_ATTEST_CERTIFY. # see https://w3c.github.io/webauthn/#sctn-tpm-attestation # verification procedure tpmi_st_attest = reader.read(2) if tpmi_st_attest != cls.TPM_ST_ATTEST_CERTIFY: raise ValueError("tpmi_st_attest field is invalid") try: name = reader.read(reader.unpack("!H")) data = reader.read(reader.unpack("!H")) clock = reader.unpack("!Q") reset_count = reader.unpack("!L") restart_count = reader.unpack("!L") safe_value = reader.unpack("B") if safe_value not in (0, 1): raise ValueError("invalid value 0x{0:x} for boolean".format(safe_value)) safe = safe_value == 1 firmware_version = reader.unpack("!Q") attested_name = reader.read(reader.unpack("!H")) attested_qualified_name = reader.read(reader.unpack("!H")) except struct.error as e: raise ValueError(e) return cls( name=name, data=data, clock_info=(clock, reset_count, restart_count, safe), firmware_version=firmware_version, attested=TpmsCertifyInfo( name=attested_name, qualified_name=attested_qualified_name ), ) def __init__(self, name, data, clock_info, firmware_version, attested): self.name = name self.data = data self.clock_info = clock_info self.firmware_version = firmware_version self.attested = attested def __repr__(self): return ( "