././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5382886 fido2-1.1.2/COPYING0000644000000000000000000000246214413232070010477 0ustar00Copyright (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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5382886 fido2-1.1.2/COPYING.APLv20000644000000000000000000002645014413232070011365 0ustar00 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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5382886 fido2-1.1.2/COPYING.MPLv20000644000000000000000000004131314413232070011374 0ustar00Mozilla 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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5382886 fido2-1.1.2/examples/acr122u.py0000644000000000000000000000460414413232070013013 0ustar00from 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5382886 fido2-1.1.2/examples/acr122usam.py0000644000000000000000000002532614413232070013520 0ustar00# 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" % 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() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5382886 fido2-1.1.2/examples/acr1252u.py0000644000000000000000000001331714413232070013101 0ustar00from 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1687790326.9336421 fido2-1.1.2/examples/bio_enrollment.py0000644000000000000000000000562514446321367014666 0ustar00# 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 fido2.hid import CtapHidDevice from fido2.ctap2 import Ctap2, FPBioEnrollment, CaptureError from fido2.ctap2.pin import ClientPin from fido2.ctap2.bio import BioEnrollment from getpass import getpass import sys pin = None uv = "discouraged" for dev in CtapHidDevice.list_devices(): try: ctap = Ctap2(dev) if BioEnrollment.is_supported(ctap.info): 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) bio.set_name(template_id, "Example") print("Fingerprint registered successfully with ID:", template_id) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5382886 fido2-1.1.2/examples/cred_blob.py0000644000000000000000000001063514413232070013550 0ustar00# 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 fido2.hid import CtapHidDevice from fido2.client import Fido2Client, UserInteraction 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 # Handle user interaction class CliInteraction(UserInteraction): def prompt_up(self): print("\nTouch your authenticator device now...\n") def request_pin(self, permissions, rd_id): return getpass("Enter PIN: ") def request_uv(self, permissions, rd_id): print("User Verification required.") return True # Locate a device for dev in enumerate_devices(): client = Fido2Client(dev, "https://example.com", user_interaction=CliInteraction()) if "credBlob" in client.info.extensions: break else: print("No Authenticator with the CredBlob extension found!") sys.exit(1) # Prefer UV token if supported uv = "discouraged" if client.info.options.get("pinUvAuthToken") or client.info.options.get("uv"): uv = "preferred" print("Authenticator supports UV token") server = Fido2Server({"id": "example.com", "name": "Example RP"}) user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, resident_key_requirement="required", user_verification=uv, authenticator_attachment="cross-platform", ) # Add CredBlob extension, attach data blob = os.urandom(32) # 32 random bytes options = dict(create_options["publicKey"]) options["extensions"] = {"credBlob": blob} # Create a credential result = client.make_credential(options) # 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() options = dict(request_options["publicKey"]) options["extensions"] = {"getCredBlob": True} # Authenticate the credential # Only one cred in allowCredentials, only one response. result = client.get_assertion(options).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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5382886 fido2-1.1.2/examples/credential.py0000644000000000000000000001123414413232070013743 0ustar00# 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 fido2.hid import CtapHidDevice from fido2.client import Fido2Client, WindowsClient, UserInteraction from fido2.server import Fido2Server from getpass import getpass import sys import ctypes # Handle user interaction class CliInteraction(UserInteraction): def prompt_up(self): print("\nTouch your authenticator device now...\n") def request_pin(self, permissions, rd_id): return getpass("Enter PIN: ") def request_uv(self, permissions, rd_id): print("User Verification required.") return True 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.") 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", user_interaction=CliInteraction()) # Prefer UV if supported and configured if client.info.options.get("uv") or client.info.options.get("pinUvAuthToken"): uv = "preferred" print("Authenticator supports User Verification") 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 result = client.make_credential(create_options["publicKey"]) # 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 result = client.get_assertion(request_options["publicKey"]) # Only one cred in allowCredentials, only one response. result = result.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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5382886 fido2-1.1.2/examples/get_info.py0000644000000000000000000000501214413232070013420 0ustar00# 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 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("Product name: %s" % dev.product_name) print("Serial number: %s" % dev.serial_number) 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() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5382886 fido2-1.1.2/examples/hmac_secret.py0000644000000000000000000001140114413232070014102 0ustar00# 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 fido2.hid import CtapHidDevice from fido2.client import Fido2Client, UserInteraction 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 # Handle user interaction class CliInteraction(UserInteraction): def prompt_up(self): print("\nTouch your authenticator device now...\n") def request_pin(self, permissions, rd_id): return getpass("Enter PIN: ") def request_uv(self, permissions, rd_id): print("User Verification required.") return True # Locate a device for dev in enumerate_devices(): client = Fido2Client(dev, "https://example.com", user_interaction=CliInteraction()) if "hmac-secret" in client.info.extensions: break else: print("No Authenticator with the HmacSecret extension found!") sys.exit(1) # Prepare parameters for makeCredential rp = {"id": "example.com", "name": "Example RP"} user = {"id": b"user_id", "name": "A. User"} challenge = b"Y2hhbGxlbmdl" # Create a credential with a HmacSecret result = client.make_credential( { "rp": rp, "user": user, "challenge": challenge, "pubKeyCredParams": [{"type": "public-key", "alg": -7}], "extensions": {"hmacCreateSecret": True}, }, ) # 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:", salt.hex()) # Authenticate the credential result = client.get_assertion( { "rpId": rp["id"], "challenge": challenge, "allowCredentials": allow_list, "extensions": {"hmacGetSecret": {"salt1": salt}}, }, ).get_response( 0 ) # Only one cred in allowList, only one response. output1 = result.extension_results["hmacGetSecret"]["output1"] print("Authenticated, secret:", output1.hex()) # Authenticate again, using two salts to generate two secrets: # Generate a second salt for HmacSecret: salt2 = os.urandom(32) print("Authenticate with second salt:", salt2.hex()) # 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}}, }, ).get_response( 0 ) # One cred in allowCredentials, single response. output = result.extension_results["hmacGetSecret"] print("Old secret:", output["output1"].hex()) print("New secret:", output["output2"].hex()) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5382886 fido2-1.1.2/examples/large_blobs.py0000644000000000000000000001164514413232070014112 0ustar00# 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 fido2.hid import CtapHidDevice from fido2.client import Fido2Client, UserInteraction 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 # Handle user interaction class CliInteraction(UserInteraction): def prompt_up(self): print("\nTouch your authenticator device now...\n") def request_pin(self, permissions, rd_id): return getpass("Enter PIN: ") def request_uv(self, permissions, rd_id): print("User Verification required.") return True # Locate a device for dev in enumerate_devices(): client = Fido2Client(dev, "https://example.com", user_interaction=CliInteraction()) if "largeBlobKey" in client.info.extensions: break else: print("No Authenticator with the largeBlobKey extension found!") sys.exit(1) if not client.info.options.get("largeBlobs"): print("Authenticator does not support large blobs!") sys.exit(1) # Prefer UV token if supported uv = "discouraged" if client.info.options.get("pinUvAuthToken") or client.info.options.get("uv"): uv = "preferred" print("Authenticator supports UV token") server = Fido2Server({"id": "example.com", "name": "Example RP"}) user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, resident_key_requirement="required", user_verification=uv, authenticator_attachment="cross-platform", ) print("Creating a credential with LargeBlob support...") # Enable largeBlob options = dict(create_options["publicKey"]) options["extensions"] = {"largeBlob": {"support": "required"}} # Create a credential result = client.make_credential(options) # Complete registration auth_data = server.register_complete( state, result.client_data, result.attestation_object ) credentials = [auth_data.credential_data] if not result.extension_results.get("supported"): print("Credential does not support largeBlob, failure!") sys.exit(1) print("Credential created! Writing a blob...") # Prepare parameters for getAssertion request_options, state = server.authenticate_begin(user_verification=uv) # Write a large blob options = dict(request_options["publicKey"]) options["extensions"] = {"largeBlob": {"write": b"Here is some data to store!"}} # Authenticate the credential selection = client.get_assertion(options) # Only one cred in allowCredentials, only one response. result = selection.get_response(0) if not result.extension_results.get("written"): print("Failed to write blob!") sys.exit(1) print("Blob written! Reading back the blob...") # Read the blob options = dict(request_options["publicKey"]) options["extensions"] = {"largeBlob": {"read": True}} # Authenticate the credential selection = client.get_assertion(options) # Only one cred in allowCredentials, only one response. result = selection.get_response(0) print("Read blob:", result.extension_results.get("blob")) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1687774873.4323528 fido2-1.1.2/examples/multi_device.py0000644000000000000000000000714714446263231014323 0ustar00# 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 fido2.hid import CtapHidDevice from fido2.client import Fido2Client, ClientError, UserInteraction from threading import Event, Thread from getpass import getpass import sys # Locate a device devs = list(CtapHidDevice.list_devices()) if not devs: print("No FIDO device found") sys.exit(1) # Handle user interaction class CliInteraction(UserInteraction): def prompt_up(self): print("\nTouch your authenticator device now...\n") def request_pin(self, permissions, rd_id): return getpass("Enter PIN: ") def request_uv(self, permissions, rd_id): print("User Verification required.") return True cli_interaction = CliInteraction() clients = [ Fido2Client(d, "https://example.com", user_interaction=cli_interaction) 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() selected = None def select(client): global selected try: client.selection(cancel) selected = client except ClientError as e: if e.code != ClientError.ERR.TIMEOUT: raise else: return cancel.set() print("\nTouch the authenticator you wish to use...\n") threads = [] for client in clients: t = Thread(target=select, args=(client,)) threads.append(t) t.start() for t in threads: t.join() if cancel.is_set(): print("Authenticator selected, making credential...") result = selected.make_credential( { "rp": rp, "user": user, "challenge": challenge, "pubKeyCredParams": [{"type": "public-key", "alg": -7}], }, ) print("New credential created!") print("ATTESTATION OBJECT:", result.attestation_object) print() print("CREDENTIAL DATA:", result.attestation_object.auth_data.credential_data) else: print("Operation timed out!") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5382886 fido2-1.1.2/examples/resident_key.py0000644000000000000000000001126614413232070014323 0ustar00# 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 fido2.hid import CtapHidDevice from fido2.client import Fido2Client, WindowsClient, UserInteraction 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 # Handle user interaction class CliInteraction(UserInteraction): def prompt_up(self): print("\nTouch your authenticator device now...\n") def request_pin(self, permissions, rd_id): return getpass("Enter PIN: ") def request_uv(self, permissions, rd_id): print("User Verification required.") return True 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", user_interaction=CliInteraction() ) if client.info.options.get("rk"): 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") 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_requirement="required", user_verification=uv, authenticator_attachment="cross-platform", ) # Create a credential result = client.make_credential(create_options["publicKey"]) # 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 selection = client.get_assertion(request_options["publicKey"]) 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1688637503.5717473 fido2-1.1.2/examples/server/poetry.lock0000644000000000000000000006742114451510100014765 0ustar00# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. [[package]] name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false python-versions = "*" files = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] [package.dependencies] pycparser = "*" [[package]] name = "click" version = "8.1.3" description = "Composable command line interface toolkit" category = "main" optional = false python-versions = ">=3.7" files = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "cryptography" version = "41.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false python-versions = ">=3.7" files = [ {file = "cryptography-41.0.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:f73bff05db2a3e5974a6fd248af2566134d8981fd7ab012e5dd4ddb1d9a70699"}, {file = "cryptography-41.0.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1a5472d40c8f8e91ff7a3d8ac6dfa363d8e3138b961529c996f3e2df0c7a411a"}, {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fa01527046ca5facdf973eef2535a27fec4cb651e4daec4d043ef63f6ecd4ca"}, {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b46e37db3cc267b4dea1f56da7346c9727e1209aa98487179ee8ebed09d21e43"}, {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d198820aba55660b4d74f7b5fd1f17db3aa5eb3e6893b0a41b75e84e4f9e0e4b"}, {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:948224d76c4b6457349d47c0c98657557f429b4e93057cf5a2f71d603e2fc3a3"}, {file = "cryptography-41.0.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:059e348f9a3c1950937e1b5d7ba1f8e968508ab181e75fc32b879452f08356db"}, {file = "cryptography-41.0.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b4ceb5324b998ce2003bc17d519080b4ec8d5b7b70794cbd2836101406a9be31"}, {file = "cryptography-41.0.1-cp37-abi3-win32.whl", hash = "sha256:8f4ab7021127a9b4323537300a2acfb450124b2def3756f64dc3a3d2160ee4b5"}, {file = "cryptography-41.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:1fee5aacc7367487b4e22484d3c7e547992ed726d14864ee33c0176ae43b0d7c"}, {file = "cryptography-41.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9a6c7a3c87d595608a39980ebaa04d5a37f94024c9f24eb7d10262b92f739ddb"}, {file = "cryptography-41.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5d092fdfedaec4cbbffbf98cddc915ba145313a6fdaab83c6e67f4e6c218e6f3"}, {file = "cryptography-41.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a8e6c2de6fbbcc5e14fd27fb24414507cb3333198ea9ab1258d916f00bc3039"}, {file = "cryptography-41.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb33ccf15e89f7ed89b235cff9d49e2e62c6c981a6061c9c8bb47ed7951190bc"}, {file = "cryptography-41.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f0ff6e18d13a3de56f609dd1fd11470918f770c6bd5d00d632076c727d35485"}, {file = "cryptography-41.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7bfc55a5eae8b86a287747053140ba221afc65eb06207bedf6e019b8934b477c"}, {file = "cryptography-41.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eb8163f5e549a22888c18b0d53d6bb62a20510060a22fd5a995ec8a05268df8a"}, {file = "cryptography-41.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8dde71c4169ec5ccc1087bb7521d54251c016f126f922ab2dfe6649170a3b8c5"}, {file = "cryptography-41.0.1.tar.gz", hash = "sha256:d34579085401d3f49762d2f7d6634d6b6c2ae1242202e860f4d26b046e3a1006"}, ] [package.dependencies] cffi = ">=1.12" [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] nox = ["nox"] pep8test = ["black", "check-sdist", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "fido2" version = "1.1.2-dev.0" description = "FIDO2/WebAuthn library for implementing clients and servers." category = "main" optional = false python-versions = "^3.7" files = [] develop = false [package.dependencies] cryptography = ">=2.6, !=35, <44" [package.extras] pcsc = ["pyscard (>=1.9,<3)"] [package.source] type = "directory" url = "../.." [[package]] name = "flask" version = "2.2.5" description = "A simple framework for building complex web applications." category = "main" optional = false python-versions = ">=3.7" files = [ {file = "Flask-2.2.5-py3-none-any.whl", hash = "sha256:58107ed83443e86067e41eff4631b058178191a355886f8e479e347fa1285fdf"}, {file = "Flask-2.2.5.tar.gz", hash = "sha256:edee9b0a7ff26621bd5a8c10ff484ae28737a2410d99b0bb9a6850c7fb977aa0"}, ] [package.dependencies] click = ">=8.0" importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} itsdangerous = ">=2.0" Jinja2 = ">=3.0" Werkzeug = ">=2.2.2" [package.extras] async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] [[package]] name = "importlib-metadata" version = "6.7.0" description = "Read metadata from Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, ] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." category = "main" optional = false python-versions = ">=3.7" files = [ {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, ] [[package]] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." category = "main" optional = false python-versions = ">=3.7" files = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] [package.dependencies] MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." category = "main" optional = false python-versions = ">=3.7" files = [ {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] [[package]] name = "pycparser" version = "2.21" description = "C parser in Python" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] [[package]] name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" files = [ {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] [[package]] name = "werkzeug" version = "2.2.3" description = "The comprehensive WSGI web application library." category = "main" optional = false python-versions = ">=3.7" files = [ {file = "Werkzeug-2.2.3-py3-none-any.whl", hash = "sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"}, {file = "Werkzeug-2.2.3.tar.gz", hash = "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe"}, ] [package.dependencies] MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog"] [[package]] name = "zipp" version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" files = [ {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "2.0" python-versions = "^3.7" content-hash = "d21c95e6be38b286b3aa30ae191c220faf68523b4fcfddc46e81c694952d7f73" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/examples/server/pyproject.toml0000644000000000000000000000076514413232070015510 0ustar00[tool.poetry] name = "fido2-example-server" version = "0.1.0" description = "Example server for python-fido2" authors = ["Dain Nilsson "] license = "Apache-2" packages = [ { include = "server" }, ] [tool.poetry.dependencies] python = "^3.7" Flask = "^2.0" fido2 = {path = "../.."} [tool.poetry.dev-dependencies] [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] server = "server.server:main" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/examples/server/README.adoc0000644000000000000000000000467714413232070014367 0ustar00== 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 `poetry`. For instructions on installing `poetry`, see https://python-poetry.org/. Run the following command in the `examples/server` directory to set up the example: $ poetry install Once the environment has been created, you can run the server by running: $ poetry 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: $ poetry 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/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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/examples/server/server/__init__.py0000644000000000000000000000000014413232070016171 0ustar00././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/examples/server/server/server.py0000644000000000000000000000772714413232070015767 0ustar00# 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 fido2.webauthn import PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity from fido2.server import Fido2Server from flask import Flask, session, request, redirect, abort, jsonify import os import fido2.features fido2.features.webauthn_json_mapping.enabled = True app = Flask(__name__, static_url_path="") app.secret_key = os.urandom(32) # Used for session. rp = PublicKeyCredentialRpEntity(name="Demo server", id="localhost") 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(): options, state = server.register_begin( PublicKeyCredentialUserEntity( id=b"user_id", name="a_user", display_name="A. User", ), credentials, user_verification="discouraged", authenticator_attachment="cross-platform", ) session["state"] = state print("\n\n\n\n") print(options) print("\n\n\n\n") return jsonify(dict(options)) @app.route("/api/register/complete", methods=["POST"]) def register_complete(): response = request.json print("RegistrationResponse:", response) auth_data = server.register_complete(session["state"], response) credentials.append(auth_data.credential_data) print("REGISTERED CREDENTIAL:", auth_data.credential_data) return jsonify({"status": "OK"}) @app.route("/api/authenticate/begin", methods=["POST"]) def authenticate_begin(): if not credentials: abort(404) options, state = server.authenticate_begin(credentials) session["state"] = state return jsonify(dict(options)) @app.route("/api/authenticate/complete", methods=["POST"]) def authenticate_complete(): if not credentials: abort(404) response = request.json print("AuthenticationResponse:", response) server.authenticate_complete( session.pop("state"), credentials, response, ) print("ASSERTION OK") return jsonify({"status": "OK"}) def main(): print(__doc__) app.run(ssl_context="adhoc", debug=False) if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/examples/server/server/static/authenticate.html0000644000000000000000000000320214413232070020722 0ustar00 Fido 2.0 webauthn demo

WebAuthn demo using python-fido2

This demo requires a browser supporting the WebAuthn API!


Authenticate using a credential

././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/examples/server/server/static/index.html0000644000000000000000000000111514413232070017354 0ustar00 Fido 2.0 webauthn demo

WebAuthn demo using python-fido2

This demo requires a browser supporting the WebAuthn API!


Available actions

Register
Authenticate
././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/examples/server/server/static/register.html0000644000000000000000000000321714413232070020076 0ustar00 Fido 2.0 webauthn demo

WebAuthn demo using python-fido2

This demo requires a browser supporting the WebAuthn API!


Register a credential

././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/examples/server/server/static/webauthn-json.browser-ponyfill.js0000644000000000000000000001367314413232070024031 0ustar00// src/webauthn-json/base64url.ts function base64urlToBuffer(baseurl64String) { const padding = "==".slice(0, (4 - baseurl64String.length % 4) % 4); const base64String = baseurl64String.replace(/-/g, "+").replace(/_/g, "/") + padding; const str = atob(base64String); const buffer = new ArrayBuffer(str.length); const byteView = new Uint8Array(buffer); for (let i = 0; i < str.length; i++) { byteView[i] = str.charCodeAt(i); } return buffer; } function bufferToBase64url(buffer) { const byteView = new Uint8Array(buffer); let str = ""; for (const charCode of byteView) { str += String.fromCharCode(charCode); } const base64String = btoa(str); const base64urlString = base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); return base64urlString; } // src/webauthn-json/convert.ts var copyValue = "copy"; var convertValue = "convert"; function convert(conversionFn, schema, input) { if (schema === copyValue) { return input; } if (schema === convertValue) { return conversionFn(input); } if (schema instanceof Array) { return input.map((v) => convert(conversionFn, schema[0], v)); } if (schema instanceof Object) { const output = {}; for (const [key, schemaField] of Object.entries(schema)) { if (schemaField.derive) { const v = schemaField.derive(input); if (v !== void 0) { input[key] = v; } } if (!(key in input)) { if (schemaField.required) { throw new Error(`Missing key: ${key}`); } continue; } if (input[key] == null) { output[key] = null; continue; } output[key] = convert(conversionFn, schemaField.schema, input[key]); } return output; } } function derived(schema, derive) { return { required: true, schema, derive }; } function required(schema) { return { required: true, schema }; } function optional(schema) { return { required: false, schema }; } // src/webauthn-json/basic/schema.ts var publicKeyCredentialDescriptorSchema = { type: required(copyValue), id: required(convertValue), transports: optional(copyValue) }; var simplifiedExtensionsSchema = { appid: optional(copyValue), appidExclude: optional(copyValue), credProps: optional(copyValue) }; var simplifiedClientExtensionResultsSchema = { appid: optional(copyValue), appidExclude: optional(copyValue), credProps: optional(copyValue) }; var credentialCreationOptions = { publicKey: required({ rp: required(copyValue), user: required({ id: required(convertValue), name: required(copyValue), displayName: required(copyValue) }), challenge: required(convertValue), pubKeyCredParams: required(copyValue), timeout: optional(copyValue), excludeCredentials: optional([publicKeyCredentialDescriptorSchema]), authenticatorSelection: optional(copyValue), attestation: optional(copyValue), extensions: optional(simplifiedExtensionsSchema) }), signal: optional(copyValue) }; var publicKeyCredentialWithAttestation = { type: required(copyValue), id: required(copyValue), rawId: required(convertValue), authenticatorAttachment: optional(copyValue), response: required({ clientDataJSON: required(convertValue), attestationObject: required(convertValue), transports: derived(copyValue, (response) => { var _a; return ((_a = response.getTransports) == null ? void 0 : _a.call(response)) || []; }) }), clientExtensionResults: derived(simplifiedClientExtensionResultsSchema, (pkc) => pkc.getClientExtensionResults()) }; var credentialRequestOptions = { mediation: optional(copyValue), publicKey: required({ challenge: required(convertValue), timeout: optional(copyValue), rpId: optional(copyValue), allowCredentials: optional([publicKeyCredentialDescriptorSchema]), userVerification: optional(copyValue), extensions: optional(simplifiedExtensionsSchema) }), signal: optional(copyValue) }; var publicKeyCredentialWithAssertion = { type: required(copyValue), id: required(copyValue), rawId: required(convertValue), authenticatorAttachment: optional(copyValue), response: required({ clientDataJSON: required(convertValue), authenticatorData: required(convertValue), signature: required(convertValue), userHandle: required(convertValue) }), clientExtensionResults: derived(simplifiedClientExtensionResultsSchema, (pkc) => pkc.getClientExtensionResults()) }; // src/webauthn-json/basic/api.ts function createRequestFromJSON(requestJSON) { return convert(base64urlToBuffer, credentialCreationOptions, requestJSON); } function createResponseToJSON(credential) { return convert(bufferToBase64url, publicKeyCredentialWithAttestation, credential); } function getRequestFromJSON(requestJSON) { return convert(base64urlToBuffer, credentialRequestOptions, requestJSON); } function getResponseToJSON(credential) { return convert(bufferToBase64url, publicKeyCredentialWithAssertion, credential); } // src/webauthn-json/basic/supported.ts function supported() { return !!(navigator.credentials && navigator.credentials.create && navigator.credentials.get && window.PublicKeyCredential); } // src/webauthn-json/browser-ponyfill.ts async function create(options) { const response = await navigator.credentials.create(options); response.toJSON = () => createResponseToJSON(response); return response; } async function get(options) { const response = await navigator.credentials.get(options); response.toJSON = () => getResponseToJSON(response); return response; } export { create, get, createRequestFromJSON as parseCreationOptionsFromJSON, getRequestFromJSON as parseRequestOptionsFromJSON, supported }; //# sourceMappingURL=webauthn-json.browser-ponyfill.js.map ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/examples/server/server/static/webauthn-json.browser-ponyfill.js.map0000644000000000000000000003305314413232070024577 0ustar00{ "version": 3, "sources": ["../../src/webauthn-json/base64url.ts", "../../src/webauthn-json/convert.ts", "../../src/webauthn-json/basic/schema.ts", "../../src/webauthn-json/basic/api.ts", "../../src/webauthn-json/basic/supported.ts", "../../src/webauthn-json/browser-ponyfill.ts"], "sourcesContent": ["export type Base64urlString = string;\n\nexport function base64urlToBuffer(\n baseurl64String: Base64urlString,\n): ArrayBuffer {\n // Base64url to Base64\n const padding = \"==\".slice(0, (4 - (baseurl64String.length % 4)) % 4);\n const base64String =\n baseurl64String.replace(/-/g, \"+\").replace(/_/g, \"/\") + padding;\n\n // Base64 to binary string\n const str = atob(base64String);\n\n // Binary string to buffer\n const buffer = new ArrayBuffer(str.length);\n const byteView = new Uint8Array(buffer);\n for (let i = 0; i < str.length; i++) {\n byteView[i] = str.charCodeAt(i);\n }\n return buffer;\n}\n\nexport function bufferToBase64url(buffer: ArrayBuffer): Base64urlString {\n // Buffer to binary string\n const byteView = new Uint8Array(buffer);\n let str = \"\";\n for (const charCode of byteView) {\n str += String.fromCharCode(charCode);\n }\n\n // Binary string to base64\n const base64String = btoa(str);\n\n // Base64 to base64url\n // We assume that the base64url string is well-formed.\n const base64urlString = base64String\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=/g, \"\");\n return base64urlString;\n}\n", "// We export these values in order so that they can be used to deduplicate\n// schema definitions in minified JS code.\n\nimport { Schema, SchemaProperty } from \"./schema-format\";\n\n// TODO: Parcel isn't deduplicating these values.\nexport const copyValue = \"copy\";\nexport const convertValue = \"convert\";\n\nexport function convert(\n conversionFn: (v: From) => To,\n schema: Schema,\n input: any,\n): any {\n if (schema === copyValue) {\n return input;\n }\n if (schema === convertValue) {\n return conversionFn(input);\n }\n if (schema instanceof Array) {\n return input.map((v: any) => convert(conversionFn, schema[0], v));\n }\n if (schema instanceof Object) {\n const output: any = {};\n for (const [key, schemaField] of Object.entries(schema)) {\n if (schemaField.derive) {\n const v = schemaField.derive(input);\n if (v !== undefined) {\n input[key] = v;\n }\n }\n\n if (!(key in input)) {\n if (schemaField.required) {\n throw new Error(`Missing key: ${key}`);\n }\n continue;\n }\n // Fields can be null (rather than missing or `undefined`), e.g. the\n // `userHandle` field of the `AuthenticatorAssertionResponse`:\n // https://www.w3.org/TR/webauthn/#iface-authenticatorassertionresponse\n if (input[key] == null) {\n output[key] = null;\n continue;\n }\n output[key] = convert(\n conversionFn,\n schemaField.schema,\n input[key],\n );\n }\n return output;\n }\n}\n\nexport function derived(\n schema: Schema,\n derive: (v: any) => any,\n): SchemaProperty {\n return {\n required: true,\n schema,\n derive,\n };\n}\n\nexport function required(schema: Schema): SchemaProperty {\n return {\n required: true,\n schema,\n };\n}\n\nexport function optional(schema: Schema): SchemaProperty {\n return {\n required: false,\n schema,\n };\n}\n", "import { Schema } from \"../schema-format\";\nimport {\n convertValue as convert,\n copyValue as copy,\n derived,\n optional,\n required,\n} from \"../convert\";\n\n// Shared by `create()` and `get()`.\n\nconst publicKeyCredentialDescriptorSchema: Schema = {\n type: required(copy),\n id: required(convert),\n transports: optional(copy),\n};\n\nconst simplifiedExtensionsSchema: Schema = {\n appid: optional(copy),\n appidExclude: optional(copy),\n credProps: optional(copy),\n};\n\nconst simplifiedClientExtensionResultsSchema = {\n appid: optional(copy),\n appidExclude: optional(copy),\n credProps: optional(copy),\n};\n\n// `navigator.create()` request\n\nexport const credentialCreationOptions: Schema = {\n publicKey: required({\n rp: required(copy),\n user: required({\n id: required(convert),\n name: required(copy),\n displayName: required(copy),\n }),\n\n challenge: required(convert),\n pubKeyCredParams: required(copy),\n\n timeout: optional(copy),\n excludeCredentials: optional([publicKeyCredentialDescriptorSchema]),\n authenticatorSelection: optional(copy),\n attestation: optional(copy),\n extensions: optional(simplifiedExtensionsSchema),\n }),\n signal: optional(copy),\n};\n\n// `navigator.create()` response\n\nexport const publicKeyCredentialWithAttestation: Schema = {\n type: required(copy),\n id: required(copy),\n rawId: required(convert),\n authenticatorAttachment: optional(copy),\n response: required({\n clientDataJSON: required(convert),\n attestationObject: required(convert),\n transports: derived(\n copy,\n (response: any) => response.getTransports?.() || [],\n ),\n }),\n clientExtensionResults: derived(\n simplifiedClientExtensionResultsSchema,\n (pkc: PublicKeyCredential) => pkc.getClientExtensionResults(),\n ),\n};\n\n// `navigator.get()` request\n\nexport const credentialRequestOptions: Schema = {\n mediation: optional(copy),\n publicKey: required({\n challenge: required(convert),\n timeout: optional(copy),\n rpId: optional(copy),\n allowCredentials: optional([publicKeyCredentialDescriptorSchema]),\n userVerification: optional(copy),\n extensions: optional(simplifiedExtensionsSchema),\n }),\n signal: optional(copy),\n};\n\n// `navigator.get()` response\n\nexport const publicKeyCredentialWithAssertion: Schema = {\n type: required(copy),\n id: required(copy),\n rawId: required(convert),\n authenticatorAttachment: optional(copy),\n response: required({\n clientDataJSON: required(convert),\n authenticatorData: required(convert),\n signature: required(convert),\n userHandle: required(convert),\n }),\n clientExtensionResults: derived(\n simplifiedClientExtensionResultsSchema,\n (pkc: PublicKeyCredential) => pkc.getClientExtensionResults(),\n ),\n};\n\nexport const schema: { [s: string]: Schema } = {\n credentialCreationOptions,\n publicKeyCredentialWithAttestation,\n credentialRequestOptions,\n publicKeyCredentialWithAssertion,\n};\n", "import { base64urlToBuffer, bufferToBase64url } from \"../base64url\";\nimport { convert } from \"../convert\";\nimport {\n CredentialCreationOptionsJSON,\n CredentialRequestOptionsJSON,\n PublicKeyCredentialWithAssertionJSON,\n PublicKeyCredentialWithAttestationJSON,\n} from \"./json\";\nimport {\n credentialCreationOptions,\n credentialRequestOptions,\n publicKeyCredentialWithAssertion,\n publicKeyCredentialWithAttestation,\n} from \"./schema\";\n\nexport function createRequestFromJSON(\n requestJSON: CredentialCreationOptionsJSON,\n): CredentialCreationOptions {\n return convert(base64urlToBuffer, credentialCreationOptions, requestJSON);\n}\n\nexport function createResponseToJSON(\n credential: PublicKeyCredential,\n): PublicKeyCredentialWithAttestationJSON {\n return convert(\n bufferToBase64url,\n publicKeyCredentialWithAttestation,\n credential,\n );\n}\n\nexport async function create(\n requestJSON: CredentialCreationOptionsJSON,\n): Promise {\n const credential = (await navigator.credentials.create(\n createRequestFromJSON(requestJSON),\n )) as PublicKeyCredential;\n return createResponseToJSON(credential);\n}\n\nexport function getRequestFromJSON(\n requestJSON: CredentialRequestOptionsJSON,\n): CredentialRequestOptions {\n return convert(base64urlToBuffer, credentialRequestOptions, requestJSON);\n}\n\nexport function getResponseToJSON(\n credential: PublicKeyCredential,\n): PublicKeyCredentialWithAssertionJSON {\n return convert(\n bufferToBase64url,\n publicKeyCredentialWithAssertion,\n credential,\n );\n}\n\nexport async function get(\n requestJSON: CredentialRequestOptionsJSON,\n): Promise {\n const credential = (await navigator.credentials.get(\n getRequestFromJSON(requestJSON),\n )) as PublicKeyCredential;\n return getResponseToJSON(credential);\n}\n\ndeclare global {\n interface Window {\n PublicKeyCredential: PublicKeyCredential | undefined;\n }\n}\n", "// This function does a simple check to test for the credential management API\n// functions we need, and an indication of public key credential authentication\n// support.\n// https://developers.google.com/web/updates/2018/03/webauthn-credential-management\n\nexport function supported(): boolean {\n return !!(\n navigator.credentials &&\n navigator.credentials.create &&\n navigator.credentials.get &&\n window.PublicKeyCredential\n );\n}\n", "import {\n createRequestFromJSON as parseCreationOptionsFromJSON,\n createResponseToJSON,\n getRequestFromJSON as parseRequestOptionsFromJSON,\n getResponseToJSON,\n} from \"./basic/api\";\nimport { supported } from \"./basic/supported\";\n\nimport {\n CredentialCreationOptionsJSON,\n CredentialRequestOptionsJSON,\n PublicKeyCredentialWithAssertionJSON as AuthenticationResponseJSON,\n PublicKeyCredentialWithAttestationJSON as RegistrationResponseJSON,\n} from \"./basic/json\";\n\nexport { parseCreationOptionsFromJSON, parseRequestOptionsFromJSON, supported };\nexport type {\n CredentialCreationOptionsJSON,\n CredentialRequestOptionsJSON,\n AuthenticationResponseJSON,\n RegistrationResponseJSON,\n};\n\nexport interface RegistrationPublicKeyCredential extends PublicKeyCredential {\n toJSON(): RegistrationResponseJSON;\n}\n\nexport async function create(\n options: CredentialCreationOptions,\n): Promise {\n const response = (await navigator.credentials.create(\n options,\n )) as RegistrationPublicKeyCredential;\n response.toJSON = () => createResponseToJSON(response);\n return response;\n}\n\nexport interface AuthenticationPublicKeyCredential extends PublicKeyCredential {\n toJSON(): AuthenticationResponseJSON;\n}\n\nexport async function get(\n options: CredentialRequestOptions,\n): Promise {\n const response = (await navigator.credentials.get(\n options,\n )) as AuthenticationPublicKeyCredential;\n response.toJSON = () => getResponseToJSON(response);\n return response;\n}\n"], "mappings": ";AAEO,2BACL,iBACa;AAEb,QAAM,UAAU,KAAK,MAAM,GAAI,KAAK,gBAAgB,SAAS,KAAM;AACnE,QAAM,eACJ,gBAAgB,QAAQ,MAAM,KAAK,QAAQ,MAAM,OAAO;AAG1D,QAAM,MAAM,KAAK;AAGjB,QAAM,SAAS,IAAI,YAAY,IAAI;AACnC,QAAM,WAAW,IAAI,WAAW;AAChC,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,aAAS,KAAK,IAAI,WAAW;AAAA;AAE/B,SAAO;AAAA;AAGF,2BAA2B,QAAsC;AAEtE,QAAM,WAAW,IAAI,WAAW;AAChC,MAAI,MAAM;AACV,aAAW,YAAY,UAAU;AAC/B,WAAO,OAAO,aAAa;AAAA;AAI7B,QAAM,eAAe,KAAK;AAI1B,QAAM,kBAAkB,aACrB,QAAQ,OAAO,KACf,QAAQ,OAAO,KACf,QAAQ,MAAM;AACjB,SAAO;AAAA;;;ACjCF,IAAM,YAAY;AAClB,IAAM,eAAe;AAErB,iBACL,cACA,QACA,OACK;AACL,MAAI,WAAW,WAAW;AACxB,WAAO;AAAA;AAET,MAAI,WAAW,cAAc;AAC3B,WAAO,aAAa;AAAA;AAEtB,MAAI,kBAAkB,OAAO;AAC3B,WAAO,MAAM,IAAI,CAAC,MAAW,QAAkB,cAAc,OAAO,IAAI;AAAA;AAE1E,MAAI,kBAAkB,QAAQ;AAC5B,UAAM,SAAc;AACpB,eAAW,CAAC,KAAK,gBAAgB,OAAO,QAAQ,SAAS;AACvD,UAAI,YAAY,QAAQ;AACtB,cAAM,IAAI,YAAY,OAAO;AAC7B,YAAI,MAAM,QAAW;AACnB,gBAAM,OAAO;AAAA;AAAA;AAIjB,UAAI,CAAE,QAAO,QAAQ;AACnB,YAAI,YAAY,UAAU;AACxB,gBAAM,IAAI,MAAM,gBAAgB;AAAA;AAElC;AAAA;AAKF,UAAI,MAAM,QAAQ,MAAM;AACtB,eAAO,OAAO;AACd;AAAA;AAEF,aAAO,OAAO,QACZ,cACA,YAAY,QACZ,MAAM;AAAA;AAGV,WAAO;AAAA;AAAA;AAIJ,iBACL,QACA,QACgB;AAChB,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA;AAAA;AAAA;AAIG,kBAAkB,QAAgC;AACvD,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA;AAAA;AAIG,kBAAkB,QAAgC;AACvD,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA;AAAA;;;AClEJ,IAAM,sCAA8C;AAAA,EAClD,MAAM,SAAS;AAAA,EACf,IAAI,SAAS;AAAA,EACb,YAAY,SAAS;AAAA;AAGvB,IAAM,6BAAqC;AAAA,EACzC,OAAO,SAAS;AAAA,EAChB,cAAc,SAAS;AAAA,EACvB,WAAW,SAAS;AAAA;AAGtB,IAAM,yCAAyC;AAAA,EAC7C,OAAO,SAAS;AAAA,EAChB,cAAc,SAAS;AAAA,EACvB,WAAW,SAAS;AAAA;AAKf,IAAM,4BAAoC;AAAA,EAC/C,WAAW,SAAS;AAAA,IAClB,IAAI,SAAS;AAAA,IACb,MAAM,SAAS;AAAA,MACb,IAAI,SAAS;AAAA,MACb,MAAM,SAAS;AAAA,MACf,aAAa,SAAS;AAAA;AAAA,IAGxB,WAAW,SAAS;AAAA,IACpB,kBAAkB,SAAS;AAAA,IAE3B,SAAS,SAAS;AAAA,IAClB,oBAAoB,SAAS,CAAC;AAAA,IAC9B,wBAAwB,SAAS;AAAA,IACjC,aAAa,SAAS;AAAA,IACtB,YAAY,SAAS;AAAA;AAAA,EAEvB,QAAQ,SAAS;AAAA;AAKZ,IAAM,qCAA6C;AAAA,EACxD,MAAM,SAAS;AAAA,EACf,IAAI,SAAS;AAAA,EACb,OAAO,SAAS;AAAA,EAChB,yBAAyB,SAAS;AAAA,EAClC,UAAU,SAAS;AAAA,IACjB,gBAAgB,SAAS;AAAA,IACzB,mBAAmB,SAAS;AAAA,IAC5B,YAAY,QACV,WACA,CAAC,aAAe;AAhEtB;AAgEyB,6BAAS,kBAAT,sCAA8B;AAAA;AAAA;AAAA,EAGrD,wBAAwB,QACtB,wCACA,CAAC,QAA6B,IAAI;AAAA;AAM/B,IAAM,2BAAmC;AAAA,EAC9C,WAAW,SAAS;AAAA,EACpB,WAAW,SAAS;AAAA,IAClB,WAAW,SAAS;AAAA,IACpB,SAAS,SAAS;AAAA,IAClB,MAAM,SAAS;AAAA,IACf,kBAAkB,SAAS,CAAC;AAAA,IAC5B,kBAAkB,SAAS;AAAA,IAC3B,YAAY,SAAS;AAAA;AAAA,EAEvB,QAAQ,SAAS;AAAA;AAKZ,IAAM,mCAA2C;AAAA,EACtD,MAAM,SAAS;AAAA,EACf,IAAI,SAAS;AAAA,EACb,OAAO,SAAS;AAAA,EAChB,yBAAyB,SAAS;AAAA,EAClC,UAAU,SAAS;AAAA,IACjB,gBAAgB,SAAS;AAAA,IACzB,mBAAmB,SAAS;AAAA,IAC5B,WAAW,SAAS;AAAA,IACpB,YAAY,SAAS;AAAA;AAAA,EAEvB,wBAAwB,QACtB,wCACA,CAAC,QAA6B,IAAI;AAAA;;;ACxF/B,+BACL,aAC2B;AAC3B,SAAO,QAAQ,mBAAmB,2BAA2B;AAAA;AAGxD,8BACL,YACwC;AACxC,SAAO,QACL,mBACA,oCACA;AAAA;AAaG,4BACL,aAC0B;AAC1B,SAAO,QAAQ,mBAAmB,0BAA0B;AAAA;AAGvD,2BACL,YACsC;AACtC,SAAO,QACL,mBACA,kCACA;AAAA;;;AC/CG,qBAA8B;AACnC,SAAO,CAAC,CACN,WAAU,eACV,UAAU,YAAY,UACtB,UAAU,YAAY,OACtB,OAAO;AAAA;;;ACiBX,sBACE,SAC0C;AAC1C,QAAM,WAAY,MAAM,UAAU,YAAY,OAC5C;AAEF,WAAS,SAAS,MAAM,qBAAqB;AAC7C,SAAO;AAAA;AAOT,mBACE,SAC4C;AAC5C,QAAM,WAAY,MAAM,UAAU,YAAY,IAC5C;AAEF,WAAS,SAAS,MAAM,kBAAkB;AAC1C,SAAO;AAAA;", "names": [] } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/examples/u2f_nfc.py0000644000000000000000000000201014413232070013143 0ustar00from 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") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/examples/verify_attestation.py0000644000000000000000000001341414413232070015556 0ustar00# 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 fido2.hid import CtapHidDevice from fido2.client import Fido2Client, WindowsClient, UserInteraction from fido2.server import Fido2Server from fido2.attestation import AttestationVerifier from base64 import b64decode from getpass import getpass 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 uv = "discouraged" # Handle user interaction class CliInteraction(UserInteraction): def prompt_up(self): print("\nTouch your authenticator device now...\n") def request_pin(self, permissions, rd_id): return getpass("Enter PIN: ") def request_uv(self, permissions, rd_id): print("User Verification required.") return True 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.") 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", user_interaction=CliInteraction()) # Prefer UV if supported if client.info.options.get("uv"): uv = "preferred" print("Authenticator supports User Verification") 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 result = client.make_credential(create_options["publicKey"]) # 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:", auth_data.credential_data.aaguid.hex()) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/examples/verify_attestation_mds3.py0000644000000000000000000001477114413232070016513 0ustar00# 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 the FIDO MDS to only allow authenticators for which metadata is available. 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 correctly signed and valid according to its metadata statement. On Windows, the native WebAuthn API will be used. NOTE: You need to retrieve a MDS3 blob to run this example. See https://fidoalliance.org/metadata/ for more info. """ from fido2.hid import CtapHidDevice from fido2.client import Fido2Client, WindowsClient, UserInteraction from fido2.server import Fido2Server from fido2.attestation import UntrustedAttestation from fido2.mds3 import parse_blob, MdsAttestationVerifier from base64 import b64decode from getpass import getpass import sys import ctypes # Load the root CA used to sign the Metadata Statement blob ca = b64decode( """ MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK 6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH WD9f""" ) # Parse the MDS3 blob if len(sys.argv) != 2: print("This example requires a FIDO MDS3 metadata blob, which you can get here:") print("https://fidoalliance.org/metadata/") print() print("USAGE: python verify_attestation_mds3.py blob.jwt") sys.exit(1) with open(sys.argv[1], "rb") as f: metadata = parse_blob(f.read(), ca) # The verifier is used to query for data in the blob and to verify attestation. # We could optionally pass a filter function to only allow specific authenticators. mds = MdsAttestationVerifier(metadata) uv = "discouraged" # Handle user interaction class CliInteraction(UserInteraction): def prompt_up(self): print("\nTouch your authenticator device now...\n") def request_pin(self, permissions, rd_id): return getpass("Enter PIN: ") def request_uv(self, permissions, rd_id): print("User Verification required.") return True 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.") 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", user_interaction=CliInteraction()) # Prefer UV if supported if client.info.options.get("uv"): uv = "preferred" print("Authenticator supports User Verification") # The MDS verifier is passed to the server to verify that new credentials registered # exist in the MDS blob, else the registration will fail. server = Fido2Server( {"id": "example.com", "name": "Example RP"}, attestation="direct", verify_attestation=mds, ) 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 result = client.make_credential(create_options["publicKey"]) # Complete registration try: auth_data = server.register_complete( state, result.client_data, result.attestation_object ) print("Registration completed") # mds can also be used to get the metadata for the Authenticator, # regardless of if it was used to verify the attestation or not: entry = mds.find_entry(result.attestation_object, result.client_data.hash) print("Authenticator description:", entry.metadata_statement.description) except UntrustedAttestation: print("Authenticator metadata not found") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1688652058.0642614 fido2-1.1.2/fido2/__init__.py0000644000000000000000000000256314451544432012574 0ustar00# Copyright (c) 2013 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. __version__ = "1.1.2" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/attestation/__init__.py0000644000000000000000000000364614413232070015124 0ustar00# 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 .base import ( # noqa: F401 Attestation, NoneAttestation, AttestationType, AttestationResult, InvalidData, InvalidSignature, UnsupportedType, UnsupportedAttestation, UntrustedAttestation, verify_x509_chain, AttestationVerifier, ) from .apple import AppleAttestation # noqa: F401 from .android import AndroidSafetynetAttestation # noqa: F401 from .packed import PackedAttestation # noqa: F401 from .u2f import FidoU2FAttestation # noqa: F401 from .tpm import TpmAttestation # noqa: F401 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/attestation/android.py0000644000000000000000000000614014413232070014775 0ustar00# 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 annotations 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: bool = 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/attestation/apple.py0000644000000000000000000000463214413232070014462 0ustar00# 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 annotations 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/attestation/base.py0000644000000000000000000002007314413232070014270 0ustar00# 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 annotations from ..webauthn import AuthenticatorData, AttestationObject from enum import IntEnum, unique 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 dataclasses import dataclass from functools import wraps from typing import List, Type, Mapping, Sequence, Optional, Any 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().__init__( f'Attestation format "{fmt}" is not supported' if fmt else "This attestation format is not supported!" ) self.auth_data = auth_data self.fmt = fmt @unique class AttestationType(IntEnum): BASIC = 1 SELF = 2 ATT_CA = 3 ANON_CA = 4 NONE = 0 @dataclass class AttestationResult: attestation_type: AttestationType trust_path: List[bytes] 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: List[bytes]) -> None: """Verifies a chain of certificates. Checks that the first item in the chain is signed by the next, and so on. The first item is the leaf, the last is the root. """ 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: Mapping[str, Any], auth_data: AuthenticatorData, client_data_hash: bytes, ) -> AttestationResult: """Verifies attestation statement. :return: An AttestationResult if successful. """ @staticmethod def for_type(fmt: str) -> Type[Attestation]: for cls in Attestation.__subclasses__(): if getattr(cls, "FORMAT", None) == fmt: return cls class TypedUnsupportedAttestation(UnsupportedAttestation): def __init__(self): super().__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!") def _default_attestations(): return [ cls() # type: ignore for cls in Attestation.__subclasses__() if getattr(cls, "FORMAT", "none") != "none" ] class AttestationVerifier(abc.ABC): """Base class for verifying attestation. Override the ca_lookup method to provide a trusted root certificate used to verify the trust path from the attestation. """ def __init__(self, attestation_types: Optional[Sequence[Attestation]] = None): self._attestation_types = attestation_types or _default_attestations() @abc.abstractmethod def ca_lookup( self, attestation_result: AttestationResult, auth_data: AuthenticatorData ) -> Optional[bytes]: """Lookup a CA certificate to be used to verify a trust path. :param attestation_result: The result of the attestation :param auth_data: The AuthenticatorData from the registration """ raise NotImplementedError() def verify_attestation( self, attestation_object: AttestationObject, client_data_hash: bytes ) -> None: """Verify attestation. :param attestation_object: dict containing attestation data. :param client_data_hash: SHA256 hash of the ClientData bytes. """ att_verifier: Attestation = UnsupportedAttestation(attestation_object.fmt) for at in self._attestation_types: if getattr(at, "FORMAT", None) == attestation_object.fmt: att_verifier = at break # An unsupported format causes an exception to be thrown, which # includes the auth_data. The caller may choose to handle this case # and allow the registration. result = att_verifier.verify( attestation_object.att_stmt, attestation_object.auth_data, client_data_hash, ) # Lookup CA to use for trust path verification ca = self.ca_lookup(result, attestation_object.auth_data) if not ca: raise UntrustedAttestation("No root found for Authenticator") # Validate the trust chain try: verify_x509_chain(result.trust_path + [ca]) except InvalidSignature as e: raise UntrustedAttestation(e) def __call__(self, *args): """Allows passing an instance to Fido2Server as verify_attestation""" self.verify_attestation(*args) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/attestation/packed.py0000644000000000000000000001043714413232070014610 0ustar00# 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 annotations 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() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/attestation/tpm.py0000644000000000000000000004714614413232070014170 0ustar00# -*- 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 annotations 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, unique 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 from dataclasses import dataclass from typing import Tuple, Union, cast import struct TPM_ALG_NULL = 0x0010 OID_AIK_CERTIFICATE = x509.ObjectIdentifier("2.23.133.8.3") @unique class TpmRsaScheme(IntEnum): RSASSA = 0x0014 RSAPSS = 0x0016 OAEP = 0x0017 RSAES = 0x0015 @unique class TpmAlgAsym(IntEnum): RSA = 0x0001 ECC = 0x0023 @unique class TpmAlgHash(IntEnum): SHA1 = 0x0004 SHA256 = 0x000B SHA384 = 0x000C SHA512 = 0x000D def _hash_alg(self) -> hashes.Hash: 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(f"_hash_alg is not implemented for {self!r}") @dataclass class TpmsCertifyInfo: name: bytes qualified_name: bytes TPM_GENERATED_VALUE = b"\xffTCG" TPM_ST_ATTEST_CERTIFY = b"\x80\x17" @dataclass class TpmAttestationFormat: """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 """ name: bytes data: bytes clock_info: Tuple[int, int, int, bool] firmware_version: int attested: TpmsCertifyInfo @classmethod def parse(cls, data: bytes) -> TpmAttestationFormat: 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 != 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 != 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(f"invalid value 0x{safe_value:x} for boolean") 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 ), ) @dataclass class TpmsRsaParms: """Parse TPMS_RSA_PARMS struct See: https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf section 12.2.3.5 """ symmetric: int scheme: int key_bits: int exponent: int @classmethod def parse(cls, reader, attributes): symmetric = reader.unpack("!H") restricted_decryption = attributes & ( ATTRIBUTES.RESTRICTED | ATTRIBUTES.DECRYPT ) is_restricted_decryption_key = restricted_decryption == ( ATTRIBUTES.DECRYPT | ATTRIBUTES.RESTRICTED ) if not is_restricted_decryption_key and symmetric != TPM_ALG_NULL: # if the key is not a restricted decryption key, this field # shall be set to TPM_ALG_NULL. raise ValueError("symmetric is expected to be NULL") # Otherwise should be set to a supported symmetric algorithm, keysize and mode # TODO(baloo): Should we have non-null value here, do we expect more data? scheme = reader.unpack("!H") restricted_sign = attributes & (ATTRIBUTES.RESTRICTED | ATTRIBUTES.SIGN_ENCRYPT) is_unrestricted_signing_key = restricted_sign == ATTRIBUTES.SIGN_ENCRYPT if is_unrestricted_signing_key and scheme not in ( TPM_ALG_NULL, TpmRsaScheme.RSASSA, TpmRsaScheme.RSAPSS, ): raise ValueError( "key is an unrestricted signing key, scheme is " "expected to be TPM_ALG_RSAPSS, TPM_ALG_RSASSA, " "or TPM_ALG_NULL" ) is_restricted_signing_key = restricted_sign == ( ATTRIBUTES.RESTRICTED | ATTRIBUTES.SIGN_ENCRYPT ) if is_restricted_signing_key and scheme not in ( TpmRsaScheme.RSASSA, TpmRsaScheme.RSAPSS, ): raise ValueError( "key is a restricted signing key, scheme is " "expected to be TPM_ALG_RSAPSS, or TPM_ALG_RSASSA" ) is_unrestricted_decryption_key = restricted_decryption == ATTRIBUTES.DECRYPT if is_unrestricted_decryption_key and scheme not in ( TpmRsaScheme.OAEP, TpmRsaScheme.RSAES, TPM_ALG_NULL, ): raise ValueError( "key is an unrestricted decryption key, scheme is " "expected to be TPM_ALG_RSAES, TPM_ALG_OAEP, or " "TPM_ALG_NULL" ) if is_restricted_decryption_key and scheme not in (TPM_ALG_NULL,): raise ValueError( "key is an restricted decryption key, scheme is " "expected to be TPM_ALG_NULL" ) key_bits = reader.unpack("!H") exponent = reader.unpack("!L") if exponent == 0: # When zero, indicates that the exponent is the default of 2^16 + 1 exponent = (2**16) + 1 return cls(symmetric, scheme, key_bits, exponent) class Tpm2bPublicKeyRsa(bytes): @classmethod def parse(cls, reader: ByteBuffer) -> Tpm2bPublicKeyRsa: return cls(reader.read(reader.unpack("!H"))) @unique class TpmEccCurve(IntEnum): """TPM_ECC_CURVE https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf section 6.4 """ NONE = 0x0000 NIST_P192 = 0x0001 NIST_P224 = 0x0002 NIST_P256 = 0x0003 NIST_P384 = 0x0004 NIST_P521 = 0x0005 BN_P256 = 0x0010 BN_P638 = 0x0011 SM2_P256 = 0x0020 def to_curve(self) -> ec.EllipticCurve: if self == TpmEccCurve.NONE: raise ValueError("No such curve") elif self == TpmEccCurve.NIST_P192: return ec.SECP192R1() elif self == TpmEccCurve.NIST_P224: return ec.SECP224R1() elif self == TpmEccCurve.NIST_P256: return ec.SECP256R1() elif self == TpmEccCurve.NIST_P384: return ec.SECP384R1() elif self == TpmEccCurve.NIST_P521: return ec.SECP521R1() raise ValueError("curve is not supported", self) @unique class TpmiAlgKdf(IntEnum): """TPMI_ALG_KDF https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf section 9.28 """ NULL = TPM_ALG_NULL KDF1_SP800_56A = 0x0020 KDF2 = 0x0021 KDF1_SP800_108 = 0x0022 @dataclass class TpmsEccParms: symmetric: int scheme: int curve_id: TpmEccCurve kdf: TpmiAlgKdf @classmethod def parse(cls, reader: ByteBuffer) -> TpmsEccParms: symmetric = reader.unpack("!H") scheme = reader.unpack("!H") if symmetric != TPM_ALG_NULL: raise ValueError("symmetric is expected to be NULL") if scheme != TPM_ALG_NULL: raise ValueError("scheme is expected to be NULL") curve_id = TpmEccCurve(reader.unpack("!H")) kdf_scheme = TpmiAlgKdf(reader.unpack("!H")) return cls(symmetric, scheme, curve_id, kdf_scheme) @dataclass class TpmsEccPoint: """TPMS_ECC_POINT https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf Section 11.2.5.2 """ x: bytes y: bytes @classmethod def parse(cls, reader: ByteBuffer) -> TpmsEccPoint: x = reader.read(reader.unpack("!H")) y = reader.read(reader.unpack("!H")) return cls(x, y) @unique class ATTRIBUTES(IntEnum): """Object attributes see section 8.3 https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf """ FIXED_TPM = 1 << 1 ST_CLEAR = 1 << 2 FIXED_PARENT = 1 << 4 SENSITIVE_DATA_ORIGIN = 1 << 5 USER_WITH_AUTH = 1 << 6 ADMIN_WITH_POLICY = 1 << 7 NO_DA = 1 << 10 ENCRYPTED_DUPLICATION = 1 << 11 RESTRICTED = 1 << 16 DECRYPT = 1 << 17 SIGN_ENCRYPT = 1 << 18 SHALL_BE_ZERO = ( (1 << 0) # 0 Reserved | (1 << 3) # 3 Reserved | (0x3 << 8) # 9:8 Reserved | (0xF << 12) # 15:12 Reserved | ((0xFFFFFFFF << 19) & (2**32 - 1)) # 31:19 Reserved ) _PublicKey = Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey] _Parameters = Union[TpmsRsaParms, TpmsEccParms] _Unique = Union[Tpm2bPublicKeyRsa, TpmsEccPoint] @dataclass class TpmPublicFormat: """the public area structure is defined by [TPMv2-Part2] Section 12.2.4 (TPMT_PUBLIC) as: TPMI_ALG_PUBLIC - type TPMI_ALG_HASH - nameAlg or + to indicate TPM_ALG_NULL TPMA_OBJECT - objectAttributes TPM2B_DIGEST - authPolicy TPMU_PUBLIC_PARMS - type parameters TPMU_PUBLIC_ID - uniq See: https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf """ sign_alg: TpmAlgAsym name_alg: TpmAlgHash attributes: int auth_policy: bytes parameters: _Parameters unique: _Unique data: bytes @classmethod def parse(cls, data: bytes) -> TpmPublicFormat: reader = ByteBuffer(data) sign_alg = TpmAlgAsym(reader.unpack("!H")) name_alg = TpmAlgHash(reader.unpack("!H")) attributes = reader.unpack("!L") if attributes & ATTRIBUTES.SHALL_BE_ZERO != 0: raise ValueError(f"attributes is not formated correctly: 0x{attributes:x}") auth_policy = reader.read(reader.unpack("!H")) if sign_alg == TpmAlgAsym.RSA: parameters: _Parameters = TpmsRsaParms.parse(reader, attributes) unique: _Unique = Tpm2bPublicKeyRsa.parse(reader) elif sign_alg == TpmAlgAsym.ECC: parameters = TpmsEccParms.parse(reader) unique = TpmsEccPoint.parse(reader) else: raise NotImplementedError(f"sign alg {sign_alg:x} is not supported") rest = reader.read() if len(rest) != 0: raise ValueError("there should not be any data left in buffer") return cls( sign_alg, name_alg, attributes, auth_policy, parameters, unique, data ) def public_key(self) -> _PublicKey: if self.sign_alg == TpmAlgAsym.RSA: exponent = cast(TpmsRsaParms, self.parameters).exponent modulus = bytes2int(cast(Tpm2bPublicKeyRsa, self.unique)) return rsa.RSAPublicNumbers(exponent, modulus).public_key(default_backend()) elif self.sign_alg == TpmAlgAsym.ECC: unique = cast(TpmsEccPoint, self.unique) return ec.EllipticCurvePublicNumbers( bytes2int(unique.x), bytes2int(unique.y), cast(TpmsEccParms, self.parameters).curve_id.to_curve(), ).public_key(default_backend()) raise NotImplementedError(f"public_key not implemented for {self.sign_alg!r}") def name(self) -> bytes: """ Computing Entity Names see: https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-1-Architecture-01.38.pdf section 16 Names Name ≔ nameAlg || HnameAlg (handle→nvPublicArea) where nameAlg algorithm used to compute Name HnameAlg hash using the nameAlg parameter in the NV Index location associated with handle nvPublicArea contents of the TPMS_NV_PUBLIC associated with handle """ output = struct.pack("!H", self.name_alg) digest = hashes.Hash(self.name_alg._hash_alg(), backend=default_backend()) digest.update(self.data) output += digest.finalize() return output def _validate_tpm_cert(cert): # https://www.w3.org/TR/webauthn/#tpm-cert-requirements _validate_cert_common(cert) s = cert.subject.get_attributes_for_oid(x509.NameOID) if s: raise InvalidData("Certificate should not have Subject") s = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName) if not s: raise InvalidData("Certificate should have SubjectAlternativeName") ext = cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) has_aik = [x == OID_AIK_CERTIFICATE for x in ext.value] if True not in has_aik: raise InvalidData( 'Extended key usage MUST contain the "joint-iso-itu-t(2) ' "internationalorganizations(23) 133 tcg-kp(8) " 'tcg-kp-AIKCertificate(3)" OID.' ) class TpmAttestation(Attestation): FORMAT = "tpm" @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["x5c"] cert_info = statement["certInfo"] cert = x509.load_der_x509_certificate(x5c[0], default_backend()) _validate_tpm_cert(cert) pub_key = CoseKey.for_alg(alg).from_cryptography_key(cert.public_key()) try: pub_area = TpmPublicFormat.parse(statement["pubArea"]) except Exception as e: raise InvalidData("unable to parse pubArea", e) # Verify that the public key specified by the parameters and unique # fields of pubArea is identical to the credentialPublicKey in the # attestedCredentialData in authenticatorData. if ( auth_data.credential_data.public_key.from_cryptography_key( pub_area.public_key() ) != auth_data.credential_data.public_key ): raise InvalidSignature( "attestation pubArea does not match attestedCredentialData" ) try: # TpmAttestationFormat.parse is reponsible for: # Verify that magic is set to TPM_GENERATED_VALUE. # Verify that type is set to TPM_ST_ATTEST_CERTIFY. tpm = TpmAttestationFormat.parse(cert_info) # Verify that extraData is set to the hash of attToBeSigned # using the hash algorithm employed in "alg". att_to_be_signed = auth_data + client_data_hash hash_alg = pub_key._HASH_ALG # type: ignore digest = hashes.Hash(hash_alg, backend=default_backend()) digest.update(att_to_be_signed) data = digest.finalize() if tpm.data != data: raise InvalidSignature( "attestation does not sign for authData and ClientData" ) # Verify that attested contains a TPMS_CERTIFY_INFO structure as # specified in [TPMv2-Part2] section 10.12.3, whose name field # contains a valid Name for pubArea, as computed using the # algorithm in the nameAlg field of pubArea using the procedure # specified in [TPMv2-Part1] section 16. # [TPMv2-Part2]: # https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf # [TPMv2-Part1]: # https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-1-Architecture-01.38.pdf if tpm.attested.name != pub_area.name(): raise InvalidData( "TPMS_CERTIFY_INFO does not include a valid name for pubArea" ) pub_key.verify(cert_info, statement["sig"]) return AttestationResult(AttestationType.ATT_CA, x5c) except _InvalidSignature: raise InvalidSignature("signature of certInfo does not match") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/attestation/u2f.py0000644000000000000000000000534514413232070014057 0ustar00# 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 annotations from .base import ( Attestation, AttestationType, AttestationResult, InvalidSignature, catch_builtins, ) from ..cose import ES256 from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.exceptions import InvalidSignature as _InvalidSignature class FidoU2FAttestation(Attestation): FORMAT = "fido-u2f" @catch_builtins def verify(self, statement, auth_data, client_data_hash): cd = auth_data.credential_data pk = b"\x04" + cd.public_key[-2] + cd.public_key[-3] x5c = statement["x5c"] FidoU2FAttestation.verify_signature( auth_data.rp_id_hash, client_data_hash, cd.credential_id, pk, x5c[0], statement["sig"], ) return AttestationResult(AttestationType.BASIC, x5c) @staticmethod def verify_signature( app_param, client_param, key_handle, public_key, cert_bytes, signature ): m = b"\0" + app_param + client_param + key_handle + public_key cert = x509.load_der_x509_certificate(cert_bytes, default_backend()) try: ES256.from_cryptography_key(cert.public_key()).verify(m, signature) except _InvalidSignature: raise InvalidSignature() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/cbor.py0000644000000000000000000001223314413232070011743 0ustar00# 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. """ Minimal CBOR implementation supporting a subset of functionality and types required for FIDO 2 CTAP. """ from __future__ import annotations import struct from typing import Any, Tuple, Union, Sequence, Mapping, Type, Callable CborType = Union[int, bool, str, bytes, Sequence[Any], Mapping[Any, Any]] def dump_int(data: int, mt: int = 0) -> bytes: if data < 0: mt = 1 data = -1 - data mt = mt << 5 if data <= 23: args: Any = (">B", mt | data) elif data <= 0xFF: args = (">BB", mt | 24, data) elif data <= 0xFFFF: args = (">BH", mt | 25, data) elif data <= 0xFFFFFFFF: args = (">BI", mt | 26, data) else: args = (">BQ", mt | 27, data) return struct.pack(*args) def dump_bool(data: bool) -> bytes: return b"\xf5" if data else b"\xf4" def dump_list(data: Sequence[CborType]) -> bytes: return dump_int(len(data), mt=4) + b"".join([encode(x) for x in data]) def _sort_keys(entry): key = entry[0] return key[0], len(key), key def dump_dict(data: Mapping[CborType, CborType]) -> bytes: items = [(encode(k), encode(v)) for k, v in data.items()] items.sort(key=_sort_keys) return dump_int(len(items), mt=5) + b"".join([k + v for (k, v) in items]) def dump_bytes(data: bytes) -> bytes: return dump_int(len(data), mt=2) + data def dump_text(data: str) -> bytes: data_bytes = data.encode("utf8") return dump_int(len(data_bytes), mt=3) + data_bytes _SERIALIZERS: Sequence[Tuple[Type, Callable[[Any], bytes]]] = [ (bool, dump_bool), (int, dump_int), (str, dump_text), (bytes, dump_bytes), (Mapping, dump_dict), (Sequence, dump_list), ] def encode(data: CborType) -> bytes: for k, v in _SERIALIZERS: if isinstance(data, k): return v(data) raise ValueError(f"Unsupported value: {data!r}") def load_int(ai: int, data: bytes) -> Tuple[int, bytes]: if ai < 24: return ai, data elif ai == 24: return data[0], data[1:] elif ai == 25: return struct.unpack_from(">H", data)[0], data[2:] elif ai == 26: return struct.unpack_from(">I", data)[0], data[4:] elif ai == 27: return struct.unpack_from(">Q", data)[0], data[8:] raise ValueError("Invalid additional information") def load_nint(ai: int, data: bytes) -> Tuple[int, bytes]: val, rest = load_int(ai, data) return -1 - val, rest def load_bool(ai: int, data: bytes) -> Tuple[bool, bytes]: return ai == 21, data def load_bytes(ai: int, data: bytes) -> Tuple[bytes, bytes]: l, data = load_int(ai, data) return data[:l], data[l:] def load_text(ai: int, data: bytes) -> Tuple[str, bytes]: enc, rest = load_bytes(ai, data) return enc.decode("utf8"), rest def load_array(ai: int, data: bytes) -> Tuple[Sequence[CborType], bytes]: l, data = load_int(ai, data) values = [] for i in range(l): val, data = decode_from(data) values.append(val) return values, data def load_map(ai: int, data: bytes) -> Tuple[Mapping[CborType, CborType], bytes]: l, data = load_int(ai, data) values = {} for i in range(l): k, data = decode_from(data) v, data = decode_from(data) values[k] = v return values, data _DESERIALIZERS = { 0: load_int, 1: load_nint, 2: load_bytes, 3: load_text, 4: load_array, 5: load_map, 7: load_bool, } def decode_from(data: bytes) -> Tuple[Any, bytes]: fb = data[0] return _DESERIALIZERS[fb >> 5](fb & 0b11111, data[1:]) def decode(data) -> CborType: value, rest = decode_from(data) if rest != b"": raise ValueError("Extraneous data") return value ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1688371539.8190246 fido2-1.1.2/fido2/client.py0000644000000000000000000010105414450500524012277 0ustar00# 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 annotations from .hid import STATUS from .ctap import CtapDevice, CtapError from .ctap1 import Ctap1, APDU, ApduError from .ctap2 import Ctap2, AssertionResponse, Info from .ctap2.pin import ClientPin, PinProtocol from .ctap2.extensions import Ctap2Extension from .webauthn import ( Aaguid, AttestationObject, CollectedClientData, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, AuthenticatorSelectionCriteria, UserVerificationRequirement, AuthenticatorAttestationResponse, AuthenticatorAssertionResponse, AttestationConveyancePreference, ) from .cose import ES256 from .rpid import verify_rp_id from .utils import sha256 from enum import IntEnum, unique from urllib.parse import urlparse from dataclasses import replace from threading import Timer, Event from typing import ( Type, Any, Callable, Optional, Mapping, Sequence, ) import abc import platform import inspect import logging logger = logging.getLogger(__name__) class ClientError(Exception): @unique class ERR(IntEnum): OTHER_ERROR = 1 BAD_REQUEST = 2 CONFIGURATION_UNSUPPORTED = 3 DEVICE_INELIGIBLE = 4 TIMEOUT = 5 def __call__(self, cause=None): return ClientError(self, cause) def __init__(self, code, cause=None): self.code = ClientError.ERR(code) self.cause = cause def __repr__(self): r = "Client error: {0} - {0.name}".format(self.code) if self.cause: r += f" (cause: {self.cause})" return r def _ctap2client_err(e, err_cls=ClientError): if e.code in [CtapError.ERR.CREDENTIAL_EXCLUDED, CtapError.ERR.NO_CREDENTIALS]: ce = ClientError.ERR.DEVICE_INELIGIBLE elif e.code in [ CtapError.ERR.KEEPALIVE_CANCEL, CtapError.ERR.ACTION_TIMEOUT, CtapError.ERR.USER_ACTION_TIMEOUT, ]: ce = ClientError.ERR.TIMEOUT elif e.code in [ CtapError.ERR.UNSUPPORTED_ALGORITHM, CtapError.ERR.UNSUPPORTED_OPTION, CtapError.ERR.KEY_STORE_FULL, ]: ce = ClientError.ERR.CONFIGURATION_UNSUPPORTED elif e.code in [ CtapError.ERR.INVALID_COMMAND, CtapError.ERR.CBOR_UNEXPECTED_TYPE, CtapError.ERR.INVALID_CBOR, CtapError.ERR.MISSING_PARAMETER, CtapError.ERR.INVALID_OPTION, CtapError.ERR.PUAT_REQUIRED, CtapError.ERR.PIN_INVALID, CtapError.ERR.PIN_BLOCKED, CtapError.ERR.PIN_NOT_SET, CtapError.ERR.PIN_POLICY_VIOLATION, CtapError.ERR.PIN_TOKEN_EXPIRED, CtapError.ERR.PIN_AUTH_INVALID, CtapError.ERR.PIN_AUTH_BLOCKED, CtapError.ERR.REQUEST_TOO_LARGE, CtapError.ERR.OPERATION_DENIED, ]: ce = ClientError.ERR.BAD_REQUEST else: ce = ClientError.ERR.OTHER_ERROR return err_cls(ce, e) class PinRequiredError(ClientError): def __init__( self, code=ClientError.ERR.BAD_REQUEST, cause="PIN required but not provided" ): super().__init__(code, cause) def _call_polling(poll_delay, event, on_keepalive, func, *args, **kwargs): event = event or Event() while not event.is_set(): try: return func(*args, **kwargs) except ApduError as e: if e.code == APDU.USE_NOT_SATISFIED: if on_keepalive: on_keepalive(STATUS.UPNEEDED) on_keepalive = None event.wait(poll_delay) else: raise ClientError.ERR.OTHER_ERROR(e) except CtapError as e: raise _ctap2client_err(e) raise ClientError.ERR.TIMEOUT() class _BaseClient: def __init__(self, origin: str, verify: Callable[[str, str], bool]): self.origin = origin self._verify = verify def _verify_rp_id(self, rp_id): try: if self._verify(rp_id, self.origin): return except Exception: # nosec pass # Fall through to ClientError raise ClientError.ERR.BAD_REQUEST() def _build_client_data(self, typ, challenge): return CollectedClientData.create( type=typ, origin=self.origin, challenge=challenge, ) class AssertionSelection: """GetAssertion result holding one or more assertions. Since multiple assertions may be retured by Fido2Client.get_assertion, this result is returned which can be used to select a specific response to get. """ def __init__( self, client_data: CollectedClientData, assertions: Sequence[AssertionResponse] ): self._client_data = client_data self._assertions = assertions def get_assertions(self) -> Sequence[AssertionResponse]: """Get the raw AssertionResponses available to inspect before selecting one.""" return self._assertions def _get_extension_results( self, assertion: AssertionResponse ) -> Optional[Mapping[str, Any]]: return None # Not implemented def get_response(self, index: int) -> AuthenticatorAssertionResponse: """Get a single response.""" assertion = self._assertions[index] return AuthenticatorAssertionResponse( self._client_data, assertion.auth_data, assertion.signature, assertion.user["id"] if assertion.user else None, assertion.credential["id"] if assertion.credential else None, self._get_extension_results(assertion), ) class WebAuthnClient(abc.ABC): @abc.abstractmethod def make_credential( self, options: PublicKeyCredentialCreationOptions, event: Optional[Event] = None, ) -> AuthenticatorAttestationResponse: """Creates a credential. :param options: PublicKeyCredentialCreationOptions data. :param threading.Event event: (optional) Signal to abort the operation. """ raise NotImplementedError() @abc.abstractmethod def get_assertion( self, options: PublicKeyCredentialRequestOptions, event: Optional[Event] = None, ) -> AssertionSelection: """Get an assertion. :param options: PublicKeyCredentialRequestOptions data. :param threading.Event event: (optional) Signal to abort the operation. """ raise NotImplementedError() def _default_extensions() -> Sequence[Type[Ctap2Extension]]: return [ cls for cls in Ctap2Extension.__subclasses__() if not inspect.isabstract(cls) ] class UserInteraction: """Provides user interaction to the Client. Users of Fido2Client should subclass this to implement asking the user to perform specific actions, such as entering a PIN or touching their""" def prompt_up(self) -> None: """Called when the authenticator is awaiting a user presence check.""" logger.info("User Presence check required.") def request_pin( self, permissions: ClientPin.PERMISSION, rp_id: Optional[str] ) -> Optional[str]: """Called when the client requires a PIN from the user. Should return a PIN, or None/Empty to cancel.""" logger.info("PIN requested, but UserInteraction does not support it.") return None def request_uv( self, permissions: ClientPin.PERMISSION, rp_id: Optional[str] ) -> bool: """Called when the client is about to request UV from the user. Should return True if allowed, or False to cancel.""" logger.info("User Verification requested.") return True def _user_keepalive(user_interaction): def on_keepalive(status): if status == STATUS.UPNEEDED: # Waiting for touch user_interaction.prompt_up() return on_keepalive class _ClientBackend(abc.ABC): info: Info @abc.abstractmethod def selection(self, event: Optional[Event]) -> None: raise NotImplementedError() @abc.abstractmethod def do_make_credential(self, *args) -> AuthenticatorAttestationResponse: raise NotImplementedError() @abc.abstractmethod def do_get_assertion(self, *args) -> AssertionSelection: raise NotImplementedError() class _Ctap1ClientBackend(_ClientBackend): def __init__(self, device: CtapDevice, user_interaction: UserInteraction): self.ctap1 = Ctap1(device) self.info = Info(versions=["U2F_V2"], extensions=[], aaguid=Aaguid.NONE) self._poll_delay = 0.25 self._on_keepalive = _user_keepalive(user_interaction) def selection(self, event): _call_polling( self._poll_delay, event, None, self.ctap1.register, b"\0" * 32, b"\0" * 32, ) def do_make_credential( self, client_data, rp, user, key_params, exclude_list, extensions, rk, user_verification, enterprise_attestation, event, ): if ( rk or user_verification == UserVerificationRequirement.REQUIRED or ES256.ALGORITHM not in [p.alg for p in key_params] or enterprise_attestation ): raise CtapError(CtapError.ERR.UNSUPPORTED_OPTION) app_param = sha256(rp["id"].encode()) dummy_param = b"\0" * 32 for cred in exclude_list or []: key_handle = cred["id"] try: self.ctap1.authenticate(dummy_param, app_param, key_handle, True) raise ClientError.ERR.OTHER_ERROR() # Shouldn't happen except ApduError as e: if e.code == APDU.USE_NOT_SATISFIED: _call_polling( self._poll_delay, event, self._on_keepalive, self.ctap1.register, dummy_param, dummy_param, ) raise ClientError.ERR.DEVICE_INELIGIBLE() att_obj = AttestationObject.from_ctap1( app_param, _call_polling( self._poll_delay, event, self._on_keepalive, self.ctap1.register, client_data.hash, app_param, ), ) return AuthenticatorAttestationResponse( client_data, AttestationObject.create(att_obj.fmt, att_obj.auth_data, att_obj.att_stmt), {}, ) def do_get_assertion( self, client_data, rp_id, allow_list, extensions, user_verification, event, ): if user_verification == UserVerificationRequirement.REQUIRED or not allow_list: raise CtapError(CtapError.ERR.UNSUPPORTED_OPTION) app_param = sha256(rp_id.encode()) client_param = client_data.hash for cred in allow_list: try: auth_resp = _call_polling( self._poll_delay, event, self._on_keepalive, self.ctap1.authenticate, client_param, app_param, cred["id"], ) assertions = [AssertionResponse.from_ctap1(app_param, cred, auth_resp)] return AssertionSelection(client_data, assertions) except ClientError as e: if e.code == ClientError.ERR.TIMEOUT: raise # Other errors are ignored so we move to the next. raise ClientError.ERR.DEVICE_INELIGIBLE() class _Ctap2ClientAssertionSelection(AssertionSelection): def __init__( self, client_data: CollectedClientData, assertions: Sequence[AssertionResponse], extensions: Sequence[Ctap2Extension], pin_token: Optional[str], pin_protocol: Optional[PinProtocol], ): super().__init__(client_data, assertions) self._extensions = extensions self._pin_token = pin_token self._pin_protocol = pin_protocol def _get_extension_results(self, assertion): # Process extenstion outputs extension_outputs = {} try: for ext in self._extensions: output = ext.process_get_output( assertion, self._pin_token, self._pin_protocol ) if output is not None: extension_outputs.update(output) except ValueError as e: raise ClientError.ERR.CONFIGURATION_UNSUPPORTED(e) return extension_outputs class _Ctap2ClientBackend(_ClientBackend): def __init__( self, device: CtapDevice, user_interaction: UserInteraction, extensions: Sequence[Type[Ctap2Extension]], ): self.ctap2 = Ctap2(device) self.info = self.ctap2.info self.extensions = extensions self.user_interaction = user_interaction def selection(self, event): if "FIDO_2_1" in self.info.versions: self.ctap2.selection(event=event) else: # Selection not supported, make dummy credential instead try: self.ctap2.make_credential( b"\0" * 32, {"id": "example.com", "name": "example.com"}, {"id": b"dummy", "name": "dummy"}, [{"type": "public-key", "alg": -7}], pin_uv_param=b"", event=event, ) except CtapError as e: if e.code in ( CtapError.ERR.PIN_NOT_SET, CtapError.ERR.PIN_INVALID, CtapError.ERR.PIN_AUTH_INVALID, ): return raise def _should_use_uv(self, user_verification, mc): uv_supported = any( k in self.info.options for k in ("uv", "clientPin", "bioEnroll") ) uv_configured = any( self.info.options.get(k) for k in ("uv", "clientPin", "bioEnroll") ) if ( user_verification == UserVerificationRequirement.REQUIRED or ( user_verification == UserVerificationRequirement.PREFERRED and uv_supported ) or self.info.options.get("alwaysUv") ): if not uv_configured: raise ClientError.ERR.CONFIGURATION_UNSUPPORTED( "User verification not configured/supported" ) return True elif mc and uv_configured and not self.info.options.get("makeCredUvNotRqd"): return True return False def _get_token( self, client_pin, permissions, rp_id, event, on_keepalive, allow_internal_uv ): # Prefer UV if self.info.options.get("uv"): if ClientPin.is_token_supported(self.info): if self.user_interaction.request_uv(permissions, rp_id): return client_pin.get_uv_token( permissions, rp_id, event, on_keepalive ) elif allow_internal_uv: if self.user_interaction.request_uv(permissions, rp_id): return None # No token, use uv=True # PIN if UV not supported/allowed. if self.info.options.get("clientPin"): pin = self.user_interaction.request_pin(permissions, rp_id) if pin: return client_pin.get_pin_token(pin, permissions, rp_id) raise PinRequiredError() # Client PIN not configured. raise ClientError.ERR.CONFIGURATION_UNSUPPORTED( "User verification not configured" ) def _get_auth_params( self, client_data, rp_id, user_verification, permissions, event, on_keepalive ): mc = client_data.type == CollectedClientData.TYPE.CREATE self.info = self.ctap2.get_info() # Make sure we have "fresh" info pin_protocol = None pin_token = None pin_auth = None internal_uv = False if self._should_use_uv(user_verification, mc) or permissions: client_pin = ClientPin(self.ctap2) allow_internal_uv = not permissions permissions |= ( ClientPin.PERMISSION.MAKE_CREDENTIAL if mc else ClientPin.PERMISSION.GET_ASSERTION ) pin_token = self._get_token( client_pin, permissions, rp_id, event, on_keepalive, allow_internal_uv ) if pin_token: pin_protocol = client_pin.protocol pin_auth = client_pin.protocol.authenticate(pin_token, client_data.hash) else: internal_uv = True return pin_protocol, pin_token, pin_auth, internal_uv def do_make_credential( self, client_data, rp, user, key_params, exclude_list, extensions, rk, user_verification, enterprise_attestation, event, ): if exclude_list: # Filter out credential IDs which are too long max_len = self.info.max_cred_id_length if max_len: exclude_list = [e for e in exclude_list if len(e) <= max_len] # Reject the request if too many credentials remain. max_creds = self.info.max_creds_in_list if max_creds and len(exclude_list) > max_creds: raise ClientError.ERR.BAD_REQUEST("exclude_list too long") # Process extensions client_inputs = extensions or {} extension_inputs = {} used_extensions = [] permissions = ClientPin.PERMISSION(0) try: for ext in [cls(self.ctap2) for cls in self.extensions]: auth_input, req_perms = ext.process_create_input_with_permissions( client_inputs ) if auth_input is not None: used_extensions.append(ext) permissions |= req_perms extension_inputs[ext.NAME] = auth_input except ValueError as e: raise ClientError.ERR.CONFIGURATION_UNSUPPORTED(e) on_keepalive = _user_keepalive(self.user_interaction) # Handle auth pin_protocol, pin_token, pin_auth, internal_uv = self._get_auth_params( client_data, rp["id"], user_verification, permissions, event, on_keepalive ) if not (rk or internal_uv): options = None else: options = {} if rk: options["rk"] = True if internal_uv: options["uv"] = True att_obj = self.ctap2.make_credential( client_data.hash, rp, user, key_params, exclude_list or None, extension_inputs or None, options, pin_auth, pin_protocol.VERSION if pin_protocol else None, enterprise_attestation, event=event, on_keepalive=on_keepalive, ) # Process extenstion outputs extension_outputs = {} try: for ext in used_extensions: output = ext.process_create_output(att_obj, pin_token, pin_protocol) if output is not None: extension_outputs.update(output) except ValueError as e: raise ClientError.ERR.CONFIGURATION_UNSUPPORTED(e) return AuthenticatorAttestationResponse( client_data, AttestationObject.create(att_obj.fmt, att_obj.auth_data, att_obj.att_stmt), extension_outputs, ) def do_get_assertion( self, client_data, rp_id, allow_list, extensions, user_verification, event, ): if allow_list: # Filter out credential IDs which are too long max_len = self.info.max_cred_id_length if max_len: allow_list = [e for e in allow_list if len(e) <= max_len] if not allow_list: raise CtapError(CtapError.ERR.NO_CREDENTIALS) # Reject the request if too many credentials remain. max_creds = self.info.max_creds_in_list if max_creds and len(allow_list) > max_creds: raise ClientError.ERR.BAD_REQUEST("allow_list too long") # Process extensions client_inputs = extensions or {} extension_inputs = {} used_extensions = [] permissions = ClientPin.PERMISSION(0) try: for ext in [cls(self.ctap2) for cls in self.extensions]: auth_input, req_perms = ext.process_get_input_with_permissions( client_inputs ) if auth_input is not None: used_extensions.append(ext) permissions |= req_perms extension_inputs[ext.NAME] = auth_input except ValueError as e: raise ClientError.ERR.CONFIGURATION_UNSUPPORTED(e) on_keepalive = _user_keepalive(self.user_interaction) pin_protocol, pin_token, pin_auth, internal_uv = self._get_auth_params( client_data, rp_id, user_verification, permissions, event, on_keepalive ) options = {"uv": True} if internal_uv else None assertions = self.ctap2.get_assertions( rp_id, client_data.hash, allow_list or None, extension_inputs or None, options, pin_auth, pin_protocol.VERSION if pin_protocol else None, event=event, on_keepalive=on_keepalive, ) return _Ctap2ClientAssertionSelection( client_data, assertions, used_extensions, pin_token, pin_protocol, ) class Fido2Client(WebAuthnClient, _BaseClient): """WebAuthn-like client implementation. The client allows registration and authentication of WebAuthn credentials against an Authenticator using CTAP (1 or 2). :param device: CtapDevice to use. :param str origin: The origin to use. :param verify: Function to verify an RP ID for a given origin. """ def __init__( self, device: CtapDevice, origin: str, verify: Callable[[str, str], bool] = verify_rp_id, extension_types: Sequence[Type[Ctap2Extension]] = _default_extensions(), user_interaction: UserInteraction = UserInteraction(), ): super().__init__(origin, verify) # TODO: Decide how to configure this list. self._enterprise_rpid_list: Optional[Sequence[str]] = None try: self._backend: _ClientBackend = _Ctap2ClientBackend( device, user_interaction, extension_types ) except (ValueError, CtapError): self._backend = _Ctap1ClientBackend(device, user_interaction) @property def info(self) -> Info: return self._backend.info def selection(self, event: Optional[Event] = None) -> None: try: self._backend.selection(event) except CtapError as e: raise _ctap2client_err(e) def make_credential( self, options: PublicKeyCredentialCreationOptions, event: Optional[Event] = None, ) -> AuthenticatorAttestationResponse: """Creates a credential. :param options: PublicKeyCredentialCreationOptions data. :param threading.Event event: (optional) Signal to abort the operation. """ options = PublicKeyCredentialCreationOptions.from_dict(options) event = event or Event() if options.timeout: timer = Timer(options.timeout / 1000, event.set) timer.daemon = True timer.start() rp = options.rp if rp.id is None: url = urlparse(self.origin) if url.scheme != "https" or not url.netloc: raise ClientError.ERR.BAD_REQUEST( "RP ID required for non-https origin." ) rp = replace(rp, id=url.netloc) logger.debug(f"Register a new credential for RP ID: {rp.id}") self._verify_rp_id(rp.id) client_data = self._build_client_data( CollectedClientData.TYPE.CREATE, options.challenge ) selection = options.authenticator_selection or AuthenticatorSelectionCriteria() enterprise_attestation = None if options.attestation == AttestationConveyancePreference.ENTERPRISE: if self.info.options.get("ep"): if self._enterprise_rpid_list is not None: # Platform facilitated if rp.id in self._enterprise_rpid_list: enterprise_attestation = 2 else: # Vendor facilitated enterprise_attestation = 1 try: return self._backend.do_make_credential( client_data, rp, options.user, options.pub_key_cred_params, options.exclude_credentials, options.extensions, selection.require_resident_key, selection.user_verification, enterprise_attestation, event, ) except CtapError as e: raise _ctap2client_err(e) finally: if options.timeout: timer.cancel() def get_assertion( self, options: PublicKeyCredentialRequestOptions, event: Optional[Event] = None, ) -> AssertionSelection: """Get an assertion. :param options: PublicKeyCredentialRequestOptions data. :param threading.Event event: (optional) Signal to abort the operation. """ options = PublicKeyCredentialRequestOptions.from_dict(options) event = event or Event() if options.timeout: timer = Timer(options.timeout / 1000, event.set) timer.daemon = True timer.start() logger.debug(f"Assert a credential for RP ID: {options.rp_id}") self._verify_rp_id(options.rp_id) client_data = self._build_client_data( CollectedClientData.TYPE.GET, options.challenge ) try: return self._backend.do_get_assertion( client_data, options.rp_id, options.allow_credentials, options.extensions, options.user_verification, event, ) except CtapError as e: raise _ctap2client_err(e) finally: if options.timeout: timer.cancel() if platform.system().lower() == "windows": try: from .win_api import ( WinAPI, WebAuthNAuthenticatorAttachment, WebAuthNUserVerificationRequirement, WebAuthNAttestationConvoyancePreference, ) except Exception: # nosec # TODO: Make this less generic pass class WindowsClient(WebAuthnClient, _BaseClient): """Fido2Client-like class using the Windows WebAuthn API. Note: This class only works on Windows 10 19H1 or later. This is also when Windows started restricting access to FIDO devices, causing the standard client classes to require admin priveleges to run (unlike this one). The make_credential and get_assertion methods are intended to work as a drop-in replacement for the Fido2Client methods of the same name. :param str origin: The origin to use. :param verify: Function to verify an RP ID for a given origin. :param ctypes.wintypes.HWND handle: (optional) Window reference to use. """ def __init__( self, origin: str, verify: Callable[[str, str], bool] = verify_rp_id, handle=None, ): super().__init__(origin, verify) self.api = WinAPI(handle) self.info = Info( versions=["U2F_V2", "FIDO_2_0"], extensions=[], aaguid=Aaguid.NONE ) @staticmethod def is_available() -> bool: return platform.system().lower() == "windows" and WinAPI.version > 0 def make_credential(self, options, **kwargs): """Create a credential using Windows WebAuthN APIs. :param options: PublicKeyCredentialCreationOptions data. :param threading.Event event: (optional) Signal to abort the operation. """ options = PublicKeyCredentialCreationOptions.from_dict(options) logger.debug(f"Register a new credential for RP ID: {options.rp.id}") self._verify_rp_id(options.rp.id) client_data = self._build_client_data( CollectedClientData.TYPE.CREATE, options.challenge ) selection = options.authenticator_selection or AuthenticatorSelectionCriteria() try: result = self.api.make_credential( options.rp, options.user, options.pub_key_cred_params, client_data, options.timeout or 0, selection.require_resident_key or False, WebAuthNAuthenticatorAttachment.from_string( selection.authenticator_attachment or "any" ), WebAuthNUserVerificationRequirement.from_string( selection.user_verification or "discouraged" ), WebAuthNAttestationConvoyancePreference.from_string( options.attestation or "none" ), options.exclude_credentials, options.extensions, kwargs.get("event"), ) except OSError as e: raise ClientError.ERR.OTHER_ERROR(e) logger.info("New credential registered") return AuthenticatorAttestationResponse( client_data, AttestationObject(result), {} ) def get_assertion(self, options, **kwargs): """Get assertion using Windows WebAuthN APIs. :param options: PublicKeyCredentialRequestOptions data. :param threading.Event event: (optional) Signal to abort the operation. """ options = PublicKeyCredentialRequestOptions.from_dict(options) logger.debug(f"Assert a credential for RP ID: {options.rp_id}") self._verify_rp_id(options.rp_id) client_data = self._build_client_data( CollectedClientData.TYPE.GET, options.challenge ) try: (credential, auth_data, signature, user_id) = self.api.get_assertion( options.rp_id, client_data, options.timeout or 0, WebAuthNAuthenticatorAttachment.ANY, WebAuthNUserVerificationRequirement.from_string( options.user_verification or "discouraged" ), options.allow_credentials, options.extensions, kwargs.get("event"), ) except OSError as e: raise ClientError.ERR.OTHER_ERROR(e) user = {"id": user_id} if user_id else None return AssertionSelection( client_data, [ AssertionResponse( credential=credential, auth_data=auth_data, signature=signature, user=user, ) ], ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/cose.py0000644000000000000000000002174414413232070011756 0ustar00# 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 annotations from .utils import bytes2int, int2bytes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, rsa, padding, ed25519 from typing import Sequence, Type, Mapping, Any, Union, TypeVar class CoseKey(dict): """A COSE formatted public key. :param _: The COSE key paramters. :cvar ALGORITHM: COSE algorithm identifier. """ ALGORITHM: int = None # type: ignore def verify(self, message: bytes, signature: bytes) -> None: """Validates a digital signature over a given message. :param message: The message which was signed. :param signature: The signature to check. """ raise NotImplementedError("Signature verification not supported.") @classmethod def from_cryptography_key( cls: Type[T_CoseKey], public_key: Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey], ) -> T_CoseKey: """Converts a PublicKey object from Cryptography into a COSE key. :param public_key: Either an EC or RSA public key. :return: A CoseKey. """ raise NotImplementedError("Creation from cryptography not supported.") @staticmethod def for_alg(alg: int) -> Type[CoseKey]: """Get a subclass of CoseKey corresponding to an algorithm identifier. :param alg: The COSE identifier of the algorithm. :return: A CoseKey. """ for cls in CoseKey.__subclasses__(): if cls.ALGORITHM == alg: return cls return UnsupportedKey @staticmethod def for_name(name: str) -> Type[CoseKey]: """Get a subclass of CoseKey corresponding to an algorithm identifier. :param alg: The COSE identifier of the algorithm. :return: A CoseKey. """ for cls in CoseKey.__subclasses__(): if cls.__name__ == name: return cls return UnsupportedKey @staticmethod def parse(cose: Mapping[int, Any]) -> CoseKey: """Create a CoseKey from a dict""" alg = cose.get(3) if not alg: raise ValueError("COSE alg identifier must be provided.") return CoseKey.for_alg(alg)(cose) @staticmethod def supported_algorithms() -> Sequence[int]: """Get a list of all supported algorithm identifiers""" algs: Sequence[Type[CoseKey]] = [ES256, EdDSA, ES384, ES512, PS256, RS256] return [cls.ALGORITHM for cls in algs] T_CoseKey = TypeVar("T_CoseKey", bound=CoseKey) class UnsupportedKey(CoseKey): """A COSE key with an unsupported algorithm.""" class ES256(CoseKey): ALGORITHM = -7 _HASH_ALG = hashes.SHA256() def verify(self, message, signature): if self[-1] != 1: raise ValueError("Unsupported elliptic curve") ec.EllipticCurvePublicNumbers( bytes2int(self[-2]), bytes2int(self[-3]), ec.SECP256R1() ).public_key(default_backend()).verify( signature, message, ec.ECDSA(self._HASH_ALG) ) @classmethod def from_cryptography_key(cls, public_key): pn = public_key.public_numbers() return cls( { 1: 2, 3: cls.ALGORITHM, -1: 1, -2: int2bytes(pn.x, 32), -3: int2bytes(pn.y, 32), } ) @classmethod def from_ctap1(cls, data): """Creates an ES256 key from a CTAP1 formatted public key byte string. :param data: A 65 byte SECP256R1 public key. :return: A ES256 key. """ return cls({1: 2, 3: cls.ALGORITHM, -1: 1, -2: data[1:33], -3: data[33:65]}) class ES384(CoseKey): ALGORITHM = -35 _HASH_ALG = hashes.SHA384() def verify(self, message, signature): if self[-1] != 1: raise ValueError("Unsupported elliptic curve") ec.EllipticCurvePublicNumbers( bytes2int(self[-2]), bytes2int(self[-3]), ec.SECP384R1() ).public_key(default_backend()).verify( signature, message, ec.ECDSA(self._HASH_ALG) ) @classmethod def from_cryptography_key(cls, public_key): pn = public_key.public_numbers() return cls( { 1: 2, 3: cls.ALGORITHM, -1: 1, -2: int2bytes(pn.x, 48), -3: int2bytes(pn.y, 48), } ) class ES512(CoseKey): ALGORITHM = -36 _HASH_ALG = hashes.SHA512() def verify(self, message, signature): if self[-1] != 1: raise ValueError("Unsupported elliptic curve") ec.EllipticCurvePublicNumbers( bytes2int(self[-2]), bytes2int(self[-3]), ec.SECP521R1() ).public_key(default_backend()).verify( signature, message, ec.ECDSA(self._HASH_ALG) ) @classmethod def from_cryptography_key(cls, public_key): pn = public_key.public_numbers() return cls( { 1: 2, 3: cls.ALGORITHM, -1: 1, -2: int2bytes(pn.x, 64), -3: int2bytes(pn.y, 64), } ) class RS256(CoseKey): ALGORITHM = -257 _HASH_ALG = hashes.SHA256() def verify(self, message, signature): rsa.RSAPublicNumbers(bytes2int(self[-2]), bytes2int(self[-1])).public_key( default_backend() ).verify(signature, message, padding.PKCS1v15(), self._HASH_ALG) @classmethod def from_cryptography_key(cls, public_key): pn = public_key.public_numbers() return cls({1: 3, 3: cls.ALGORITHM, -1: int2bytes(pn.n), -2: int2bytes(pn.e)}) class PS256(CoseKey): ALGORITHM = -37 _HASH_ALG = hashes.SHA256() def verify(self, message, signature): rsa.RSAPublicNumbers(bytes2int(self[-2]), bytes2int(self[-1])).public_key( default_backend() ).verify( signature, message, padding.PSS( mgf=padding.MGF1(self._HASH_ALG), salt_length=padding.PSS.MAX_LENGTH ), self._HASH_ALG, ) @classmethod def from_cryptography_key(cls, public_key): pn = public_key.public_numbers() return cls({1: 3, 3: cls.ALGORITHM, -1: int2bytes(pn.n), -2: int2bytes(pn.e)}) class EdDSA(CoseKey): ALGORITHM = -8 def verify(self, message, signature): if self[-1] != 6: raise ValueError("Unsupported elliptic curve") ed25519.Ed25519PublicKey.from_public_bytes(self[-2]).verify(signature, message) @classmethod def from_cryptography_key(cls, public_key): return cls( { 1: 1, 3: cls.ALGORITHM, -1: 6, -2: public_key.public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw ), } ) class RS1(CoseKey): ALGORITHM = -65535 _HASH_ALG = hashes.SHA1() # nosec def verify(self, message, signature): rsa.RSAPublicNumbers(bytes2int(self[-2]), bytes2int(self[-1])).public_key( default_backend() ).verify(signature, message, padding.PKCS1v15(), self._HASH_ALG) @classmethod def from_cryptography_key(cls, public_key): pn = public_key.public_numbers() return cls({1: 3, 3: cls.ALGORITHM, -1: int2bytes(pn.n), -2: int2bytes(pn.e)}) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/ctap.py0000644000000000000000000001303014413232070011741 0ustar00# 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 annotations import abc from enum import IntEnum, unique from threading import Event from typing import Optional, Callable, Iterator @unique class STATUS(IntEnum): PROCESSING = 1 UPNEEDED = 2 class CtapDevice(abc.ABC): """ CTAP-capable device. Subclasses of this should implement call, as well as list_devices, which should return a generator over discoverable devices. """ @property @abc.abstractmethod def capabilities(self) -> int: """Get device capabilities""" @abc.abstractmethod def call( self, cmd: int, data: bytes = b"", event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> bytes: """Sends a command to the authenticator, and reads the response. :param cmd: The integer value of the command. :param data: The payload of the command. :param event: An optional threading.Event which can be used to cancel the invocation. :param on_keepalive: An optional callback to handle keep-alive messages from the authenticator. The function is only called once for consecutive keep-alive messages with the same status. :return: The response from the authenticator. """ def close(self) -> None: """Close the device, releasing any held resources.""" def __enter__(self): return self def __exit__(self, typ, value, traceback): self.close() @classmethod @abc.abstractmethod def list_devices(cls) -> Iterator[CtapDevice]: """Generates instances of cls for discoverable devices.""" class CtapError(Exception): class UNKNOWN_ERR(int): name = "UNKNOWN_ERR" @property def value(self) -> int: return int(self) def __repr__(self): return "" % self def __str__(self): return f"0x{self:02X} - UNKNOWN" @unique class ERR(IntEnum): SUCCESS = 0x00 INVALID_COMMAND = 0x01 INVALID_PARAMETER = 0x02 INVALID_LENGTH = 0x03 INVALID_SEQ = 0x04 TIMEOUT = 0x05 CHANNEL_BUSY = 0x06 LOCK_REQUIRED = 0x0A INVALID_CHANNEL = 0x0B CBOR_UNEXPECTED_TYPE = 0x11 INVALID_CBOR = 0x12 MISSING_PARAMETER = 0x14 LIMIT_EXCEEDED = 0x15 # UNSUPPORTED_EXTENSION = 0x16 # No longer in spec FP_DATABASE_FULL = 0x17 LARGE_BLOB_STORAGE_FULL = 0x18 CREDENTIAL_EXCLUDED = 0x19 PROCESSING = 0x21 INVALID_CREDENTIAL = 0x22 USER_ACTION_PENDING = 0x23 OPERATION_PENDING = 0x24 NO_OPERATIONS = 0x25 UNSUPPORTED_ALGORITHM = 0x26 OPERATION_DENIED = 0x27 KEY_STORE_FULL = 0x28 # NOT_BUSY = 0x29 # No longer in spec # NO_OPERATION_PENDING = 0x2A # No longer in spec UNSUPPORTED_OPTION = 0x2B INVALID_OPTION = 0x2C KEEPALIVE_CANCEL = 0x2D NO_CREDENTIALS = 0x2E USER_ACTION_TIMEOUT = 0x2F NOT_ALLOWED = 0x30 PIN_INVALID = 0x31 PIN_BLOCKED = 0x32 PIN_AUTH_INVALID = 0x33 PIN_AUTH_BLOCKED = 0x34 PIN_NOT_SET = 0x35 PUAT_REQUIRED = 0x36 PIN_POLICY_VIOLATION = 0x37 PIN_TOKEN_EXPIRED = 0x38 REQUEST_TOO_LARGE = 0x39 ACTION_TIMEOUT = 0x3A UP_REQUIRED = 0x3B UV_BLOCKED = 0x3C INTEGRITY_FAILURE = 0x3D INVALID_SUBCOMMAND = 0x3E UV_INVALID = 0x3F UNAUTHORIZED_PERMISSION = 0x40 OTHER = 0x7F SPEC_LAST = 0xDF EXTENSION_FIRST = 0xE0 EXTENSION_LAST = 0xEF VENDOR_FIRST = 0xF0 VENDOR_LAST = 0xFF def __str__(self): return f"0x{self.value:02X} - {self.name}" def __init__(self, code: int): try: self.code = CtapError.ERR(code) except ValueError: self.code = CtapError.UNKNOWN_ERR(code) # type: ignore super().__init__(f"CTAP error: {self.code}") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/ctap1.py0000644000000000000000000002232514413232070012031 0ustar00# Copyright (c) 2013 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 annotations from .hid import CTAPHID from .ctap import CtapDevice from .utils import websafe_encode, websafe_decode, bytes2int, ByteBuffer from .cose import ES256 from .attestation import FidoU2FAttestation from enum import IntEnum, unique from dataclasses import dataclass import struct @unique class APDU(IntEnum): """APDU response codes.""" OK = 0x9000 USE_NOT_SATISFIED = 0x6985 WRONG_DATA = 0x6A80 class ApduError(Exception): """An Exception thrown when a response APDU doesn't have an OK (0x9000) status. :param code: APDU response code. :param data: APDU response body. """ def __init__(self, code: int, data: bytes = b""): self.code = code self.data = data def __repr__(self): return f"APDU error: 0x{self.code:04X} {len(self.data):d} bytes of data" @dataclass(init=False) class RegistrationData(bytes): """Binary response data for a CTAP1 registration. :param _: The binary contents of the response data. :ivar public_key: Binary representation of the credential public key. :ivar key_handle: Binary key handle of the credential. :ivar certificate: Attestation certificate of the authenticator, DER encoded. :ivar signature: Attestation signature. """ public_key: bytes key_handle: bytes certificate: bytes signature: bytes def __init__(self, _): super().__init__() reader = ByteBuffer(self) if reader.unpack("B") != 0x05: raise ValueError("Reserved byte != 0x05") self.public_key = reader.read(65) self.key_handle = reader.read(reader.unpack("B")) cert_buf = reader.read(2) # Tag and first length byte cert_len = cert_buf[1] if cert_len > 0x80: # Multi-byte length n_bytes = cert_len - 0x80 len_bytes = reader.read(n_bytes) cert_buf += len_bytes cert_len = bytes2int(len_bytes) self.certificate = cert_buf + reader.read(cert_len) self.signature = reader.read() @property def b64(self) -> str: """Websafe base64 encoded string of the RegistrationData.""" return websafe_encode(self) def verify(self, app_param: bytes, client_param: bytes) -> None: """Verify the included signature with regard to the given app and client params. :param app_param: SHA256 hash of the app ID used for the request. :param client_param: SHA256 hash of the ClientData used for the request. """ FidoU2FAttestation.verify_signature( app_param, client_param, self.key_handle, self.public_key, self.certificate, self.signature, ) @classmethod def from_b64(cls, data: str) -> RegistrationData: """Parse a RegistrationData from a websafe base64 encoded string. :param data: Websafe base64 encoded string. :return: The decoded and parsed RegistrationData. """ return cls(websafe_decode(data)) @dataclass(init=False) class SignatureData(bytes): """Binary response data for a CTAP1 authentication. :param _: The binary contents of the response data. :ivar user_presence: User presence byte. :ivar counter: Signature counter. :ivar signature: Cryptographic signature. """ user_presence: int counter: int signature: bytes def __init__(self, _): super().__init__() reader = ByteBuffer(self) self.user_presence = reader.unpack("B") self.counter = reader.unpack(">I") self.signature = reader.read() @property def b64(self) -> str: """str: Websafe base64 encoded string of the SignatureData.""" return websafe_encode(self) def verify(self, app_param: bytes, client_param: bytes, public_key: bytes) -> None: """Verify the included signature with regard to the given app and client params, using the given public key. :param app_param: SHA256 hash of the app ID used for the request. :param client_param: SHA256 hash of the ClientData used for the request. :param public_key: Binary representation of the credential public key. """ m = app_param + self[:5] + client_param ES256.from_ctap1(public_key).verify(m, self.signature) @classmethod def from_b64(cls, data: str) -> SignatureData: """Parse a SignatureData from a websafe base64 encoded string. :param data: Websafe base64 encoded string. :return: The decoded and parsed SignatureData. """ return cls(websafe_decode(data)) class Ctap1: """Implementation of the CTAP1 specification. :param device: A CtapHidDevice handle supporting CTAP1. """ @unique class INS(IntEnum): REGISTER = 0x01 AUTHENTICATE = 0x02 VERSION = 0x03 def __init__(self, device: CtapDevice): self.device = device def send_apdu( self, cla: int = 0, ins: int = 0, p1: int = 0, p2: int = 0, data: bytes = b"" ) -> bytes: """Packs and sends an APDU for use in CTAP1 commands. This is a low-level method mainly used internally. Avoid calling it directly if possible, and use the get_version, register, and authenticate methods if possible instead. :param cla: The CLA parameter of the request. :param ins: The INS parameter of the request. :param p1: The P1 parameter of the request. :param p2: The P2 parameter of the request. :param data: The body of the request. :return: The response APDU data of a successful request. :raise: ApduError """ apdu = struct.pack(">BBBBBH", cla, ins, p1, p2, 0, len(data)) + data + b"\0\0" response = self.device.call(CTAPHID.MSG, apdu) status = struct.unpack(">H", response[-2:])[0] data = response[:-2] if status != APDU.OK: raise ApduError(status, data) return data def get_version(self) -> str: """Get the U2F version implemented by the authenticator. The only version specified is "U2F_V2". :return: A U2F version string. """ return self.send_apdu(ins=Ctap1.INS.VERSION).decode() def register(self, client_param: bytes, app_param: bytes) -> RegistrationData: """Register a new U2F credential. :param client_param: SHA256 hash of the ClientData used for the request. :param app_param: SHA256 hash of the app ID used for the request. :return: The registration response from the authenticator. """ data = client_param + app_param response = self.send_apdu(ins=Ctap1.INS.REGISTER, data=data) return RegistrationData(response) def authenticate( self, client_param: bytes, app_param: bytes, key_handle: bytes, check_only: bool = False, ) -> SignatureData: """Authenticate a previously registered credential. :param client_param: SHA256 hash of the ClientData used for the request. :param app_param: SHA256 hash of the app ID used for the request. :param key_handle: The binary key handle of the credential. :param check_only: True to send a "check-only" request, which is used to determine if a key handle is known. :return: The authentication response from the authenticator. """ data = ( client_param + app_param + struct.pack(">B", len(key_handle)) + key_handle ) p1 = 0x07 if check_only else 0x03 response = self.send_apdu(ins=Ctap1.INS.AUTHENTICATE, p1=p1, data=data) return SignatureData(response) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/ctap2/__init__.py0000644000000000000000000000333114413232070013565 0ustar00# 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 .base import ( # noqa Ctap2, Info, AttestationResponse, AssertionResponse, ) from .pin import ClientPin, PinProtocolV1, PinProtocolV2 # noqa from .credman import CredentialManagement # noqa from .bio import FPBioEnrollment, CaptureError # noqa from .blob import LargeBlobs # noqa from .config import Config # noqa ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1688650725.2315347 fido2-1.1.2/fido2/ctap2/base.py0000644000000000000000000005520714451541745012767 0ustar00# 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 annotations from .. import cbor from ..utils import _DataClassMapping from ..ctap import CtapDevice, CtapError from ..cose import CoseKey from ..hid import CTAPHID, CAPABILITY from ..webauthn import AuthenticatorData, Aaguid from enum import IntEnum, unique from dataclasses import dataclass, field, fields, Field from threading import Event from typing import Mapping, Dict, Any, List, Optional, Callable import struct import logging logger = logging.getLogger(__name__) def args(*params) -> Dict[int, Any]: """Constructs a dict from a list of arguments for sending a CBOR command. None elements will be omitted. :param params: Arguments, in order, to add to the command. :return: The input parameters as a dict. """ return dict((i, v) for i, v in enumerate(params, 1) if v is not None) class _CborDataObject(_DataClassMapping[int]): @classmethod def _get_field_key(cls, field: Field) -> int: return fields(cls).index(field) + 1 # type: ignore @dataclass(eq=False, frozen=True) class Info(_CborDataObject): """Binary CBOR encoded response data returned by the CTAP2 GET_INFO command. :param _: The binary content of the Info data. :ivar versions: The versions supported by the authenticator. :ivar extensions: The extensions supported by the authenticator. :ivar aaguid: The AAGUID of the authenticator. :ivar options: The options supported by the authenticator. :ivar max_msg_size: The max message size supported by the authenticator. :ivar pin_uv_protocols: The PIN/UV protocol versions supported by the authenticator. :ivar max_creds_in_list: Max number of credentials supported in list at a time. :ivar max_cred_id_length: Max length of Credential ID supported. :ivar transports: List of supported transports. :ivar algorithms: List of supported algorithms for credential creation. :ivar data: The Info members, in the form of a dict. """ versions: List[str] extensions: List[str] aaguid: Aaguid options: Dict[str, bool] = field(default_factory=dict) max_msg_size: int = 1024 pin_uv_protocols: List[int] = field(default_factory=list) max_creds_in_list: Optional[int] = None max_cred_id_length: Optional[int] = None transports: List[str] = field(default_factory=list) algorithms: Optional[List[Dict[str, Any]]] = None max_large_blob: Optional[int] = None force_pin_change: bool = False min_pin_length: int = 4 firmware_version: Optional[int] = None max_cred_blob_length: Optional[int] = None max_rpids_for_min_pin: int = 0 preferred_platform_uv_attempts: Optional[int] = None uv_modality: Optional[int] = None certifications: Optional[Dict] = None remaining_disc_creds: Optional[int] = None vendor_prototype_config_commands: Optional[List[int]] = None @dataclass(eq=False, frozen=True) class AttestationResponse(_CborDataObject): """Binary CBOR encoded attestation object. :param _: The binary representation of the attestation object. :type _: bytes :ivar fmt: The type of attestation used. :type fmt: str :ivar auth_data: The attested authenticator data. :type auth_data: AuthenticatorData :ivar att_stmt: The attestation statement. :type att_stmt: Dict[str, Any] """ fmt: str auth_data: AuthenticatorData att_stmt: Dict[str, Any] ep_att: Optional[bool] = None large_blob_key: Optional[bytes] = None @dataclass(eq=False, frozen=True) class AssertionResponse(_CborDataObject): """Binary CBOR encoded assertion response. :param _: The binary representation of the assertion response. :ivar credential: The credential used for the assertion. :ivar auth_data: The authenticator data part of the response. :ivar signature: The digital signature of the assertion. :ivar user: The user data of the credential. :ivar number_of_credentials: The total number of responses available (only set for the first response, if > 1). """ credential: Dict[str, Any] auth_data: AuthenticatorData signature: bytes user: Optional[Dict[str, Any]] = None number_of_credentials: Optional[int] = None user_selected: Optional[bool] = None large_blob_key: Optional[bytes] = None def verify(self, client_param: bytes, public_key: CoseKey): """Verify the digital signature of the response with regard to the client_param, using the given public key. :param client_param: SHA256 hash of the ClientData used for the request. :param public_key: The public key of the credential, to verify. """ public_key.verify(self.auth_data + client_param, self.signature) @classmethod def from_ctap1( cls, app_param: bytes, credential: Dict[str, Any], authentication ) -> "AssertionResponse": """Create an AssertionResponse from a CTAP1 SignatureData instance. :param app_param: SHA256 hash of the RP ID used for the CTAP1 request. :param credential: Credential used for the CTAP1 request (from the allowList). :param authentication: The CTAP1 signature data. :return: The assertion response. """ return cls( credential=credential, auth_data=AuthenticatorData.create( app_param, authentication.user_presence & AuthenticatorData.FLAG.UP, authentication.counter, ), signature=authentication.signature, ) class Ctap2: """Implementation of the CTAP2 specification. :param device: A CtapHidDevice handle supporting CTAP2. :param strict_cbor: Validate that CBOR returned from the Authenticator is canonical, defaults to True. """ @unique class CMD(IntEnum): MAKE_CREDENTIAL = 0x01 GET_ASSERTION = 0x02 GET_INFO = 0x04 CLIENT_PIN = 0x06 RESET = 0x07 GET_NEXT_ASSERTION = 0x08 BIO_ENROLLMENT = 0x09 CREDENTIAL_MGMT = 0x0A SELECTION = 0x0B LARGE_BLOBS = 0x0C CONFIG = 0x0D BIO_ENROLLMENT_PRE = 0x40 CREDENTIAL_MGMT_PRE = 0x41 def __init__(self, device: CtapDevice, strict_cbor: bool = True): if not device.capabilities & CAPABILITY.CBOR: raise ValueError("Device does not support CTAP2.") self.device = device self._strict_cbor = strict_cbor self._info = self.get_info() @property def info(self) -> Info: """Get a cached Info object which can be used to determine capabilities. :rtype: Info :return: The response of calling GetAuthenticatorInfo. """ return self._info def send_cbor( self, cmd: int, data: Optional[Mapping[int, Any]] = None, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> Mapping[int, Any]: """Sends a CBOR message to the device, and waits for a response. :param cmd: The command byte of the request. :param data: The payload to send (to be CBOR encoded). :param event: Optional threading.Event used to cancel the request. :param on_keepalive: Optional function called when keep-alive is sent by the authenticator. """ request = struct.pack(">B", cmd) if data is not None: request += cbor.encode(data) response = self.device.call(CTAPHID.CBOR, request, event, on_keepalive) status = response[0] if status != 0x00: raise CtapError(status) enc = response[1:] if not enc: return {} decoded = cbor.decode(enc) if self._strict_cbor: expected = cbor.encode(decoded) if expected != enc: raise ValueError( "Non-canonical CBOR from Authenticator.\n" f"Got: {enc.hex()}\nExpected: {expected.hex()}" ) if isinstance(decoded, Mapping): return decoded raise TypeError("Decoded value of wrong type") def get_info(self) -> Info: """CTAP2 getInfo command. :return: Information about the authenticator. """ return Info.from_dict(self.send_cbor(Ctap2.CMD.GET_INFO)) def client_pin( self, pin_uv_protocol: int, sub_cmd: int, key_agreement: Optional[Mapping[int, Any]] = None, pin_uv_param: Optional[bytes] = None, new_pin_enc: Optional[bytes] = None, pin_hash_enc: Optional[bytes] = None, permissions: Optional[int] = None, permissions_rpid: Optional[str] = None, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> Mapping[int, Any]: """CTAP2 clientPin command, used for various PIN operations. This method is not intended to be called directly. It is intended to be used by an instance of the PinProtocolV1 class. :param pin_uv_protocol: The PIN/UV protocol version to use. :param sub_cmd: A clientPin sub command. :param key_agreement: The keyAgreement parameter. :param pin_uv_param: The pinAuth parameter. :param new_pin_enc: The newPinEnc parameter. :param pin_hash_enc: The pinHashEnc parameter. :param permissions: The permissions parameter. :param permissions_rpid: The permissions RPID parameter. :param event: Optional threading.Event used to cancel the request. :param on_keepalive: Optional callback function to handle keep-alive messages from the authenticator. :return: The response of the command, decoded. """ return self.send_cbor( Ctap2.CMD.CLIENT_PIN, args( pin_uv_protocol, sub_cmd, key_agreement, pin_uv_param, new_pin_enc, pin_hash_enc, None, None, permissions, permissions_rpid, ), event=event, on_keepalive=on_keepalive, ) def reset( self, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> None: """CTAP2 reset command, erases all credentials and PIN. :param event: Optional threading.Event object used to cancel the request. :param on_keepalive: Optional callback function to handle keep-alive messages from the authenticator. """ self.send_cbor(Ctap2.CMD.RESET, event=event, on_keepalive=on_keepalive) logger.info("Reset completed - All data erased") def make_credential( self, client_data_hash: bytes, rp: Mapping[str, Any], user: Mapping[str, Any], key_params: List[Mapping[str, Any]], exclude_list: Optional[List[Mapping[str, Any]]] = None, extensions: Optional[Mapping[str, Any]] = None, options: Optional[Mapping[str, Any]] = None, pin_uv_param: Optional[bytes] = None, pin_uv_protocol: Optional[int] = None, enterprise_attestation: Optional[int] = None, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> AttestationResponse: """CTAP2 makeCredential operation. :param client_data_hash: SHA256 hash of the ClientData. :param rp: PublicKeyCredentialRpEntity parameters. :param user: PublicKeyCredentialUserEntity parameters. :param key_params: List of acceptable credential types. :param exclude_list: Optional list of PublicKeyCredentialDescriptors. :param extensions: Optional dict of extensions. :param options: Optional dict of options. :param pin_uv_param: Optional PIN/UV auth parameter. :param pin_uv_protocol: The version of PIN/UV protocol used, if any. :param event: Optional threading.Event used to cancel the request. :param on_keepalive: Optional callback function to handle keep-alive messages from the authenticator. :return: The new credential. """ logger.debug("Calling CTAP2 make_credential") return AttestationResponse.from_dict( self.send_cbor( Ctap2.CMD.MAKE_CREDENTIAL, args( client_data_hash, rp, user, key_params, exclude_list, extensions, options, pin_uv_param, pin_uv_protocol, enterprise_attestation, ), event=event, on_keepalive=on_keepalive, ) ) def get_assertion( self, rp_id: str, client_data_hash: bytes, allow_list: Optional[List[Mapping[str, Any]]] = None, extensions: Optional[Mapping[str, Any]] = None, options: Optional[Mapping[str, Any]] = None, pin_uv_param: Optional[bytes] = None, pin_uv_protocol: Optional[int] = None, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> AssertionResponse: """CTAP2 getAssertion command. :param rp_id: The RP ID of the credential. :param client_data_hash: SHA256 hash of the ClientData used. :param allow_list: Optional list of PublicKeyCredentialDescriptors. :param extensions: Optional dict of extensions. :param options: Optional dict of options. :param pin_uv_param: Optional PIN/UV auth parameter. :param pin_uv_protocol: The version of PIN/UV protocol used, if any. :param event: Optional threading.Event used to cancel the request. :param on_keepalive: Optional callback function to handle keep-alive messages from the authenticator. :return: The new assertion. """ logger.debug("Calling CTAP2 get_assertion") return AssertionResponse.from_dict( self.send_cbor( Ctap2.CMD.GET_ASSERTION, args( rp_id, client_data_hash, allow_list, extensions, options, pin_uv_param, pin_uv_protocol, ), event=event, on_keepalive=on_keepalive, ) ) def get_next_assertion(self) -> AssertionResponse: """CTAP2 getNextAssertion command. :return: The next available assertion response. """ return AssertionResponse.from_dict(self.send_cbor(Ctap2.CMD.GET_NEXT_ASSERTION)) def get_assertions(self, *args, **kwargs) -> List[AssertionResponse]: """Convenience method to get list of assertions. See get_assertion and get_next_assertion for details. """ first = self.get_assertion(*args, **kwargs) rest = [ self.get_next_assertion() for _ in range(1, first.number_of_credentials or 1) ] return [first] + rest def credential_mgmt( self, sub_cmd: int, sub_cmd_params: Optional[Mapping[int, Any]] = None, pin_uv_protocol: Optional[int] = None, pin_uv_param: Optional[bytes] = None, ) -> Mapping[int, Any]: """CTAP2 credentialManagement command, used to manage resident credentials. NOTE: This implements the current draft version of the CTAP2 specification and should be considered highly experimental. This method is not intended to be called directly. It is intended to be used by an instance of the CredentialManagement class. :param sub_cmd: A CredentialManagement sub command. :param sub_cmd_params: Sub command specific parameters. :param pin_uv_protocol: PIN/UV auth protocol version used. :param pin_uv_param: PIN/UV Auth parameter. """ if "credMgmt" in self.info.options: cmd = Ctap2.CMD.CREDENTIAL_MGMT elif "credentialMgmtPreview" in self.info.options: cmd = Ctap2.CMD.CREDENTIAL_MGMT_PRE else: raise ValueError( "Credential Management not supported by this Authenticator" ) return self.send_cbor( cmd, args(sub_cmd, sub_cmd_params, pin_uv_protocol, pin_uv_param), ) def bio_enrollment( self, modality: Optional[int] = None, sub_cmd: Optional[int] = None, sub_cmd_params: Optional[Mapping[int, Any]] = None, pin_uv_protocol: Optional[int] = None, pin_uv_param: Optional[bytes] = None, get_modality: Optional[bool] = None, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> Mapping[int, Any]: """CTAP2 bio enrollment command. Used to provision/enumerate/delete bio enrollments in the authenticator. NOTE: This implements the current draft version of the CTAP2 specification and should be considered highly experimental. This method is not intended to be called directly. It is intended to be used by an instance of the BioEnrollment class. :param modality: The user verification modality being used. :param sub_cmd: A BioEnrollment sub command. :param sub_cmd_params: Sub command specific parameters. :param pin_uv_protocol: PIN/UV protocol version used. :param pin_uv_param: PIN/UV auth param. :param get_modality: Get the user verification type modality. """ if "bioEnroll" in self.info.options: cmd = Ctap2.CMD.BIO_ENROLLMENT elif "userVerificationMgmtPreview" in self.info.options: cmd = Ctap2.CMD.BIO_ENROLLMENT_PRE else: raise ValueError("Authenticator does not support Bio Enroll") return self.send_cbor( cmd, args( modality, sub_cmd, sub_cmd_params, pin_uv_protocol, pin_uv_param, get_modality, ), event=event, on_keepalive=on_keepalive, ) def selection( self, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> None: """CTAP2 authenticator selection command. This command allows the platform to let a user select a certain authenticator by asking for user presence. :param event: Optional threading.Event used to cancel the request. :param on_keepalive: Optional callback function to handle keep-alive messages from the authenticator. """ self.send_cbor(Ctap2.CMD.SELECTION, event=event, on_keepalive=on_keepalive) def large_blobs( self, offset: int, get: Optional[int] = None, set: Optional[bytes] = None, length: Optional[int] = None, pin_uv_param: Optional[bytes] = None, pin_uv_protocol: Optional[int] = None, ) -> Mapping[int, Any]: """CTAP2 authenticator large blobs command. This command is used to read and write the large blob array. This method is not intended to be called directly. It is intended to be used by an instance of the LargeBlobs class. :param offset: The offset of where to start reading/writing data. :param get: Optional (max) length of data to read. :param set: Optional data to write. :param length: Length of the payload in set. :param pin_uv_protocol: PIN/UV protocol version used. :param pin_uv_param: PIN/UV auth param. """ return self.send_cbor( Ctap2.CMD.LARGE_BLOBS, args(get, set, offset, length, pin_uv_param, pin_uv_protocol), ) def config( self, sub_cmd: int, sub_cmd_params: Optional[Mapping[int, Any]] = None, pin_uv_protocol: Optional[int] = None, pin_uv_param: Optional[bytes] = None, ) -> Mapping[int, Any]: """CTAP2 authenticator config command. This command is used to configure various authenticator features through the use of its subcommands. This method is not intended to be called directly. It is intended to be used by an instance of the Config class. :param sub_cmd: A Config sub command. :param sub_cmd_params: Sub command specific parameters. :param pin_uv_protocol: PIN/UV auth protocol version used. :param pin_uv_param: PIN/UV Auth parameter. """ return self.send_cbor( Ctap2.CMD.CONFIG, args(sub_cmd, sub_cmd_params, pin_uv_protocol, pin_uv_param), ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/ctap2/bio.py0000644000000000000000000003151714413232070012606 0ustar00# 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 annotations from .. import cbor from ..ctap import CtapError from .base import Ctap2, Info from .pin import PinProtocol from enum import IntEnum, unique from threading import Event from typing import Optional, Callable, Mapping, Any, Tuple import struct import logging logger = logging.getLogger(__name__) class BioEnrollment: @unique class RESULT(IntEnum): MODALITY = 0x01 FINGERPRINT_KIND = 0x02 MAX_SAMPLES_REQUIRED = 0x03 TEMPLATE_ID = 0x04 LAST_SAMPLE_STATUS = 0x05 REMAINING_SAMPLES = 0x06 TEMPLATE_INFOS = 0x07 @unique class TEMPLATE_INFO(IntEnum): ID = 0x01 NAME = 0x02 @unique class MODALITY(IntEnum): FINGERPRINT = 0x01 @staticmethod def is_supported(info: Info) -> bool: if "bioEnroll" in info.options: return True # We also support the Prototype command if ( "FIDO_2_1_PRE" in info.versions and "userVerificationMgmtPreview" in info.options ): return True return False def __init__(self, ctap: Ctap2, modality: MODALITY): if not self.is_supported(ctap.info): raise ValueError("Authenticator does not support BioEnroll") self.ctap = ctap self.modality = self.get_modality() if modality != self.modality: raise ValueError(f"Device does not support {modality:s}") def get_modality(self) -> int: """Get bio modality. :return: The type of modality supported by the authenticator. """ return self.ctap.bio_enrollment(get_modality=True)[ BioEnrollment.RESULT.MODALITY ] class CaptureError(Exception): def __init__(self, code: int): self.code = code super().__init__(f"Fingerprint capture error: {code}") class FPEnrollmentContext: """Helper object to perform fingerprint enrollment. :param bio: An instance of FPBioEnrollment. :param timeout: Optional timeout for fingerprint captures (ms). :ivar remaining: The number of (estimated) remaining samples needed. :ivar template_id: The ID of the new template (only available after the initial sample has been captured). """ def __init__(self, bio: "FPBioEnrollment", timeout: Optional[int] = None): self._bio = bio self.timeout = timeout self.template_id: Optional[bytes] = None self.remaining: Optional[int] = None def capture( self, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> Optional[bytes]: """Capture a fingerprint sample. This call will block for up to timeout milliseconds (or indefinitely, if timeout not specified) waiting for the user to scan their fingerprint to collect one sample. :return: None, if more samples are needed, or the template ID if enrollment is completed. """ if self.template_id is None: self.template_id, status, self.remaining = self._bio.enroll_begin( self.timeout, event, on_keepalive ) else: status, self.remaining = self._bio.enroll_capture_next( self.template_id, self.timeout, event, on_keepalive ) if status != FPBioEnrollment.FEEDBACK.FP_GOOD: raise CaptureError(status) if self.remaining == 0: return self.template_id return None def cancel(self) -> None: """Cancels ongoing enrollment.""" self._bio.enroll_cancel() self.template_id = None class FPBioEnrollment(BioEnrollment): """Implementation of a draft specification of the bio enrollment API. WARNING: This specification is not final and this class is likely to change. NOTE: The get_fingerprint_sensor_info method does not require authentication, and can be used by setting pin_uv_protocol and pin_uv_token to None. :param ctap: An instance of a CTAP2 object. :param pin_uv_protocol: The PIN/UV protocol version used. :param pin_uv_token: A valid PIN/UV Auth Token for the current CTAP session. """ @unique class CMD(IntEnum): ENROLL_BEGIN = 0x01 ENROLL_CAPTURE_NEXT = 0x02 ENROLL_CANCEL = 0x03 ENUMERATE_ENROLLMENTS = 0x04 SET_NAME = 0x05 REMOVE_ENROLLMENT = 0x06 GET_SENSOR_INFO = 0x07 @unique class PARAM(IntEnum): TEMPLATE_ID = 0x01 TEMPLATE_NAME = 0x02 TIMEOUT_MS = 0x03 @unique class FEEDBACK(IntEnum): FP_GOOD = 0x00 FP_TOO_HIGH = 0x01 FP_TOO_LOW = 0x02 FP_TOO_LEFT = 0x03 FP_TOO_RIGHT = 0x04 FP_TOO_FAST = 0x05 FP_TOO_SLOW = 0x06 FP_POOR_QUALITY = 0x07 FP_TOO_SKEWED = 0x08 FP_TOO_SHORT = 0x09 FP_MERGE_FAILURE = 0x0A FP_EXISTS = 0x0B FP_DATABASE_FULL = 0x0C NO_USER_ACTIVITY = 0x0D NO_UP_TRANSITION = 0x0E def __str__(self): return f"0x{self.value:02X} - {self.name}" def __init__(self, ctap: Ctap2, pin_uv_protocol: PinProtocol, pin_uv_token: bytes): super().__init__(ctap, BioEnrollment.MODALITY.FINGERPRINT) self.pin_uv_protocol = pin_uv_protocol self.pin_uv_token = pin_uv_token def _call(self, sub_cmd, params=None, auth=True, event=None, on_keepalive=None): if params is not None: params = {k: v for k, v in params.items() if v is not None} kwargs = { "modality": self.modality, "sub_cmd": sub_cmd, "sub_cmd_params": params, "event": event, "on_keepalive": on_keepalive, } if auth: msg = struct.pack(">BB", self.modality, sub_cmd) if params is not None: msg += cbor.encode(params) kwargs["pin_uv_protocol"] = self.pin_uv_protocol.VERSION kwargs["pin_uv_param"] = self.pin_uv_protocol.authenticate( self.pin_uv_token, msg ) return self.ctap.bio_enrollment(**kwargs) def get_fingerprint_sensor_info(self) -> Mapping[int, Any]: """Get fingerprint sensor info. :return: A dict containing FINGERPRINT_KIND and MAX_SAMPLES_REQUIRES. """ return self._call(FPBioEnrollment.CMD.GET_SENSOR_INFO, auth=False) def enroll_begin( self, timeout: Optional[int] = None, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> Tuple[bytes, FPBioEnrollment.FEEDBACK, int]: """Start fingerprint enrollment. Starts the process of enrolling a new fingerprint, and will wait for the user to scan their fingerprint once to provide an initial sample. :param timeout: Optional timeout in milliseconds. :return: A tuple containing the new template ID, the sample status, and the number of samples remaining to complete the enrollment. """ logger.debug(f"Starting fingerprint enrollment (timeout={timeout})") result = self._call( FPBioEnrollment.CMD.ENROLL_BEGIN, {FPBioEnrollment.PARAM.TIMEOUT_MS: timeout}, event=event, on_keepalive=on_keepalive, ) logger.debug(f"Sample capture result: {result}") return ( result[BioEnrollment.RESULT.TEMPLATE_ID], FPBioEnrollment.FEEDBACK(result[BioEnrollment.RESULT.LAST_SAMPLE_STATUS]), result[BioEnrollment.RESULT.REMAINING_SAMPLES], ) def enroll_capture_next( self, template_id: bytes, timeout: Optional[int] = None, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> Tuple[FPBioEnrollment.FEEDBACK, int]: """Continue fingerprint enrollment. Continues enrolling a new fingerprint and will wait for the user to scan their fingerpring once to provide a new sample. Once the number of samples remaining is 0, the enrollment is completed. :param template_id: The template ID returned by a call to `enroll_begin`. :param timeout: Optional timeout in milliseconds. :return: A tuple containing the sample status, and the number of samples remaining to complete the enrollment. """ logger.debug(f"Capturing next sample with (timeout={timeout})") result = self._call( FPBioEnrollment.CMD.ENROLL_CAPTURE_NEXT, { FPBioEnrollment.PARAM.TEMPLATE_ID: template_id, FPBioEnrollment.PARAM.TIMEOUT_MS: timeout, }, event=event, on_keepalive=on_keepalive, ) logger.debug(f"Sample capture result: {result}") return ( FPBioEnrollment.FEEDBACK(result[BioEnrollment.RESULT.LAST_SAMPLE_STATUS]), result[BioEnrollment.RESULT.REMAINING_SAMPLES], ) def enroll_cancel(self) -> None: """Cancel any ongoing fingerprint enrollment.""" logger.debug("Cancelling fingerprint enrollment.") self._call(FPBioEnrollment.CMD.ENROLL_CANCEL, auth=False) def enroll(self, timeout: Optional[int] = None) -> FPEnrollmentContext: """Convenience wrapper for doing fingerprint enrollment. See FPEnrollmentContext for details. :return: An initialized FPEnrollmentContext. """ return FPEnrollmentContext(self, timeout) def enumerate_enrollments(self) -> Mapping[bytes, Optional[str]]: """Get a dict of enrolled fingerprint templates which maps template ID's to their friendly names. :return: A dict of enrolled template_id -> name pairs. """ try: return { t[BioEnrollment.TEMPLATE_INFO.ID]: t[BioEnrollment.TEMPLATE_INFO.NAME] for t in self._call(FPBioEnrollment.CMD.ENUMERATE_ENROLLMENTS)[ BioEnrollment.RESULT.TEMPLATE_INFOS ] } except CtapError as e: if e.code == CtapError.ERR.INVALID_OPTION: return {} raise def set_name(self, template_id: bytes, name: str) -> None: """Set/Change the friendly name of a previously enrolled fingerprint template. :param template_id: The ID of the template to change. :param name: A friendly name to give the template. """ logger.debug(f"Changing name of template: {template_id.hex()} to {name}") self._call( FPBioEnrollment.CMD.SET_NAME, { BioEnrollment.TEMPLATE_INFO.ID: template_id, BioEnrollment.TEMPLATE_INFO.NAME: name, }, ) logger.info("Fingerprint template renamed") def remove_enrollment(self, template_id: bytes) -> None: """Remove a previously enrolled fingerprint template. :param template_id: The Id of the template to remove. """ logger.debug(f"Deleting template: {template_id.hex()}") self._call( FPBioEnrollment.CMD.REMOVE_ENROLLMENT, {BioEnrollment.TEMPLATE_INFO.ID: template_id}, ) logger.info("Fingerprint template deleted") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/ctap2/blob.py0000644000000000000000000001666514413232070012762 0ustar00# 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 annotations from .. import cbor from ..utils import sha256 from .base import Ctap2, Info from .pin import PinProtocol, _PinUv from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.exceptions import InvalidTag from typing import Optional, Any, Sequence, Mapping, cast import struct import zlib import os def _compress(data): o = zlib.compressobj(wbits=-zlib.MAX_WBITS) return o.compress(data) + o.flush() def _decompress(data): o = zlib.decompressobj(wbits=-zlib.MAX_WBITS) return o.decompress(data) + o.flush() def _lb_ad(orig_size): return b"blob" + struct.pack(" bool: return info.options.get("largeBlobs") is True def __init__( self, ctap: Ctap2, pin_uv_protocol: Optional[PinProtocol] = None, pin_uv_token: Optional[bytes] = None, ): if not self.is_supported(ctap.info): raise ValueError("Authenticator does not support LargeBlobs") self.ctap = ctap self.max_fragment_length = self.ctap.info.max_msg_size - 64 self.pin_uv = ( _PinUv(pin_uv_protocol, pin_uv_token) if pin_uv_protocol and pin_uv_token else None ) def read_blob_array(self) -> Sequence[Mapping[int, Any]]: """Gets the entire contents of the Large Blobs array. :return: The CBOR decoded list of Large Blobs. """ offset = 0 buf = b"" while True: fragment = self.ctap.large_blobs(offset, get=self.max_fragment_length)[1] buf += fragment if len(fragment) < self.max_fragment_length: break offset += self.max_fragment_length data, check = buf[:-16], buf[-16:] if check != sha256(data)[:-16]: return [] return cast(Sequence[Mapping[int, Any]], cbor.decode(data)) def write_blob_array(self, blob_array: Sequence[Mapping[int, Any]]) -> None: """Writes the entire Large Blobs array. :param blob_array: A list to write to the Authenticator. """ if not isinstance(blob_array, list): raise TypeError("large-blob array must be a list") data = cbor.encode(blob_array) data += sha256(data)[:16] offset = 0 size = len(data) while offset < size: ln = min(size - offset, self.max_fragment_length) _set = data[offset : offset + ln] if self.pin_uv: msg = ( b"\xff" * 32 + b"\x0c\x00" + struct.pack(" Optional[bytes]: """Gets the Large Blob stored for a single credential. :param large_blob_key: The largeBlobKey for the credential, or None. :returns: The decrypted and deflated value stored for the credential. """ for entry in self.read_blob_array(): try: compressed, orig_size = _lb_unpack(large_blob_key, entry) decompressed = _decompress(compressed) if len(decompressed) == orig_size: return decompressed except (ValueError, zlib.error): continue return None def put_blob(self, large_blob_key: bytes, data: Optional[bytes]) -> None: """Stores a Large Blob for a single credential. Any existing entries for the same credential will be replaced. :param large_blob_key: The largeBlobKey for the credential. :param data: The data to compress, encrypt and store. """ modified = data is not None entries = [] for entry in self.read_blob_array(): try: _lb_unpack(large_blob_key, entry) modified = True except ValueError: entries.append(entry) if data is not None: entries.append(_lb_pack(large_blob_key, data)) if modified: self.write_blob_array(entries) def delete_blob(self, large_blob_key: bytes) -> None: """Deletes any Large Blob(s) stored for a single credential. :param large_blob_key: The largeBlobKey for the credential. """ self.put_blob(large_blob_key, None) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/ctap2/config.py0000644000000000000000000001145314413232070013277 0ustar00# 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 annotations from .. import cbor from .base import Ctap2, Info from .pin import PinProtocol, _PinUv from typing import Optional, List from enum import IntEnum, unique import struct class Config: """Implementation of the CTAP2.1 Authenticator Config API. :param ctap: An instance of a CTAP2 object. :param pin_uv_protocol: An instance of a PinUvAuthProtocol. :param pin_uv_token: A valid PIN/UV Auth Token for the current CTAP session. """ @unique class CMD(IntEnum): ENABLE_ENTERPRISE_ATT = 0x01 TOGGLE_ALWAYS_UV = 0x02 SET_MIN_PIN_LENGTH = 0x03 VENDOR_PROTOTYPE = 0xFF @unique class PARAM(IntEnum): NEW_MIN_PIN_LENGTH = 0x01 MIN_PIN_LENGTH_RPIDS = 0x02 FORCE_CHANGE_PIN = 0x03 @staticmethod def is_supported(info: Info) -> bool: return info.options.get("authnrCfg") is True def __init__( self, ctap: Ctap2, pin_uv_protocol: Optional[PinProtocol] = None, pin_uv_token: Optional[bytes] = None, ): if not self.is_supported(ctap.info): raise ValueError("Authenticator does not support Config") self.ctap = ctap self.pin_uv = ( _PinUv(pin_uv_protocol, pin_uv_token) if pin_uv_protocol and pin_uv_token else None ) def _call(self, sub_cmd, params=None): if params: params = {k: v for k, v in params.items() if v is not None} else: params = None if self.pin_uv: msg = ( b"\xff" * 32 + b"\x0d" + struct.pack(" None: """Enables Enterprise Attestation. If already enabled, this command is ignored. """ self._call(Config.CMD.ENABLE_ENTERPRISE_ATT) def toggle_always_uv(self) -> None: """Toggle the alwaysUV setting. When true, the Authenticator always requires UV for credential assertion. """ self._call(Config.CMD.TOGGLE_ALWAYS_UV) def set_min_pin_length( self, min_pin_length: Optional[int] = None, rp_ids: Optional[List[str]] = None, force_change_pin: bool = False, ) -> None: """Set the minimum PIN length allowed when setting/changing the PIN. :param min_pin_length: The minimum PIN length the Authenticator should allow. :param rp_ids: A list of RP IDs which should be allowed to get the current minimum PIN length. :param force_change_pin: True if the Authenticator should enforce changing the PIN before the next use. """ self._call( Config.CMD.SET_MIN_PIN_LENGTH, { Config.PARAM.NEW_MIN_PIN_LENGTH: min_pin_length, Config.PARAM.MIN_PIN_LENGTH_RPIDS: rp_ids, Config.PARAM.FORCE_CHANGE_PIN: force_change_pin, }, ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/ctap2/credman.py0000644000000000000000000002160114413232070013437 0ustar00# 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 annotations from .. import cbor from ..ctap import CtapError from ..webauthn import PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity from .base import Ctap2, Info from .pin import PinProtocol, _PinUv from enum import IntEnum, unique from typing import Mapping, Sequence, Any import struct import logging logger = logging.getLogger(__name__) class CredentialManagement: """Implementation of a draft specification of the Credential Management API. WARNING: This specification is not final and this class is likely to change. :param ctap: An instance of a CTAP2 object. :param pin_uv_protocol: An instance of a PinUvAuthProtocol. :param pin_uv_token: A valid PIN/UV Auth Token for the current CTAP session. """ @unique class CMD(IntEnum): GET_CREDS_METADATA = 0x01 ENUMERATE_RPS_BEGIN = 0x02 ENUMERATE_RPS_NEXT = 0x03 ENUMERATE_CREDS_BEGIN = 0x04 ENUMERATE_CREDS_NEXT = 0x05 DELETE_CREDENTIAL = 0x06 UPDATE_USER_INFO = 0x07 @unique class PARAM(IntEnum): RP_ID_HASH = 0x01 CREDENTIAL_ID = 0x02 USER = 0x03 @unique class RESULT(IntEnum): EXISTING_CRED_COUNT = 0x01 MAX_REMAINING_COUNT = 0x02 RP = 0x03 RP_ID_HASH = 0x04 TOTAL_RPS = 0x05 USER = 0x06 CREDENTIAL_ID = 0x07 PUBLIC_KEY = 0x08 TOTAL_CREDENTIALS = 0x09 CRED_PROTECT = 0x0A LARGE_BLOB_KEY = 0x0B @staticmethod def is_supported(info: Info) -> bool: if info.options.get("credMgmt"): return True # We also support the Prototype command if "FIDO_2_1_PRE" in info.versions and "credentialMgmtPreview" in info.options: return True return False def __init__( self, ctap: Ctap2, pin_uv_protocol: PinProtocol, pin_uv_token: bytes, ): if not self.is_supported(ctap.info): raise ValueError("Authenticator does not support Credential Management") self.ctap = ctap self.pin_uv = _PinUv(pin_uv_protocol, pin_uv_token) def _call(self, sub_cmd, params=None, auth=True): kwargs = {"sub_cmd": sub_cmd, "sub_cmd_params": params} if auth: msg = struct.pack(">B", sub_cmd) if params is not None: msg += cbor.encode(params) kwargs["pin_uv_protocol"] = self.pin_uv.protocol.VERSION kwargs["pin_uv_param"] = self.pin_uv.protocol.authenticate( self.pin_uv.token, msg ) return self.ctap.credential_mgmt(**kwargs) def get_metadata(self) -> Mapping[int, Any]: """Get credentials metadata. This returns the existing resident credentials count, and the max possible number of remaining resident credentials (the actual number of remaining credentials may depend on algorithm choice, etc). :return: A dict containing EXISTING_CRED_COUNT, and MAX_REMAINING_COUNT. """ return self._call(CredentialManagement.CMD.GET_CREDS_METADATA) def enumerate_rps_begin(self) -> Mapping[int, Any]: """Start enumeration of RP entities of resident credentials. This will begin enumeration of stored RP entities, returning the first entity, as well as a count of the total number of entities stored. :return: A dict containing RP, RP_ID_HASH, and TOTAL_RPS. """ return self._call(CredentialManagement.CMD.ENUMERATE_RPS_BEGIN) def enumerate_rps_next(self) -> Mapping[int, Any]: """Get the next RP entity stored. This continues enumeration of stored RP entities, returning the next entity. :return: A dict containing RP, and RP_ID_HASH. """ return self._call(CredentialManagement.CMD.ENUMERATE_RPS_NEXT, auth=False) def enumerate_rps(self) -> Sequence[Mapping[int, Any]]: """Convenience method to enumerate all RPs. See enumerate_rps_begin and enumerate_rps_next for details. """ try: first = self.enumerate_rps_begin() except CtapError as e: if e.code == CtapError.ERR.NO_CREDENTIALS: return [] raise # Other error n_rps = first[CredentialManagement.RESULT.TOTAL_RPS] if n_rps == 0: return [] rest = [self.enumerate_rps_next() for _ in range(1, n_rps)] return [first] + rest def enumerate_creds_begin(self, rp_id_hash: bytes) -> Mapping[int, Any]: """Start enumeration of resident credentials. This will begin enumeration of resident credentials for a given RP, returning the first credential, as well as a count of the total number of resident credentials stored for the given RP. :param rp_id_hash: SHA256 hash of the RP ID. :return: A dict containing USER, CREDENTIAL_ID, PUBLIC_KEY, and TOTAL_CREDENTIALS. """ return self._call( CredentialManagement.CMD.ENUMERATE_CREDS_BEGIN, {CredentialManagement.PARAM.RP_ID_HASH: rp_id_hash}, ) def enumerate_creds_next(self) -> Mapping[int, Any]: """Get the next resident credential stored. This continues enumeration of resident credentials, returning the next credential. :return: A dict containing USER, CREDENTIAL_ID, and PUBLIC_KEY. """ return self._call(CredentialManagement.CMD.ENUMERATE_CREDS_NEXT, auth=False) def enumerate_creds(self, *args, **kwargs) -> Sequence[Mapping[int, Any]]: """Convenience method to enumerate all resident credentials for an RP. See enumerate_creds_begin and enumerate_creds_next for details. """ try: first = self.enumerate_creds_begin(*args, **kwargs) except CtapError as e: if e.code == CtapError.ERR.NO_CREDENTIALS: return [] raise # Other error rest = [ self.enumerate_creds_next() for _ in range( 1, first.get(CredentialManagement.RESULT.TOTAL_CREDENTIALS, 1) ) ] return [first] + rest def delete_cred(self, cred_id: PublicKeyCredentialDescriptor) -> None: """Delete a resident credential. :param cred_id: The PublicKeyCredentialDescriptor of the credential to delete. """ logger.debug(f"Deleting credential with ID: {cred_id['id'].hex()}") self._call( CredentialManagement.CMD.DELETE_CREDENTIAL, {CredentialManagement.PARAM.CREDENTIAL_ID: cred_id}, ) logger.info("Credential deleted") def update_user_info( self, cred_id: PublicKeyCredentialDescriptor, user_info: PublicKeyCredentialUserEntity, ) -> None: """Update the user entity of a resident key. :param cred_id: The PublicKeyCredentialDescriptor of the credential to update. :param user_info: The user info update. """ logger.debug(f"Updating credential: {cred_id} with user info: {user_info}") self._call( CredentialManagement.CMD.UPDATE_USER_INFO, { CredentialManagement.PARAM.CREDENTIAL_ID: cred_id, CredentialManagement.PARAM.USER: user_info, }, ) logger.info("Credential user info updated") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/ctap2/extensions.py0000644000000000000000000002261114413232070014227 0ustar00# 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 annotations from .base import AttestationResponse, AssertionResponse, Ctap2 from .pin import ClientPin, PinProtocol from .blob import LargeBlobs from enum import Enum, unique from typing import Dict, Tuple, Any, Optional import abc class Ctap2Extension(abc.ABC): """Base class for Ctap2 extensions. Subclasses are instantiated for a single request, if the Authenticator supports the extension. """ NAME: str = None # type: ignore def __init__(self, ctap: Ctap2): self.ctap = ctap def is_supported(self) -> bool: """Whether or not the extension is supported by the authenticator.""" return self.NAME in self.ctap.info.extensions def process_create_input(self, inputs: Dict[str, Any]) -> Any: """Returns a value to include in the authenticator extension input, or None. """ return None def process_create_input_with_permissions( self, inputs: Dict[str, Any] ) -> Tuple[Any, ClientPin.PERMISSION]: return self.process_create_input(inputs), ClientPin.PERMISSION(0) def process_create_output( self, attestation_response: AttestationResponse, token: Optional[str], pin_protocol: Optional[PinProtocol], ) -> Optional[Dict[str, Any]]: """Return client extension output given attestation_response, or None.""" return None def process_get_input(self, inputs: Dict[str, Any]) -> Any: """Returns a value to include in the authenticator extension input, or None. """ return None def process_get_input_with_permissions( self, inputs: Dict[str, Any] ) -> Tuple[Any, ClientPin.PERMISSION]: return self.process_get_input(inputs), ClientPin.PERMISSION(0) def process_get_output( self, assertion_response: AssertionResponse, token: Optional[str], pin_protocol: Optional[PinProtocol], ) -> Optional[Dict[str, Any]]: """Return client extension output given assertion_response, or None.""" return None class HmacSecretExtension(Ctap2Extension): """ Implements the hmac-secret CTAP2 extension. """ NAME = "hmac-secret" SALT_LEN = 32 def __init__(self, ctap, pin_protocol=None): super().__init__(ctap) self.pin_protocol = pin_protocol def process_create_input(self, inputs): if self.is_supported() and inputs.get("hmacCreateSecret") is True: return True def process_create_output(self, attestation_response, *args): if attestation_response.auth_data.extensions.get(self.NAME): return {"hmacCreateSecret": True} def process_get_input(self, inputs): data = self.is_supported() and inputs.get("hmacGetSecret") if not data: return salt1 = data["salt1"] salt2 = data.get("salt2", b"") if not ( len(salt1) == HmacSecretExtension.SALT_LEN and (not salt2 or len(salt2) == HmacSecretExtension.SALT_LEN) ): raise ValueError("Invalid salt length") client_pin = ClientPin(self.ctap, self.pin_protocol) key_agreement, self.shared_secret = client_pin._get_shared_secret() if self.pin_protocol is None: self.pin_protocol = client_pin.protocol salt_enc = self.pin_protocol.encrypt(self.shared_secret, salt1 + salt2) salt_auth = self.pin_protocol.authenticate(self.shared_secret, salt_enc) return { 1: key_agreement, 2: salt_enc, 3: salt_auth, 4: self.pin_protocol.VERSION, } def process_get_output(self, assertion_response, *args): value = assertion_response.auth_data.extensions.get(self.NAME) decrypted = self.pin_protocol.decrypt(self.shared_secret, value) output1 = decrypted[: HmacSecretExtension.SALT_LEN] output2 = decrypted[HmacSecretExtension.SALT_LEN :] outputs = {"output1": output1} if output2: outputs["output2"] = output2 return {"hmacGetSecret": outputs} class LargeBlobExtension(Ctap2Extension): """ Implements the Large Blob WebAuthn extension. """ NAME = "largeBlobKey" def is_supported(self): return super().is_supported() and self.ctap.info.options.get("largeBlobs") def process_create_input(self, inputs): data = inputs.get("largeBlob", {}) if data: if "read" in data or "write" in data: raise ValueError("Invalid set of parameters") is_supported = self.is_supported() if data.get("support") == "required" and not is_supported: raise ValueError("Authenticator does not support large blob storage") return True def process_create_output(self, attestation_response, *args): return {"supported": attestation_response.large_blob_key is not None} def process_get_input_with_permissions(self, inputs): data = inputs.get("largeBlob", {}) permissions = ClientPin.PERMISSION(0) if data: if "support" in data or ("read" in data and "write" in data): raise ValueError("Invalid set of parameters") if not self.is_supported(): raise ValueError("Authenticator does not support large blob storage") if data.get("read") is True: self._action = True else: self._action = data.get("write") permissions = ClientPin.PERMISSION.LARGE_BLOB_WRITE return True if data else None, permissions def process_get_output(self, assertion_response, token, pin_protocol): blob_key = assertion_response.large_blob_key if self._action is True: # Read large_blobs = LargeBlobs(self.ctap) blob = large_blobs.get_blob(blob_key) return {"blob": blob} elif self._action: # Write large_blobs = LargeBlobs(self.ctap, pin_protocol, token) large_blobs.put_blob(blob_key, self._action) return {"written": True} class CredBlobExtension(Ctap2Extension): """ Implements the Credential Blob CTAP2 extension. """ NAME = "credBlob" def process_create_input(self, inputs): if self.is_supported(): blob = inputs.get("credBlob") assert self.ctap.info.max_cred_blob_length is not None # nosec if blob and len(blob) <= self.ctap.info.max_cred_blob_length: return blob def process_get_input(self, inputs): if self.is_supported() and inputs.get("getCredBlob") is True: return True class CredProtectExtension(Ctap2Extension): """ Implements the Credential Protection CTAP2 extension. """ @unique class POLICY(Enum): OPTIONAL = "userVerificationOptional" OPTIONAL_WITH_LIST = "userVerificationOptionalWithCredentialIDList" REQUIRED = "userVerificationRequired" ALWAYS_RUN = True NAME = "credProtect" def process_create_input(self, inputs): policy = inputs.get("credentialProtectionPolicy") if policy: index = list(CredProtectExtension.POLICY).index(policy) enforce = inputs.get("enforceCredentialProtectionPolicy", False) if enforce and not self.is_supported() and index > 0: raise ValueError("Authenticator does not support Credential Protection") return index + 1 class MinPinLengthExtension(Ctap2Extension): """ Implements the Minimum PIN Length CTAP2 extension. """ NAME = "minPinLength" def is_supported(self): # NB: There is no key in the extensions field. return "setMinPINLength" in self.ctap.info.options def process_create_input(self, inputs): if self.is_supported() and inputs.get(self.NAME) is True: return True ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1688371539.8190246 fido2-1.1.2/fido2/ctap2/pin.py0000644000000000000000000003617114450500524012627 0ustar00# 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 annotations from ..utils import sha256, hmac_sha256, bytes2int, int2bytes from ..cose import CoseKey from .base import Ctap2 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.kdf.hkdf import HKDF from enum import IntEnum, IntFlag, unique from dataclasses import dataclass from threading import Event from typing import Optional, Any, Mapping, ClassVar, Tuple, Callable import abc import os import logging logger = logging.getLogger(__name__) def _pad_pin(pin: str) -> bytes: if not isinstance(pin, str): raise ValueError(f"PIN of wrong type, expecting {str}") if len(pin) < 4: raise ValueError("PIN must be >= 4 characters") pin_padded = pin.encode().ljust(64, b"\0") pin_padded += b"\0" * (-(len(pin_padded) - 16) % 16) if len(pin_padded) > 255: raise ValueError("PIN must be <= 255 bytes") return pin_padded class PinProtocol(abc.ABC): VERSION: ClassVar[int] @abc.abstractmethod def encapsulate(self, peer_cose_key: CoseKey) -> Tuple[Mapping[int, Any], bytes]: """Generates an encapsulation of the public key. Returns the message to transmit and the shared secret. """ @abc.abstractmethod def encrypt(self, key: bytes, plaintext: bytes) -> bytes: """Encrypts data""" @abc.abstractmethod def decrypt(self, key: bytes, ciphertext: bytes) -> bytes: """Decrypts encrypted data""" @abc.abstractmethod def authenticate(self, key: bytes, message: bytes) -> bytes: """Computes a MAC of the given message.""" @abc.abstractmethod def validate_token(self, token: bytes) -> bytes: """Validates that a token is well-formed. Returns the token, or if invalid, raises a ValueError. """ @dataclass class _PinUv: protocol: PinProtocol token: bytes class PinProtocolV1(PinProtocol): """Implementation of the CTAP2 PIN/UV protocol v1. :param ctap: An instance of a CTAP2 object. :cvar VERSION: The version number of the PIV/UV protocol. :cvar IV: An all-zero IV used for some cryptographic operations. """ VERSION = 1 IV = b"\x00" * 16 def kdf(self, z: bytes) -> bytes: return sha256(z) def encapsulate(self, peer_cose_key): be = default_backend() sk = ec.generate_private_key(ec.SECP256R1(), be) pn = sk.public_key().public_numbers() key_agreement = { 1: 2, 3: -25, # Per the spec, "although this is NOT the algorithm actually used" -1: 1, -2: int2bytes(pn.x, 32), -3: int2bytes(pn.y, 32), } x = bytes2int(peer_cose_key[-2]) y = bytes2int(peer_cose_key[-3]) pk = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()).public_key(be) shared_secret = self.kdf(sk.exchange(ec.ECDH(), pk)) # x-coordinate, 32b return key_agreement, shared_secret def _get_cipher(self, secret): be = default_backend() return Cipher(algorithms.AES(secret), modes.CBC(PinProtocolV1.IV), be) def encrypt(self, key, plaintext): cipher = self._get_cipher(key) enc = cipher.encryptor() return enc.update(plaintext) + enc.finalize() def decrypt(self, key, ciphertext): cipher = self._get_cipher(key) dec = cipher.decryptor() return dec.update(ciphertext) + dec.finalize() def authenticate(self, key, message): return hmac_sha256(key, message)[:16] def validate_token(self, token): if len(token) not in (16, 32): raise ValueError("PIN/UV token must be 16 or 32 bytes") return token class PinProtocolV2(PinProtocolV1): """Implementation of the CTAP2 PIN/UV protocol v2. :param ctap: An instance of a CTAP2 object. :cvar VERSION: The version number of the PIV/UV protocol. :cvar IV: An all-zero IV used for some cryptographic operations. """ VERSION = 2 HKDF_SALT = b"\x00" * 32 HKDF_INFO_HMAC = b"CTAP2 HMAC key" HKDF_INFO_AES = b"CTAP2 AES key" def kdf(self, z): be = default_backend() hmac_key = HKDF( algorithm=hashes.SHA256(), length=32, salt=PinProtocolV2.HKDF_SALT, info=PinProtocolV2.HKDF_INFO_HMAC, backend=be, ).derive(z) aes_key = HKDF( algorithm=hashes.SHA256(), length=32, salt=PinProtocolV2.HKDF_SALT, info=PinProtocolV2.HKDF_INFO_AES, backend=be, ).derive(z) return hmac_key + aes_key def _get_cipher(self, secret, iv): be = default_backend() return Cipher(algorithms.AES(secret), modes.CBC(iv), be) def encrypt(self, key, plaintext): aes_key = key[32:] iv = os.urandom(16) cipher = self._get_cipher(aes_key, iv) enc = cipher.encryptor() return iv + enc.update(plaintext) + enc.finalize() def decrypt(self, key, ciphertext): aes_key = key[32:] iv, ciphertext = ciphertext[:16], ciphertext[16:] cipher = self._get_cipher(aes_key, iv) dec = cipher.decryptor() return dec.update(ciphertext) + dec.finalize() def authenticate(self, key, message): hmac_key = key[:32] return hmac_sha256(hmac_key, message) def validate_token(self, token): if len(token) != 32: raise ValueError("PIN/UV token must be 32 bytes") return token class ClientPin: """Implementation of the CTAP2 Client PIN API. :param ctap: An instance of a CTAP2 object. :param protocol: An optional instance of a PinUvAuthProtocol object. If None is provided then the latest protocol supported by both library and Authenticator will be used. """ PROTOCOLS = [PinProtocolV2, PinProtocolV1] @unique class CMD(IntEnum): GET_PIN_RETRIES = 0x01 GET_KEY_AGREEMENT = 0x02 SET_PIN = 0x03 CHANGE_PIN = 0x04 GET_TOKEN_USING_PIN_LEGACY = 0x05 GET_TOKEN_USING_UV = 0x06 GET_UV_RETRIES = 0x07 GET_TOKEN_USING_PIN = 0x09 @unique class RESULT(IntEnum): KEY_AGREEMENT = 0x01 PIN_UV_TOKEN = 0x02 PIN_RETRIES = 0x03 POWER_CYCLE_STATE = 0x04 UV_RETRIES = 0x05 @unique class PERMISSION(IntFlag): MAKE_CREDENTIAL = 0x01 GET_ASSERTION = 0x02 CREDENTIAL_MGMT = 0x04 BIO_ENROLL = 0x08 LARGE_BLOB_WRITE = 0x10 AUTHENTICATOR_CFG = 0x20 @staticmethod def is_supported(info): """Checks if ClientPin functionality is supported. Note that the ClientPin function is still usable without support for client PIN functionality, as UV token may still be supported. """ return "clientPin" in info.options @staticmethod def is_token_supported(info): """Checks if pinUvAuthToken is supported.""" return info.options.get("pinUvAuthToken") is True def __init__(self, ctap: Ctap2, protocol: Optional[PinProtocol] = None): self.ctap = ctap if protocol is None: for proto in ClientPin.PROTOCOLS: if proto.VERSION in ctap.info.pin_uv_protocols: self.protocol: PinProtocol = proto() break else: raise ValueError("No compatible PIN/UV protocols supported!") else: self.protocol = protocol def _get_shared_secret(self): resp = self.ctap.client_pin( self.protocol.VERSION, ClientPin.CMD.GET_KEY_AGREEMENT ) pk = resp[ClientPin.RESULT.KEY_AGREEMENT] return self.protocol.encapsulate(pk) def get_pin_token( self, pin: str, permissions: Optional[ClientPin.PERMISSION] = None, permissions_rpid: Optional[str] = None, ) -> bytes: """Get a PIN/UV token from the authenticator using PIN. :param pin: The PIN of the authenticator. :param permissions: The permissions to associate with the token. :param permissions_rpid: The permissions RPID to associate with the token. :return: A PIN/UV token. """ if not ClientPin.is_supported(self.ctap.info): raise ValueError("Authenticator does not support get_pin_token") key_agreement, shared_secret = self._get_shared_secret() pin_hash = sha256(pin.encode())[:16] pin_hash_enc = self.protocol.encrypt(shared_secret, pin_hash) if ClientPin.is_token_supported(self.ctap.info) and permissions: cmd = ClientPin.CMD.GET_TOKEN_USING_PIN else: cmd = ClientPin.CMD.GET_TOKEN_USING_PIN_LEGACY # Ignore permissions if not supported permissions = None permissions_rpid = None resp = self.ctap.client_pin( self.protocol.VERSION, cmd, key_agreement=key_agreement, pin_hash_enc=pin_hash_enc, permissions=permissions, permissions_rpid=permissions_rpid, ) pin_token_enc = resp[ClientPin.RESULT.PIN_UV_TOKEN] logger.debug(f"Got PIN token for permissions: {permissions}") return self.protocol.validate_token( self.protocol.decrypt(shared_secret, pin_token_enc) ) def get_uv_token( self, permissions: Optional[ClientPin.PERMISSION] = None, permissions_rpid: Optional[str] = None, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> bytes: """Get a PIN/UV token from the authenticator using built-in UV. :param permissions: The permissions to associate with the token. :param permissions_rpid: The permissions RPID to associate with the token. :param event: An optional threading.Event which can be used to cancel the invocation. :param on_keepalive: An optional callback to handle keep-alive messages from the authenticator. The function is only called once for consecutive keep-alive messages with the same status. :return: A PIN/UV token. """ if not ClientPin.is_token_supported(self.ctap.info): raise ValueError("Authenticator does not support get_uv_token") key_agreement, shared_secret = self._get_shared_secret() resp = self.ctap.client_pin( self.protocol.VERSION, ClientPin.CMD.GET_TOKEN_USING_UV, key_agreement=key_agreement, permissions=permissions, permissions_rpid=permissions_rpid, event=event, on_keepalive=on_keepalive, ) pin_token_enc = resp[ClientPin.RESULT.PIN_UV_TOKEN] logger.debug(f"Got UV token for permissions: {permissions}") return self.protocol.validate_token( self.protocol.decrypt(shared_secret, pin_token_enc) ) def get_pin_retries(self) -> Tuple[int, Optional[int]]: """Get the number of PIN retries remaining. :return: A tuple of the number of PIN attempts remaining until the authenticator is locked, and the power cycle state, if available. """ resp = self.ctap.client_pin( self.protocol.VERSION, ClientPin.CMD.GET_PIN_RETRIES ) return ( resp[ClientPin.RESULT.PIN_RETRIES], resp.get(ClientPin.RESULT.POWER_CYCLE_STATE), ) def get_uv_retries(self) -> int: """Get the number of UV retries remaining. :return: A tuple of the number of UV attempts remaining until the authenticator is locked, and the power cycle state, if available. """ resp = self.ctap.client_pin(self.protocol.VERSION, ClientPin.CMD.GET_UV_RETRIES) return resp[ClientPin.RESULT.UV_RETRIES] def set_pin(self, pin: str) -> None: """Set the PIN of the autenticator. This only works when no PIN is set. To change the PIN when set, use change_pin. :param pin: A PIN to set. """ if not ClientPin.is_supported(self.ctap.info): raise ValueError("Authenticator does not support ClientPin") key_agreement, shared_secret = self._get_shared_secret() pin_enc = self.protocol.encrypt(shared_secret, _pad_pin(pin)) pin_uv_param = self.protocol.authenticate(shared_secret, pin_enc) self.ctap.client_pin( self.protocol.VERSION, ClientPin.CMD.SET_PIN, key_agreement=key_agreement, new_pin_enc=pin_enc, pin_uv_param=pin_uv_param, ) logger.info("PIN has been set") def change_pin(self, old_pin: str, new_pin: str) -> None: """Change the PIN of the authenticator. This only works when a PIN is already set. If no PIN is set, use set_pin. :param old_pin: The currently set PIN. :param new_pin: The new PIN to set. """ if not ClientPin.is_supported(self.ctap.info): raise ValueError("Authenticator does not support ClientPin") key_agreement, shared_secret = self._get_shared_secret() pin_hash = sha256(old_pin.encode())[:16] pin_hash_enc = self.protocol.encrypt(shared_secret, pin_hash) new_pin_enc = self.protocol.encrypt(shared_secret, _pad_pin(new_pin)) pin_uv_param = self.protocol.authenticate( shared_secret, new_pin_enc + pin_hash_enc ) self.ctap.client_pin( self.protocol.VERSION, ClientPin.CMD.CHANGE_PIN, key_agreement=key_agreement, pin_hash_enc=pin_hash_enc, new_pin_enc=new_pin_enc, pin_uv_param=pin_uv_param, ) logger.info("PIN has been changed") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/features.py0000644000000000000000000000667314413232070012647 0ustar00# Copyright (c) 2022 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 typing import Optional import warnings class FeatureNotEnabledError(Exception): pass class _Feature: def __init__(self, name: str, desc: str): self._enabled: Optional[bool] = None self._name = name self._desc = desc @property def enabled(self) -> bool: self.warn() return self._enabled is True @enabled.setter def enabled(self, value: bool) -> None: if self._enabled is not None: raise ValueError( f"{self._name} has already been configured with {self._enabled}" ) self._enabled = value def require(self, state=True) -> None: if self._enabled != state: self.warn() raise FeatureNotEnabledError( f"Usage requires {self._name}.enabled = {state}" ) def warn(self) -> None: if self._enabled is None: warnings.warn( f"""Deprecated use of {self._name}. You are using deprecated functionality which will change in the next major version of python-fido2. You can opt-in to use the new functionality now by adding the following to your code somewhere where it gets executed prior to using the affected functionality: import fido2.features fido2.features.{self._name}.enabled = True To silence this warning but retain the current behavior, instead set enabled to False: fido2.features.{self._name}.enabled = False {self._desc} """, DeprecationWarning, ) webauthn_json_mapping = _Feature( "webauthn_json_mapping", """JSON values for WebAuthn data class Mapping interface. This changes the keys and values used by the webauthn data classes when accessed using the Mapping (dict) interface (eg. user_entity["id"] and the from_dict() methods) to be JSON-friendly and align with the current draft of the next WebAuthn Level specification. For the most part, this means that binary values (bytes) are represented as URL-safe base64 encoded strings instead. """, ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680688171.4656444 fido2-1.1.2/fido2/hid/__init__.py0000644000000000000000000002164614413242053013333 0ustar00# 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 annotations from .base import HidDescriptor from ..ctap import CtapDevice, CtapError, STATUS from ..utils import LOG_LEVEL_TRAFFIC from threading import Event from enum import IntEnum, IntFlag, unique from typing import Tuple, Optional, Callable, Iterator import struct import sys import os import logging logger = logging.getLogger(__name__) if sys.platform.startswith("linux"): from . import linux as backend elif sys.platform.startswith("win32"): from . import windows as backend elif sys.platform.startswith("darwin"): from . import macos as backend elif sys.platform.startswith("freebsd"): from . import freebsd as backend elif sys.platform.startswith("netbsd"): from . import netbsd as backend elif sys.platform.startswith("openbsd"): from . import openbsd as backend else: raise Exception("Unsupported platform") list_descriptors = backend.list_descriptors get_descriptor = backend.get_descriptor open_connection = backend.open_connection @unique class CTAPHID(IntEnum): PING = 0x01 MSG = 0x03 LOCK = 0x04 INIT = 0x06 WINK = 0x08 CBOR = 0x10 CANCEL = 0x11 ERROR = 0x3F KEEPALIVE = 0x3B VENDOR_FIRST = 0x40 @unique class CAPABILITY(IntFlag): WINK = 0x01 LOCK = 0x02 # Not used CBOR = 0x04 NMSG = 0x08 def supported(self, flags: CAPABILITY) -> bool: return bool(flags & self) TYPE_INIT = 0x80 class CtapHidDevice(CtapDevice): """ CtapDevice implementation using the HID transport. :cvar descriptor: Device descriptor. """ def __init__(self, descriptor: HidDescriptor, connection): self.descriptor = descriptor self._packet_size = descriptor.report_size_out self._connection = connection nonce = os.urandom(8) self._channel_id = 0xFFFFFFFF response = self.call(CTAPHID.INIT, nonce) r_nonce, response = response[:8], response[8:] if r_nonce != nonce: raise Exception("Wrong nonce") ( self._channel_id, self._u2fhid_version, v1, v2, v3, self._capabilities, ) = struct.unpack_from(">IBBBBB", response) self._device_version = (v1, v2, v3) def __repr__(self): return f"CtapHidDevice({self.descriptor.path!r})" @property def version(self) -> int: """CTAP HID protocol version.""" return self._u2fhid_version @property def device_version(self) -> Tuple[int, int, int]: """Device version number.""" return self._device_version @property def capabilities(self) -> int: """Capabilities supported by the device.""" return self._capabilities @property def product_name(self) -> Optional[str]: """Product name of device.""" return self.descriptor.product_name @property def serial_number(self) -> Optional[str]: """Serial number of device.""" return self.descriptor.serial_number def _send_cancel(self): packet = struct.pack(">IB", self._channel_id, TYPE_INIT | CTAPHID.CANCEL).ljust( self._packet_size, b"\0" ) logger.log(LOG_LEVEL_TRAFFIC, "SEND: %s", packet.hex()) self._connection.write_packet(packet) def call( self, cmd: int, data: bytes = b"", event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> bytes: event = event or Event() remaining = data seq = 0 # Send request header = struct.pack(">IBH", self._channel_id, TYPE_INIT | cmd, len(remaining)) while remaining or seq == 0: size = min(len(remaining), self._packet_size - len(header)) body, remaining = remaining[:size], remaining[size:] packet = header + body logger.log(LOG_LEVEL_TRAFFIC, "SEND: %s", packet.hex()) self._connection.write_packet(packet.ljust(self._packet_size, b"\0")) header = struct.pack(">IB", self._channel_id, 0x7F & seq) seq += 1 try: # Read response seq = 0 response = b"" last_ka = None while True: if event.is_set(): # Cancel logger.debug("Sending cancel...") self._send_cancel() recv = self._connection.read_packet() logger.log(LOG_LEVEL_TRAFFIC, "RECV: %s", recv.hex()) r_channel = struct.unpack_from(">I", recv)[0] recv = recv[4:] if r_channel != self._channel_id: raise Exception("Wrong channel") if not response: # Initialization packet r_cmd, r_len = struct.unpack_from(">BH", recv) recv = recv[3:] if r_cmd == TYPE_INIT | cmd: pass # first data packet elif r_cmd == TYPE_INIT | CTAPHID.KEEPALIVE: ka_status = struct.unpack_from(">B", recv)[0] logger.debug(f"Got keepalive status: {ka_status:02x}") if on_keepalive and ka_status != last_ka: try: ka_status = STATUS(ka_status) except ValueError: pass # Unknown status value last_ka = ka_status on_keepalive(ka_status) continue elif r_cmd == TYPE_INIT | CTAPHID.ERROR: raise CtapError(struct.unpack_from(">B", recv)[0]) else: raise CtapError(CtapError.ERR.INVALID_COMMAND) else: # Continuation packet r_seq = struct.unpack_from(">B", recv)[0] recv = recv[1:] if r_seq != seq: raise Exception("Wrong sequence number") seq += 1 response += recv if len(response) >= r_len: break return response[:r_len] except KeyboardInterrupt: logger.debug("Keyboard interrupt, cancelling...") self._send_cancel() raise def wink(self) -> None: """Causes the authenticator to blink.""" self.call(CTAPHID.WINK) def ping(self, msg: bytes = b"Hello FIDO") -> bytes: """Sends data to the authenticator, which echoes it back. :param msg: The data to send. :return: The response from the authenticator. """ return self.call(CTAPHID.PING, msg) def lock(self, lock_time: int = 10) -> None: """Locks the channel.""" self.call(CTAPHID.LOCK, struct.pack(">B", lock_time)) def close(self) -> None: if self._connection: self._connection.close() self._connection = None @classmethod def list_devices(cls) -> Iterator[CtapHidDevice]: for d in list_descriptors(): yield cls(d, open_connection(d)) def list_devices() -> Iterator[CtapHidDevice]: return CtapHidDevice.list_devices() def open_device(path) -> CtapHidDevice: descriptor = get_descriptor(path) return CtapHidDevice(descriptor, open_connection(descriptor)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/hid/base.py0000644000000000000000000001114314413232070012473 0ustar00# 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 annotations from dataclasses import dataclass from typing import Tuple, Union, Optional import struct import abc import os FIDO_USAGE_PAGE = 0xF1D0 FIDO_USAGE = 0x1 @dataclass class HidDescriptor: path: Union[str, bytes] vid: int pid: int report_size_in: int report_size_out: int product_name: Optional[str] serial_number: Optional[str] class CtapHidConnection(abc.ABC): @abc.abstractmethod def read_packet(self) -> bytes: """Reads a CTAP HID packet""" @abc.abstractmethod def write_packet(self, data: bytes) -> None: """Writes a CTAP HID packet""" @abc.abstractmethod def close(self) -> None: """Closes the connection""" class FileCtapHidConnection(CtapHidConnection): """Basic CtapHidConnection implementation which uses a path to a file descriptor""" def __init__(self, descriptor): self.handle = os.open(descriptor.path, os.O_RDWR) self.descriptor = descriptor def close(self): os.close(self.handle) def write_packet(self, packet): if os.write(self.handle, packet) != len(packet): raise OSError("failed to write entire packet") def read_packet(self): return os.read(self.handle, self.descriptor.report_size_in) REPORT_DESCRIPTOR_KEY_MASK = 0xFC SIZE_MASK = ~REPORT_DESCRIPTOR_KEY_MASK OUTPUT_ITEM = 0x90 INPUT_ITEM = 0x80 COLLECTION_ITEM = 0xA0 REPORT_COUNT = 0x94 REPORT_SIZE = 0x74 USAGE_PAGE = 0x04 USAGE = 0x08 def parse_report_descriptor(data: bytes) -> Tuple[int, int]: # Parse report descriptor data usage, usage_page = None, None max_input_size, max_output_size = None, None report_count, report_size = None, None remaining = 4 while data and remaining: head, data = struct.unpack_from(">B", data)[0], data[1:] key, size = REPORT_DESCRIPTOR_KEY_MASK & head, SIZE_MASK & head value = struct.unpack_from(" HIDIOCGRAWINFO = 0x40085520 HIDIOCGRDESC = 0x2000551F HIDIOCGRDESCSIZE = 0x4004551E HIDIOCGRAWNAME_128 = 0x40805521 HIDIOCGRAWUNIQ_64 = 0x40405525 class usb_gen_descriptor(ctypes.Structure): _fields_ = [ ( "ugd_data", ctypes.c_void_p, ), # TODO: check what COMPAT_32BIT in C header means ("ugd_lang_id", ctypes.c_uint16), ("ugd_maxlen", ctypes.c_uint16), ("ugd_actlen", ctypes.c_uint16), ("ugd_offset", ctypes.c_uint16), ("ugd_config_index", ctypes.c_uint8), ("ugd_string_index", ctypes.c_uint8), ("ugd_iface_index", ctypes.c_uint8), ("ugd_altif_index", ctypes.c_uint8), ("ugd_endpt_index", ctypes.c_uint8), ("ugd_report_type", ctypes.c_uint8), ("reserved", ctypes.c_uint8 * 8), ] class HidrawCtapHidConnection(FileCtapHidConnection): def write_packet(self, packet): # Prepend the report ID super(HidrawCtapHidConnection, self).write_packet(b"\0" + packet) def open_connection(descriptor): if descriptor.path.find(devdir + "hidraw") == 0: return HidrawCtapHidConnection(descriptor) else: return FileCtapHidConnection(descriptor) def _get_report_data(fd, report_type): data = ctypes.create_string_buffer(4096) desc = usb_gen_descriptor( ugd_data=ctypes.addressof(data), ugd_maxlen=ctypes.sizeof(data), ugd_report_type=report_type, ) ret = libc.ioctl(fd, USB_GET_REPORT_DESC, ctypes.byref(desc)) if ret != 0: raise ValueError("ioctl failed") return data.raw[: desc.ugd_actlen] def _read_descriptor(vid, pid, name, serial, path): fd = os.open(path, os.O_RDONLY) data = _get_report_data(fd, 3) os.close(fd) max_in_size, max_out_size = parse_report_descriptor(data) return HidDescriptor(path, vid, pid, max_in_size, max_out_size, name, serial) def _enumerate(): for uhid in glob.glob(devdir + "uhid?*"): index = uhid[len(devdir) + len("uhid") :] if not index.isdigit(): continue pnpinfo = ("dev.uhid." + index + ".%pnpinfo").encode() desc = ("dev.uhid." + index + ".%desc").encode() ovalue = ctypes.create_string_buffer(1024) olen = ctypes.c_size_t(ctypes.sizeof(ovalue)) key = ctypes.c_char_p(pnpinfo) retval = libc.sysctlbyname(key, ovalue, ctypes.byref(olen), None, None) if retval != 0: continue dev: Dict[str, Optional[Union[str, int]]] = {} dev["name"] = uhid[len(devdir) :] dev["path"] = uhid value = ovalue.value[: olen.value].decode() m = vendor_re.search(value) dev["vendor_id"] = int(m.group(1), 16) if m else None m = product_re.search(value) dev["product_id"] = int(m.group(1), 16) if m else None m = sernum_re.search(value) dev["serial_number"] = m.group(1) if m else None key = ctypes.c_char_p(desc) retval = libc.sysctlbyname(key, ovalue, ctypes.byref(olen), None, None) if retval == 0: dev["product_desc"] = ovalue.value[: olen.value].decode() or None yield dev def get_hidraw_descriptor(path): with open(path, "rb") as f: # Read VID, PID buf = array("B", [0] * (4 + 2 + 2)) fcntl.ioctl(f, HIDIOCGRAWINFO, buf, True) _, vid, pid = struct.unpack(" 1 else None # Read unique ID try: buf = array("B", [0] * 65) fcntl.ioctl(f, HIDIOCGRAWUNIQ_64, buf, True) length = buf.index(0) + 1 # emulate ioctl return value serial = ( bytearray(buf[: (length - 1)]).decode("utf-8") if length > 1 else None ) except OSError: serial = None # Read report descriptor buf = array("B", [0] * 4) fcntl.ioctl(f, HIDIOCGRDESCSIZE, buf, True) size = struct.unpack(" 1 else None # Read unique ID try: buf = array("B", [0] * 64) length = fcntl.ioctl(f, HIDIOCGRAWUNIQ, buf, True) serial = ( bytearray(buf[: (length - 1)]).decode("utf-8") if length > 1 else None ) except OSError: serial = None # Read report descriptor buf = array("B", [0] * 4) fcntl.ioctl(f, HIDIOCGRDESCSIZE, buf, True) size = struct.unpack(" bool: """Filters out any revoked metadata entry. This filter will remove any metadata entry which has a status_report with the REVOKED status. """ return not any( r.status == AuthenticatorStatus.REVOKED for r in entry.status_reports ) def filter_attestation_key_compromised( entry: MetadataBlobPayloadEntry, certificate_chain: Sequence[bytes] ) -> bool: """Denies any attestation that has a compromised attestation key. This filter checks the status reports of a metadata entry and ensures the attestation isn't signed by a key which is marked as compromised. """ for r in entry.status_reports: if r.status == AuthenticatorStatus.ATTESTATION_KEY_COMPROMISE: if r.certificate in certificate_chain: return False return True _last_entry: ContextVar[Optional[MetadataBlobPayloadEntry]] = ContextVar("_last_entry") class MdsAttestationVerifier(AttestationVerifier): """MDS3 implementation of an AttestationVerifier. The entry_filter is an optional predicate used to filter which metadata entries to include in the lookup for verification. By default, a filter that removes any entries that have a status report indicating the authenticator is REVOKED is used. See: filter_revoked The attestation_filter is an optional predicate used to filter metadata entries while performing attestation validation, and may take into account the Authenticators attestation trust_chain. By default, a filter that will fail any verification that has a trust_chain where one of the certificates is marked as compromised by the metadata statement is used. See: filter_attestation_key_compromised NOTE: The attestation_filter is not used when calling find_entry_by_aaguid nor find_entry_by_chain as no attestation is being verified! Setting either filter (including setting it to None) will replace it, removing the default behavior. :param blob: The MetadataBlobPayload to query for device metadata. :param entry_filter: An optional filter to exclude entries from lookup. :param attestation_filter: An optional filter to fail verification for a given attestation. :param attestation_types: A list of Attestation types to support. """ def __init__( self, blob: MetadataBlobPayload, entry_filter: Optional[EntryFilter] = filter_revoked, attestation_filter: Optional[LookupFilter] = filter_attestation_key_compromised, attestation_types: Optional[Sequence[Attestation]] = None, ): super().__init__(attestation_types) self._attestation_filter = attestation_filter or ( lambda a, b: True ) # No-op for None entries = ( [e for e in blob.entries if entry_filter(e)] if entry_filter else blob.entries ) self._aaguid_table = {e.aaguid: e for e in entries if e.aaguid} self._ski_table = { ski: e for e in entries for ski in e.attestation_certificate_key_identifiers or [] } def find_entry_by_aaguid( self, aaguid: Aaguid ) -> Optional[MetadataBlobPayloadEntry]: """Find an entry by AAGUID. Returns a MetadataBlobPayloadEntry with a matching aaguid field, if found. This method does not take the attestation_filter into account. """ return self._aaguid_table.get(aaguid) def find_entry_by_chain( self, certificate_chain: Sequence[bytes] ) -> Optional[MetadataBlobPayloadEntry]: """Find an entry by trust chain. Returns a MetadataBlobPayloadEntry containing an attestationCertificateKeyIdentifier which matches one of the certificates in the given chain, if found. This method does not take the attestation_filter into account. """ for der in certificate_chain: cert = x509.load_der_x509_certificate(der, default_backend()) ski = x509.SubjectKeyIdentifier.from_public_key(cert.public_key()).digest if ski in self._ski_table: return self._ski_table[ski] return None def ca_lookup(self, result, auth_data): aaguid = auth_data.credential_data.aaguid if aaguid: logging.debug(f"Using AAGUID: {aaguid} to look up metadata") entry = self.find_entry_by_aaguid(aaguid) else: logging.debug("Using trust_path chain to look up metadata") entry = self.find_entry_by_chain(result.trust_path) if entry: logging.debug(f"Found entry: {entry}") # Check attestation filter if not self._attestation_filter(entry, result.trust_path): logging.debug("Matched entry did not pass attestation filter") return None # Figure out which root to use if not entry.metadata_statement: logging.warn("Matched entry has no metadata_statement, can't validate!") return None issuer = x509.load_der_x509_certificate( result.trust_path[-1], default_backend() ).issuer for root in entry.metadata_statement.attestation_root_certificates: subject = x509.load_der_x509_certificate( root, default_backend() ).subject if subject == issuer: _last_entry.set(entry) return root logger.info(f"No attestation root matching subject: {subject}") return None def find_entry( self, attestation_object: AttestationObject, client_data_hash: bytes ) -> Optional[MetadataBlobPayloadEntry]: """Lookup a Metadata entry based on an Attestation. Returns the first Metadata entry matching the given attestation and verifies it, including checking it against the attestation_filter. """ token = _last_entry.set(None) try: self.verify_attestation(attestation_object, client_data_hash) return _last_entry.get() except UntrustedAttestation: return None finally: _last_entry.reset(token) def parse_blob(blob: bytes, trust_root: Optional[bytes]) -> MetadataBlobPayload: """Parse a FIDO MDS3 blob and verifies its signature. See https://fidoalliance.org/metadata/ for details on obtaining the blob, as well as the CA certificate used to sign it. The resulting MetadataBlobPayload can be used to lookup metadata entries for specific Authenticators, or used with the MdsAttestationVerifier to verify that the attestation from a WebAuthn registration is valid and included in the metadata blob. NOTE: If trust_root is None, the signature of the blob will NOT be verified! """ message, signature_b64 = blob.rsplit(b".", 1) signature = websafe_decode(signature_b64) header, payload = (json.loads(websafe_decode(x)) for x in message.split(b".")) if trust_root is not None: # Verify trust chain chain = [b64decode(c) for c in header.get("x5c", [])] chain += [trust_root] verify_x509_chain(chain) # Verify blob signature using leaf leaf = x509.load_der_x509_certificate(chain[0], default_backend()) public_key = CoseKey.for_name(header["alg"]).from_cryptography_key( leaf.public_key() ) public_key.verify(message, signature) else: logger.warn("Parsing MDS blob without trust anchor, CONTENT IS NOT VERIFIED!") return MetadataBlobPayload.from_dict(payload) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5538907 fido2-1.1.2/fido2/pcsc.py0000644000000000000000000002170014413232070011745 0ustar00# 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. from __future__ import annotations from .ctap import CtapDevice, CtapError, STATUS from .hid import CAPABILITY, CTAPHID from .utils import LOG_LEVEL_TRAFFIC from smartcard import System from smartcard.CardConnection import CardConnection from smartcard.pcsc.PCSCExceptions import ListReadersException from smartcard.pcsc.PCSCContext import PCSCContext from threading import Event from typing import Tuple, Optional, Callable, Iterator import struct import logging AID_FIDO = b"\xa0\x00\x00\x06\x47\x2f\x00\x01" SW_SUCCESS = (0x90, 0x00) SW_UPDATE = (0x91, 0x00) SW1_MORE_DATA = 0x61 logger = logging.getLogger(__name__) class CtapPcscDevice(CtapDevice): """ CtapDevice implementation using pyscard (PCSC). This class is intended for use with NFC readers. """ def __init__(self, connection: CardConnection, name: str): self._capabilities = CAPABILITY(0) self.use_ext_apdu = False self._conn = connection self._conn.connect() self._name = name self._select() try: # Probe for CTAP2 by calling GET_INFO self.call(CTAPHID.CBOR, b"\x04") self._capabilities |= CAPABILITY.CBOR except CtapError: if not self._capabilities: raise ValueError("Unsupported device") def __repr__(self): return f"CtapPcscDevice({self._name})" @property def version(self) -> int: """CTAPHID protocol version.""" return 2 if CAPABILITY.CBOR in self._capabilities else 1 @property def capabilities(self) -> CAPABILITY: """Capabilities supported by the device.""" return self._capabilities @property def product_name(self) -> Optional[str]: """Product name of device.""" return None @property def serial_number(self) -> Optional[int]: """Serial number of device.""" return None def get_atr(self) -> bytes: """Get the ATR/ATS of the connected card.""" return bytes(self._conn.getATR()) def apdu_exchange( self, apdu: bytes, protocol: Optional[int] = None ) -> Tuple[bytes, int, int]: """Exchange data with smart card. :param apdu: byte string. data to exchange with card :return: byte string. response from card """ logger.log(LOG_LEVEL_TRAFFIC, "SEND: %s", apdu.hex()) resp, sw1, sw2 = self._conn.transmit(list(apdu), protocol) response = bytes(resp) logger.log( LOG_LEVEL_TRAFFIC, "RECV: %s SW=%04X", response.hex(), sw1 << 8 + sw2 ) return response, sw1, sw2 def control_exchange(self, control_code: int, control_data: bytes = b"") -> bytes: """Sends control sequence to reader's driver. :param control_code: int. code to send to reader driver. :param control_data: byte string. data to send to driver :return: byte string. response """ logger.log(LOG_LEVEL_TRAFFIC, "Send control: %s", control_data.hex()) response = self._conn.control(control_code, list(control_data)) response = bytes(response) logger.log(LOG_LEVEL_TRAFFIC, "Control response: %s", response.hex()) return response def _select(self) -> None: apdu = b"\x00\xa4\x04\x00" + struct.pack("!B", len(AID_FIDO)) + AID_FIDO resp, sw1, sw2 = self.apdu_exchange(apdu) if (sw1, sw2) != SW_SUCCESS: raise ValueError("FIDO applet selection failure.") if resp == b"U2F_V2": self._capabilities |= CAPABILITY.NMSG def _chain_apdus( self, cla: int, ins: int, p1: int, p2: int, data: bytes = b"" ) -> Tuple[bytes, int, int]: if self.use_ext_apdu: header = struct.pack("!BBBBBH", cla, ins, p1, p2, 0x00, len(data)) resp, sw1, sw2 = self.apdu_exchange(header + data) return resp, sw1, sw2 else: while len(data) > 250: to_send, data = data[:250], data[250:] header = struct.pack("!BBBBB", 0x10 | cla, ins, p1, p2, len(to_send)) resp, sw1, sw2 = self.apdu_exchange(header + to_send) if (sw1, sw2) != SW_SUCCESS: return resp, sw1, sw2 apdu = struct.pack("!BBBB", cla, ins, p1, p2) if data: apdu += struct.pack("!B", len(data)) + data resp, sw1, sw2 = self.apdu_exchange(apdu + b"\x00") while sw1 == SW1_MORE_DATA: apdu = b"\x00\xc0\x00\x00" + struct.pack("!B", sw2) # sw2 == le lres, sw1, sw2 = self.apdu_exchange(apdu) resp += lres return resp, sw1, sw2 def _call_apdu(self, apdu: bytes) -> bytes: if len(apdu) >= 7 and apdu[4] == 0: # Extended APDU data_len = struct.unpack("!H", apdu[5:7])[0] data = apdu[7 : 7 + data_len] elif len(apdu) == 4: data = b"" else: # Short APDU data_len = apdu[4] data = apdu[5 : 5 + data_len] (cla, ins, p1, p2) = apdu[:4] resp, sw1, sw2 = self._chain_apdus(cla, ins, p1, p2, data) return resp + struct.pack("!BB", sw1, sw2) def _call_cbor( self, data: bytes = b"", event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> bytes: event = event or Event() # NFCCTAP_MSG resp, sw1, sw2 = self._chain_apdus(0x80, 0x10, 0x80, 0x00, data) last_ka = None while not event.is_set(): while (sw1, sw2) == SW_UPDATE: ka_status = resp[0] if on_keepalive and last_ka != ka_status: try: ka_status = STATUS(ka_status) except ValueError: pass # Unknown status value last_ka = ka_status on_keepalive(ka_status) # NFCCTAP_GETRESPONSE resp, sw1, sw2 = self._chain_apdus(0x80, 0x11, 0x00, 0x00) if (sw1, sw2) != SW_SUCCESS: raise CtapError(CtapError.ERR.OTHER) # TODO: Map from SW error return resp raise CtapError(CtapError.ERR.KEEPALIVE_CANCEL) def call( self, cmd: int, data: bytes = b"", event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> bytes: if cmd == CTAPHID.CBOR: return self._call_cbor(data, event, on_keepalive) elif cmd == CTAPHID.MSG: return self._call_apdu(data) else: raise CtapError(CtapError.ERR.INVALID_COMMAND) def close(self) -> None: self._conn.disconnect() @classmethod def list_devices(cls, name: str = "") -> Iterator[CtapPcscDevice]: for reader in _list_readers(): if name in reader.name: try: yield cls(reader.createConnection(), reader.name) except Exception as e: logger.debug("Error %r", e) def _list_readers(): try: return System.readers() except ListReadersException: # If the PCSC system has restarted the context might be stale, try # forcing a new context (This happens on Windows if the last reader is # removed): PCSCContext.instance = None return System.readers() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1688652057.3192372 fido2-1.1.2/fido2/public_suffix_list.dat0000644000000000000000000072420014451544431015050 0ustar00// 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 https://mozilla.org/MPL/2.0/. // Please pull this list from, and only from https://publicsuffix.org/list/public_suffix_list.dat, // rather than any other VCS sites. Pulling from any other URL is not guaranteed to be supported. // Instructions on pulling and using this list can be found at https://publicsuffix.org/list/. // ===BEGIN ICANN DOMAINS=== // ac : http://nic.ac/rules.htm ac com.ac edu.ac gov.ac net.ac mil.ac org.ac // ad : https://en.wikipedia.org/wiki/.ad ad nom.ad // ae : https://tdra.gov.ae/en/aeda/ae-policies ae co.ae net.ae org.ae sch.ae ac.ae gov.ae mil.ae // aero : see https://www.information.aero/index.php?id=66 aero accident-investigation.aero accident-prevention.aero aerobatic.aero aeroclub.aero aerodrome.aero agents.aero aircraft.aero airline.aero airport.aero air-surveillance.aero airtraffic.aero air-traffic-control.aero ambulance.aero amusement.aero association.aero author.aero ballooning.aero broker.aero caa.aero cargo.aero catering.aero certification.aero championship.aero charter.aero civilaviation.aero club.aero conference.aero consultant.aero consulting.aero control.aero council.aero crew.aero design.aero dgca.aero educator.aero emergency.aero engine.aero engineer.aero entertainment.aero equipment.aero exchange.aero express.aero federation.aero flight.aero fuel.aero gliding.aero government.aero groundhandling.aero group.aero hanggliding.aero homebuilt.aero insurance.aero journal.aero journalist.aero leasing.aero logistics.aero magazine.aero maintenance.aero media.aero microlight.aero modelling.aero navigation.aero parachuting.aero paragliding.aero passenger-association.aero pilot.aero press.aero production.aero recreation.aero repbody.aero res.aero research.aero rotorcraft.aero safety.aero scientist.aero services.aero show.aero skydiving.aero software.aero student.aero trader.aero trading.aero trainer.aero union.aero workinggroup.aero works.aero // af : http://www.nic.af/help.jsp af gov.af com.af org.af net.af edu.af // ag : http://www.nic.ag/prices.htm ag com.ag org.ag net.ag co.ag nom.ag // ai : http://nic.com.ai/ ai off.ai com.ai net.ai org.ai // al : http://www.ert.gov.al/ert_alb/faq_det.html?Id=31 al com.al edu.al gov.al mil.al net.al org.al // am : https://www.amnic.net/policy/en/Policy_EN.pdf am co.am com.am commune.am net.am org.am // ao : https://en.wikipedia.org/wiki/.ao // http://www.dns.ao/REGISTR.DOC ao ed.ao gv.ao og.ao co.ao pb.ao it.ao // aq : https://en.wikipedia.org/wiki/.aq aq // ar : https://nic.ar/es/nic-argentina/normativa ar bet.ar com.ar coop.ar edu.ar gob.ar gov.ar int.ar mil.ar musica.ar mutual.ar net.ar org.ar senasa.ar tur.ar // arpa : https://en.wikipedia.org/wiki/.arpa // Confirmed by registry 2008-06-18 arpa e164.arpa in-addr.arpa ip6.arpa iris.arpa uri.arpa urn.arpa // as : https://en.wikipedia.org/wiki/.as as gov.as // asia : https://en.wikipedia.org/wiki/.asia asia // at : https://en.wikipedia.org/wiki/.at // Confirmed by registry 2008-06-17 at ac.at co.at gv.at or.at sth.ac.at // au : https://en.wikipedia.org/wiki/.au // http://www.auda.org.au/ au // 2LDs com.au net.au org.au edu.au gov.au asn.au id.au // Historic 2LDs (closed to new registration, but sites still exist) info.au conf.au oz.au // CGDNs - http://www.cgdn.org.au/ act.au nsw.au nt.au qld.au sa.au tas.au vic.au wa.au // 3LDs act.edu.au catholic.edu.au // eq.edu.au - Removed at the request of the Queensland Department of Education nsw.edu.au nt.edu.au qld.edu.au sa.edu.au tas.edu.au vic.edu.au wa.edu.au // act.gov.au Bug 984824 - Removed at request of Greg Tankard // nsw.gov.au Bug 547985 - Removed at request of // nt.gov.au Bug 940478 - Removed at request of Greg Connors qld.gov.au sa.gov.au tas.gov.au vic.gov.au wa.gov.au // 4LDs // education.tas.edu.au - Removed at the request of the Department of Education Tasmania schools.nsw.edu.au // aw : https://en.wikipedia.org/wiki/.aw aw com.aw // ax : https://en.wikipedia.org/wiki/.ax ax // az : https://en.wikipedia.org/wiki/.az az com.az net.az int.az gov.az org.az edu.az info.az pp.az mil.az name.az pro.az biz.az // ba : http://nic.ba/users_data/files/pravilnik_o_registraciji.pdf ba com.ba edu.ba gov.ba mil.ba net.ba org.ba // bb : https://en.wikipedia.org/wiki/.bb bb biz.bb co.bb com.bb edu.bb gov.bb info.bb net.bb org.bb store.bb tv.bb // bd : https://en.wikipedia.org/wiki/.bd *.bd // be : https://en.wikipedia.org/wiki/.be // Confirmed by registry 2008-06-08 be ac.be // bf : https://en.wikipedia.org/wiki/.bf bf gov.bf // bg : https://en.wikipedia.org/wiki/.bg // https://www.register.bg/user/static/rules/en/index.html bg a.bg b.bg c.bg d.bg e.bg f.bg g.bg h.bg i.bg j.bg k.bg l.bg m.bg n.bg o.bg p.bg q.bg r.bg s.bg t.bg u.bg v.bg w.bg x.bg y.bg z.bg 0.bg 1.bg 2.bg 3.bg 4.bg 5.bg 6.bg 7.bg 8.bg 9.bg // bh : https://en.wikipedia.org/wiki/.bh bh com.bh edu.bh net.bh org.bh gov.bh // bi : https://en.wikipedia.org/wiki/.bi // http://whois.nic.bi/ bi co.bi com.bi edu.bi or.bi org.bi // biz : https://en.wikipedia.org/wiki/.biz biz // bj : https://nic.bj/bj-suffixes.txt // submitted by registry bj africa.bj agro.bj architectes.bj assur.bj avocats.bj co.bj com.bj eco.bj econo.bj edu.bj info.bj loisirs.bj money.bj net.bj org.bj ote.bj resto.bj restaurant.bj tourism.bj univ.bj // bm : http://www.bermudanic.bm/dnr-text.txt bm com.bm edu.bm gov.bm net.bm org.bm // bn : http://www.bnnic.bn/faqs bn com.bn edu.bn gov.bn net.bn org.bn // bo : https://nic.bo/delegacion2015.php#h-1.10 bo com.bo edu.bo gob.bo int.bo org.bo net.bo mil.bo tv.bo web.bo // Social Domains academia.bo agro.bo arte.bo blog.bo bolivia.bo ciencia.bo cooperativa.bo democracia.bo deporte.bo ecologia.bo economia.bo empresa.bo indigena.bo industria.bo info.bo medicina.bo movimiento.bo musica.bo natural.bo nombre.bo noticias.bo patria.bo politica.bo profesional.bo plurinacional.bo pueblo.bo revista.bo salud.bo tecnologia.bo tksat.bo transporte.bo wiki.bo // br : http://registro.br/dominio/categoria.html // Submitted by registry br 9guacu.br abc.br adm.br adv.br agr.br aju.br am.br anani.br aparecida.br app.br arq.br art.br ato.br b.br barueri.br belem.br bhz.br bib.br bio.br blog.br bmd.br boavista.br bsb.br campinagrande.br campinas.br caxias.br cim.br cng.br cnt.br com.br contagem.br coop.br coz.br cri.br cuiaba.br curitiba.br def.br des.br det.br dev.br ecn.br eco.br edu.br emp.br enf.br eng.br esp.br etc.br eti.br far.br feira.br flog.br floripa.br fm.br fnd.br fortal.br fot.br foz.br fst.br g12.br geo.br ggf.br goiania.br gov.br // gov.br 26 states + df https://en.wikipedia.org/wiki/States_of_Brazil ac.gov.br al.gov.br am.gov.br ap.gov.br ba.gov.br ce.gov.br df.gov.br es.gov.br go.gov.br ma.gov.br mg.gov.br ms.gov.br mt.gov.br pa.gov.br pb.gov.br pe.gov.br pi.gov.br pr.gov.br rj.gov.br rn.gov.br ro.gov.br rr.gov.br rs.gov.br sc.gov.br se.gov.br sp.gov.br to.gov.br gru.br imb.br ind.br inf.br jab.br jampa.br jdf.br joinville.br jor.br jus.br leg.br lel.br log.br londrina.br macapa.br maceio.br manaus.br maringa.br mat.br med.br mil.br morena.br mp.br mus.br natal.br net.br niteroi.br *.nom.br not.br ntr.br odo.br ong.br org.br osasco.br palmas.br poa.br ppg.br pro.br psc.br psi.br pvh.br qsl.br radio.br rec.br recife.br rep.br ribeirao.br rio.br riobranco.br riopreto.br salvador.br sampa.br santamaria.br santoandre.br saobernardo.br saogonca.br seg.br sjc.br slg.br slz.br sorocaba.br srv.br taxi.br tc.br tec.br teo.br the.br tmp.br trd.br tur.br tv.br udi.br vet.br vix.br vlog.br wiki.br zlg.br // bs : http://www.nic.bs/rules.html bs com.bs net.bs org.bs edu.bs gov.bs // bt : https://en.wikipedia.org/wiki/.bt bt com.bt edu.bt gov.bt net.bt org.bt // bv : No registrations at this time. // Submitted by registry bv // bw : https://en.wikipedia.org/wiki/.bw // http://www.gobin.info/domainname/bw.doc // list of other 2nd level tlds ? bw co.bw org.bw // by : https://en.wikipedia.org/wiki/.by // http://tld.by/rules_2006_en.html // list of other 2nd level tlds ? by gov.by mil.by // Official information does not indicate that com.by is a reserved // second-level domain, but it's being used as one (see www.google.com.by and // www.yahoo.com.by, for example), so we list it here for safety's sake. com.by // http://hoster.by/ of.by // bz : https://en.wikipedia.org/wiki/.bz // http://www.belizenic.bz/ bz com.bz net.bz org.bz edu.bz gov.bz // ca : https://en.wikipedia.org/wiki/.ca ca // ca geographical names ab.ca bc.ca mb.ca nb.ca nf.ca nl.ca ns.ca nt.ca nu.ca on.ca pe.ca qc.ca sk.ca yk.ca // gc.ca: https://en.wikipedia.org/wiki/.gc.ca // see also: http://registry.gc.ca/en/SubdomainFAQ gc.ca // cat : https://en.wikipedia.org/wiki/.cat cat // cc : https://en.wikipedia.org/wiki/.cc cc // cd : https://en.wikipedia.org/wiki/.cd // see also: https://www.nic.cd/domain/insertDomain_2.jsp?act=1 cd gov.cd // cf : https://en.wikipedia.org/wiki/.cf cf // cg : https://en.wikipedia.org/wiki/.cg cg // ch : https://en.wikipedia.org/wiki/.ch ch // ci : https://en.wikipedia.org/wiki/.ci // http://www.nic.ci/index.php?page=charte ci org.ci or.ci com.ci co.ci edu.ci ed.ci ac.ci net.ci go.ci asso.ci aéroport.ci int.ci presse.ci md.ci gouv.ci // ck : https://en.wikipedia.org/wiki/.ck *.ck !www.ck // cl : https://www.nic.cl // Confirmed by .CL registry cl co.cl gob.cl gov.cl mil.cl // cm : https://en.wikipedia.org/wiki/.cm plus bug 981927 cm co.cm com.cm gov.cm net.cm // cn : https://en.wikipedia.org/wiki/.cn // Submitted by registry cn ac.cn com.cn edu.cn gov.cn net.cn org.cn mil.cn 公司.cn 网络.cn 網絡.cn // cn geographic names ah.cn bj.cn cq.cn fj.cn gd.cn gs.cn gz.cn gx.cn ha.cn hb.cn he.cn hi.cn hl.cn hn.cn jl.cn js.cn jx.cn ln.cn nm.cn nx.cn qh.cn sc.cn sd.cn sh.cn sn.cn sx.cn tj.cn xj.cn xz.cn yn.cn zj.cn hk.cn mo.cn tw.cn // co : https://en.wikipedia.org/wiki/.co // Submitted by registry co arts.co com.co edu.co firm.co gov.co info.co int.co mil.co net.co nom.co org.co rec.co web.co // com : https://en.wikipedia.org/wiki/.com com // coop : https://en.wikipedia.org/wiki/.coop coop // cr : http://www.nic.cr/niccr_publico/showRegistroDominiosScreen.do cr ac.cr co.cr ed.cr fi.cr go.cr or.cr sa.cr // cu : https://en.wikipedia.org/wiki/.cu cu com.cu edu.cu org.cu net.cu gov.cu inf.cu // cv : https://en.wikipedia.org/wiki/.cv // cv : http://www.dns.cv/tldcv_portal/do?com=DS;5446457100;111;+PAGE(4000018)+K-CAT-CODIGO(RDOM)+RCNT(100); <- registration rules cv com.cv edu.cv int.cv nome.cv org.cv // cw : http://www.una.cw/cw_registry/ // Confirmed by registry 2013-03-26 cw com.cw edu.cw net.cw org.cw // cx : https://en.wikipedia.org/wiki/.cx // list of other 2nd level tlds ? cx gov.cx // cy : http://www.nic.cy/ // Submitted by registry Panayiotou Fotia // namespace policies URL https://www.nic.cy/portal//sites/default/files/symfonia_gia_eggrafi.pdf cy ac.cy biz.cy com.cy ekloges.cy gov.cy ltd.cy mil.cy net.cy org.cy press.cy pro.cy tm.cy // cz : https://en.wikipedia.org/wiki/.cz cz // de : https://en.wikipedia.org/wiki/.de // Confirmed by registry (with technical // reservations) 2008-07-01 de // dj : https://en.wikipedia.org/wiki/.dj dj // dk : https://en.wikipedia.org/wiki/.dk // Confirmed by registry 2008-06-17 dk // dm : https://en.wikipedia.org/wiki/.dm dm com.dm net.dm org.dm edu.dm gov.dm // do : https://en.wikipedia.org/wiki/.do do art.do com.do edu.do gob.do gov.do mil.do net.do org.do sld.do web.do // dz : http://www.nic.dz/images/pdf_nic/charte.pdf dz art.dz asso.dz com.dz edu.dz gov.dz org.dz net.dz pol.dz soc.dz tm.dz // ec : http://www.nic.ec/reg/paso1.asp // Submitted by registry ec com.ec info.ec net.ec fin.ec k12.ec med.ec pro.ec org.ec edu.ec gov.ec gob.ec mil.ec // edu : https://en.wikipedia.org/wiki/.edu edu // ee : http://www.eenet.ee/EENet/dom_reeglid.html#lisa_B ee edu.ee gov.ee riik.ee lib.ee med.ee com.ee pri.ee aip.ee org.ee fie.ee // eg : https://en.wikipedia.org/wiki/.eg eg com.eg edu.eg eun.eg gov.eg mil.eg name.eg net.eg org.eg sci.eg // er : https://en.wikipedia.org/wiki/.er *.er // es : https://www.nic.es/site_ingles/ingles/dominios/index.html es com.es nom.es org.es gob.es edu.es // et : https://en.wikipedia.org/wiki/.et et com.et gov.et org.et edu.et biz.et name.et info.et net.et // eu : https://en.wikipedia.org/wiki/.eu eu // fi : https://en.wikipedia.org/wiki/.fi fi // aland.fi : https://en.wikipedia.org/wiki/.ax // This domain is being phased out in favor of .ax. As there are still many // domains under aland.fi, we still keep it on the list until aland.fi is // completely removed. // TODO: Check for updates (expected to be phased out around Q1/2009) aland.fi // fj : http://domains.fj/ // Submitted by registry 2020-02-11 fj ac.fj biz.fj com.fj gov.fj info.fj mil.fj name.fj net.fj org.fj pro.fj // fk : https://en.wikipedia.org/wiki/.fk *.fk // fm : https://en.wikipedia.org/wiki/.fm com.fm edu.fm net.fm org.fm fm // fo : https://en.wikipedia.org/wiki/.fo fo // fr : https://www.afnic.fr/ https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf fr asso.fr com.fr gouv.fr nom.fr prd.fr tm.fr // Former "domaines sectoriels", still registration suffixes aeroport.fr avocat.fr avoues.fr cci.fr chambagri.fr chirurgiens-dentistes.fr experts-comptables.fr geometre-expert.fr greta.fr huissier-justice.fr medecin.fr notaires.fr pharmacien.fr port.fr veterinaire.fr // ga : https://en.wikipedia.org/wiki/.ga ga // gb : This registry is effectively dormant // Submitted by registry gb // gd : https://en.wikipedia.org/wiki/.gd edu.gd gov.gd gd // ge : http://www.nic.net.ge/policy_en.pdf ge com.ge edu.ge gov.ge org.ge mil.ge net.ge pvt.ge // gf : https://en.wikipedia.org/wiki/.gf gf // gg : http://www.channelisles.net/register-domains/ // Confirmed by registry 2013-11-28 gg co.gg net.gg org.gg // gh : https://en.wikipedia.org/wiki/.gh // see also: http://www.nic.gh/reg_now.php // Although domains directly at second level are not possible at the moment, // they have been possible for some time and may come back. gh com.gh edu.gh gov.gh org.gh mil.gh // gi : http://www.nic.gi/rules.html gi com.gi ltd.gi gov.gi mod.gi edu.gi org.gi // gl : https://en.wikipedia.org/wiki/.gl // http://nic.gl gl co.gl com.gl edu.gl net.gl org.gl // gm : http://www.nic.gm/htmlpages%5Cgm-policy.htm gm // gn : http://psg.com/dns/gn/gn.txt // Submitted by registry gn ac.gn com.gn edu.gn gov.gn org.gn net.gn // gov : https://en.wikipedia.org/wiki/.gov gov // gp : http://www.nic.gp/index.php?lang=en gp com.gp net.gp mobi.gp edu.gp org.gp asso.gp // gq : https://en.wikipedia.org/wiki/.gq gq // gr : https://grweb.ics.forth.gr/english/1617-B-2005.html // Submitted by registry gr com.gr edu.gr net.gr org.gr gov.gr // gs : https://en.wikipedia.org/wiki/.gs gs // gt : https://www.gt/sitio/registration_policy.php?lang=en gt com.gt edu.gt gob.gt ind.gt mil.gt net.gt org.gt // gu : http://gadao.gov.gu/register.html // University of Guam : https://www.uog.edu // Submitted by uognoc@triton.uog.edu gu com.gu edu.gu gov.gu guam.gu info.gu net.gu org.gu web.gu // gw : https://en.wikipedia.org/wiki/.gw // gw : https://nic.gw/regras/ gw // gy : https://en.wikipedia.org/wiki/.gy // http://registry.gy/ gy co.gy com.gy edu.gy gov.gy net.gy org.gy // hk : https://www.hkirc.hk // Submitted by registry hk com.hk edu.hk gov.hk idv.hk net.hk org.hk 公司.hk 教育.hk 敎育.hk 政府.hk 個人.hk 个人.hk 箇人.hk 網络.hk 网络.hk 组織.hk 網絡.hk 网絡.hk 组织.hk 組織.hk 組织.hk // hm : https://en.wikipedia.org/wiki/.hm hm // hn : http://www.nic.hn/politicas/ps02,,05.html hn com.hn edu.hn org.hn net.hn mil.hn gob.hn // hr : http://www.dns.hr/documents/pdf/HRTLD-regulations.pdf hr iz.hr from.hr name.hr com.hr // ht : http://www.nic.ht/info/charte.cfm ht com.ht shop.ht firm.ht info.ht adult.ht net.ht pro.ht org.ht med.ht art.ht coop.ht pol.ht asso.ht edu.ht rel.ht gouv.ht perso.ht // hu : http://www.domain.hu/domain/English/sld.html // Confirmed by registry 2008-06-12 hu co.hu info.hu org.hu priv.hu sport.hu tm.hu 2000.hu agrar.hu bolt.hu casino.hu city.hu erotica.hu erotika.hu film.hu forum.hu games.hu hotel.hu ingatlan.hu jogasz.hu konyvelo.hu lakas.hu media.hu news.hu reklam.hu sex.hu shop.hu suli.hu szex.hu tozsde.hu utazas.hu video.hu // id : https://pandi.id/en/domain/registration-requirements/ id ac.id biz.id co.id desa.id go.id mil.id my.id net.id or.id ponpes.id sch.id web.id // ie : https://en.wikipedia.org/wiki/.ie ie gov.ie // il : http://www.isoc.org.il/domains/ // see also: https://en.isoc.org.il/il-cctld/registration-rules // ISOC-IL (operated by .il Registry) il ac.il co.il gov.il idf.il k12.il muni.il net.il org.il // xn--4dbrk0ce ("Israel", Hebrew) : IL ישראל // xn--4dbgdty6c.xn--4dbrk0ce. אקדמיה.ישראל // xn--5dbhl8d.xn--4dbrk0ce. ישוב.ישראל // xn--8dbq2a.xn--4dbrk0ce. צהל.ישראל // xn--hebda8b.xn--4dbrk0ce. ממשל.ישראל // im : https://www.nic.im/ // Submitted by registry im ac.im co.im com.im ltd.co.im net.im org.im plc.co.im tt.im tv.im // in : https://en.wikipedia.org/wiki/.in // see also: https://registry.in/policies // Please note, that nic.in is not an official eTLD, but used by most // government institutions. in 5g.in 6g.in ac.in ai.in am.in bihar.in biz.in business.in ca.in cn.in co.in com.in coop.in cs.in delhi.in dr.in edu.in er.in firm.in gen.in gov.in gujarat.in ind.in info.in int.in internet.in io.in me.in mil.in net.in nic.in org.in pg.in post.in pro.in res.in travel.in tv.in uk.in up.in us.in // info : https://en.wikipedia.org/wiki/.info info // int : https://en.wikipedia.org/wiki/.int // Confirmed by registry 2008-06-18 int eu.int // io : http://www.nic.io/rules.htm // list of other 2nd level tlds ? io com.io // iq : http://www.cmc.iq/english/iq/iqregister1.htm iq gov.iq edu.iq mil.iq com.iq org.iq net.iq // ir : http://www.nic.ir/Terms_and_Conditions_ir,_Appendix_1_Domain_Rules // Also see http://www.nic.ir/Internationalized_Domain_Names // Two .ir entries added at request of , 2010-04-16 ir ac.ir co.ir gov.ir id.ir net.ir org.ir sch.ir // xn--mgba3a4f16a.ir (.ir, Persian YEH) ایران.ir // xn--mgba3a4fra.ir (.ir, Arabic YEH) ايران.ir // is : http://www.isnic.is/domain/rules.php // Confirmed by registry 2008-12-06 is net.is com.is edu.is gov.is org.is int.is // it : https://en.wikipedia.org/wiki/.it it gov.it edu.it // Reserved geo-names (regions and provinces): // https://www.nic.it/sites/default/files/archivio/docs/Regulation_assignation_v7.1.pdf // Regions abr.it abruzzo.it aosta-valley.it aostavalley.it bas.it basilicata.it cal.it calabria.it cam.it campania.it emilia-romagna.it emiliaromagna.it emr.it friuli-v-giulia.it friuli-ve-giulia.it friuli-vegiulia.it friuli-venezia-giulia.it friuli-veneziagiulia.it friuli-vgiulia.it friuliv-giulia.it friulive-giulia.it friulivegiulia.it friulivenezia-giulia.it friuliveneziagiulia.it friulivgiulia.it fvg.it laz.it lazio.it lig.it liguria.it lom.it lombardia.it lombardy.it lucania.it mar.it marche.it mol.it molise.it piedmont.it piemonte.it pmn.it pug.it puglia.it sar.it sardegna.it sardinia.it sic.it sicilia.it sicily.it taa.it tos.it toscana.it trentin-sud-tirol.it trentin-süd-tirol.it trentin-sudtirol.it trentin-südtirol.it trentin-sued-tirol.it trentin-suedtirol.it trentino-a-adige.it trentino-aadige.it trentino-alto-adige.it trentino-altoadige.it trentino-s-tirol.it trentino-stirol.it trentino-sud-tirol.it trentino-süd-tirol.it trentino-sudtirol.it trentino-südtirol.it trentino-sued-tirol.it trentino-suedtirol.it trentino.it trentinoa-adige.it trentinoaadige.it trentinoalto-adige.it trentinoaltoadige.it trentinos-tirol.it trentinostirol.it trentinosud-tirol.it trentinosüd-tirol.it trentinosudtirol.it trentinosüdtirol.it trentinosued-tirol.it trentinosuedtirol.it trentinsud-tirol.it trentinsüd-tirol.it trentinsudtirol.it trentinsüdtirol.it trentinsued-tirol.it trentinsuedtirol.it tuscany.it umb.it umbria.it val-d-aosta.it val-daosta.it vald-aosta.it valdaosta.it valle-aosta.it valle-d-aosta.it valle-daosta.it valleaosta.it valled-aosta.it valledaosta.it vallee-aoste.it vallée-aoste.it vallee-d-aoste.it vallée-d-aoste.it valleeaoste.it valléeaoste.it valleedaoste.it valléedaoste.it vao.it vda.it ven.it veneto.it // Provinces ag.it agrigento.it al.it alessandria.it alto-adige.it altoadige.it an.it ancona.it andria-barletta-trani.it andria-trani-barletta.it andriabarlettatrani.it andriatranibarletta.it ao.it aosta.it aoste.it ap.it aq.it aquila.it ar.it arezzo.it ascoli-piceno.it ascolipiceno.it asti.it at.it av.it avellino.it ba.it balsan-sudtirol.it balsan-südtirol.it balsan-suedtirol.it balsan.it bari.it barletta-trani-andria.it barlettatraniandria.it belluno.it benevento.it bergamo.it bg.it bi.it biella.it bl.it bn.it bo.it bologna.it bolzano-altoadige.it bolzano.it bozen-sudtirol.it bozen-südtirol.it bozen-suedtirol.it bozen.it br.it brescia.it brindisi.it bs.it bt.it bulsan-sudtirol.it bulsan-südtirol.it bulsan-suedtirol.it bulsan.it bz.it ca.it cagliari.it caltanissetta.it campidano-medio.it campidanomedio.it campobasso.it carbonia-iglesias.it carboniaiglesias.it carrara-massa.it carraramassa.it caserta.it catania.it catanzaro.it cb.it ce.it cesena-forli.it cesena-forlì.it cesenaforli.it cesenaforlì.it ch.it chieti.it ci.it cl.it cn.it co.it como.it cosenza.it cr.it cremona.it crotone.it cs.it ct.it cuneo.it cz.it dell-ogliastra.it dellogliastra.it en.it enna.it fc.it fe.it fermo.it ferrara.it fg.it fi.it firenze.it florence.it fm.it foggia.it forli-cesena.it forlì-cesena.it forlicesena.it forlìcesena.it fr.it frosinone.it ge.it genoa.it genova.it go.it gorizia.it gr.it grosseto.it iglesias-carbonia.it iglesiascarbonia.it im.it imperia.it is.it isernia.it kr.it la-spezia.it laquila.it laspezia.it latina.it lc.it le.it lecce.it lecco.it li.it livorno.it lo.it lodi.it lt.it lu.it lucca.it macerata.it mantova.it massa-carrara.it massacarrara.it matera.it mb.it mc.it me.it medio-campidano.it mediocampidano.it messina.it mi.it milan.it milano.it mn.it mo.it modena.it monza-brianza.it monza-e-della-brianza.it monza.it monzabrianza.it monzaebrianza.it monzaedellabrianza.it ms.it mt.it na.it naples.it napoli.it no.it novara.it nu.it nuoro.it og.it ogliastra.it olbia-tempio.it olbiatempio.it or.it oristano.it ot.it pa.it padova.it padua.it palermo.it parma.it pavia.it pc.it pd.it pe.it perugia.it pesaro-urbino.it pesarourbino.it pescara.it pg.it pi.it piacenza.it pisa.it pistoia.it pn.it po.it pordenone.it potenza.it pr.it prato.it pt.it pu.it pv.it pz.it ra.it ragusa.it ravenna.it rc.it re.it reggio-calabria.it reggio-emilia.it reggiocalabria.it reggioemilia.it rg.it ri.it rieti.it rimini.it rm.it rn.it ro.it roma.it rome.it rovigo.it sa.it salerno.it sassari.it savona.it si.it siena.it siracusa.it so.it sondrio.it sp.it sr.it ss.it suedtirol.it südtirol.it sv.it ta.it taranto.it te.it tempio-olbia.it tempioolbia.it teramo.it terni.it tn.it to.it torino.it tp.it tr.it trani-andria-barletta.it trani-barletta-andria.it traniandriabarletta.it tranibarlettaandria.it trapani.it trento.it treviso.it trieste.it ts.it turin.it tv.it ud.it udine.it urbino-pesaro.it urbinopesaro.it va.it varese.it vb.it vc.it ve.it venezia.it venice.it verbania.it vercelli.it verona.it vi.it vibo-valentia.it vibovalentia.it vicenza.it viterbo.it vr.it vs.it vt.it vv.it // je : http://www.channelisles.net/register-domains/ // Confirmed by registry 2013-11-28 je co.je net.je org.je // jm : http://www.com.jm/register.html *.jm // jo : http://www.dns.jo/Registration_policy.aspx jo com.jo org.jo net.jo edu.jo sch.jo gov.jo mil.jo name.jo // jobs : https://en.wikipedia.org/wiki/.jobs jobs // jp : https://en.wikipedia.org/wiki/.jp // http://jprs.co.jp/en/jpdomain.html // Submitted by registry jp // jp organizational type names ac.jp ad.jp co.jp ed.jp go.jp gr.jp lg.jp ne.jp or.jp // jp prefecture type names aichi.jp akita.jp aomori.jp chiba.jp ehime.jp fukui.jp fukuoka.jp fukushima.jp gifu.jp gunma.jp hiroshima.jp hokkaido.jp hyogo.jp ibaraki.jp ishikawa.jp iwate.jp kagawa.jp kagoshima.jp kanagawa.jp kochi.jp kumamoto.jp kyoto.jp mie.jp miyagi.jp miyazaki.jp nagano.jp nagasaki.jp nara.jp niigata.jp oita.jp okayama.jp okinawa.jp osaka.jp saga.jp saitama.jp shiga.jp shimane.jp shizuoka.jp tochigi.jp tokushima.jp tokyo.jp tottori.jp toyama.jp wakayama.jp yamagata.jp yamaguchi.jp yamanashi.jp 栃木.jp 愛知.jp 愛媛.jp 兵庫.jp 熊本.jp 茨城.jp 北海道.jp 千葉.jp 和歌山.jp 長崎.jp 長野.jp 新潟.jp 青森.jp 静岡.jp 東京.jp 石川.jp 埼玉.jp 三重.jp 京都.jp 佐賀.jp 大分.jp 大阪.jp 奈良.jp 宮城.jp 宮崎.jp 富山.jp 山口.jp 山形.jp 山梨.jp 岩手.jp 岐阜.jp 岡山.jp 島根.jp 広島.jp 徳島.jp 沖縄.jp 滋賀.jp 神奈川.jp 福井.jp 福岡.jp 福島.jp 秋田.jp 群馬.jp 香川.jp 高知.jp 鳥取.jp 鹿児島.jp // jp geographic type names // http://jprs.jp/doc/rule/saisoku-1.html *.kawasaki.jp *.kitakyushu.jp *.kobe.jp *.nagoya.jp *.sapporo.jp *.sendai.jp *.yokohama.jp !city.kawasaki.jp !city.kitakyushu.jp !city.kobe.jp !city.nagoya.jp !city.sapporo.jp !city.sendai.jp !city.yokohama.jp // 4th level registration aisai.aichi.jp ama.aichi.jp anjo.aichi.jp asuke.aichi.jp chiryu.aichi.jp chita.aichi.jp fuso.aichi.jp gamagori.aichi.jp handa.aichi.jp hazu.aichi.jp hekinan.aichi.jp higashiura.aichi.jp ichinomiya.aichi.jp inazawa.aichi.jp inuyama.aichi.jp isshiki.aichi.jp iwakura.aichi.jp kanie.aichi.jp kariya.aichi.jp kasugai.aichi.jp kira.aichi.jp kiyosu.aichi.jp komaki.aichi.jp konan.aichi.jp kota.aichi.jp mihama.aichi.jp miyoshi.aichi.jp nishio.aichi.jp nisshin.aichi.jp obu.aichi.jp oguchi.aichi.jp oharu.aichi.jp okazaki.aichi.jp owariasahi.aichi.jp seto.aichi.jp shikatsu.aichi.jp shinshiro.aichi.jp shitara.aichi.jp tahara.aichi.jp takahama.aichi.jp tobishima.aichi.jp toei.aichi.jp togo.aichi.jp tokai.aichi.jp tokoname.aichi.jp toyoake.aichi.jp toyohashi.aichi.jp toyokawa.aichi.jp toyone.aichi.jp toyota.aichi.jp tsushima.aichi.jp yatomi.aichi.jp akita.akita.jp daisen.akita.jp fujisato.akita.jp gojome.akita.jp hachirogata.akita.jp happou.akita.jp higashinaruse.akita.jp honjo.akita.jp honjyo.akita.jp ikawa.akita.jp kamikoani.akita.jp kamioka.akita.jp katagami.akita.jp kazuno.akita.jp kitaakita.akita.jp kosaka.akita.jp kyowa.akita.jp misato.akita.jp mitane.akita.jp moriyoshi.akita.jp nikaho.akita.jp noshiro.akita.jp odate.akita.jp oga.akita.jp ogata.akita.jp semboku.akita.jp yokote.akita.jp yurihonjo.akita.jp aomori.aomori.jp gonohe.aomori.jp hachinohe.aomori.jp hashikami.aomori.jp hiranai.aomori.jp hirosaki.aomori.jp itayanagi.aomori.jp kuroishi.aomori.jp misawa.aomori.jp mutsu.aomori.jp nakadomari.aomori.jp noheji.aomori.jp oirase.aomori.jp owani.aomori.jp rokunohe.aomori.jp sannohe.aomori.jp shichinohe.aomori.jp shingo.aomori.jp takko.aomori.jp towada.aomori.jp tsugaru.aomori.jp tsuruta.aomori.jp abiko.chiba.jp asahi.chiba.jp chonan.chiba.jp chosei.chiba.jp choshi.chiba.jp chuo.chiba.jp funabashi.chiba.jp futtsu.chiba.jp hanamigawa.chiba.jp ichihara.chiba.jp ichikawa.chiba.jp ichinomiya.chiba.jp inzai.chiba.jp isumi.chiba.jp kamagaya.chiba.jp kamogawa.chiba.jp kashiwa.chiba.jp katori.chiba.jp katsuura.chiba.jp kimitsu.chiba.jp kisarazu.chiba.jp kozaki.chiba.jp kujukuri.chiba.jp kyonan.chiba.jp matsudo.chiba.jp midori.chiba.jp mihama.chiba.jp minamiboso.chiba.jp mobara.chiba.jp mutsuzawa.chiba.jp nagara.chiba.jp nagareyama.chiba.jp narashino.chiba.jp narita.chiba.jp noda.chiba.jp oamishirasato.chiba.jp omigawa.chiba.jp onjuku.chiba.jp otaki.chiba.jp sakae.chiba.jp sakura.chiba.jp shimofusa.chiba.jp shirako.chiba.jp shiroi.chiba.jp shisui.chiba.jp sodegaura.chiba.jp sosa.chiba.jp tako.chiba.jp tateyama.chiba.jp togane.chiba.jp tohnosho.chiba.jp tomisato.chiba.jp urayasu.chiba.jp yachimata.chiba.jp yachiyo.chiba.jp yokaichiba.chiba.jp yokoshibahikari.chiba.jp yotsukaido.chiba.jp ainan.ehime.jp honai.ehime.jp ikata.ehime.jp imabari.ehime.jp iyo.ehime.jp kamijima.ehime.jp kihoku.ehime.jp kumakogen.ehime.jp masaki.ehime.jp matsuno.ehime.jp matsuyama.ehime.jp namikata.ehime.jp niihama.ehime.jp ozu.ehime.jp saijo.ehime.jp seiyo.ehime.jp shikokuchuo.ehime.jp tobe.ehime.jp toon.ehime.jp uchiko.ehime.jp uwajima.ehime.jp yawatahama.ehime.jp echizen.fukui.jp eiheiji.fukui.jp fukui.fukui.jp ikeda.fukui.jp katsuyama.fukui.jp mihama.fukui.jp minamiechizen.fukui.jp obama.fukui.jp ohi.fukui.jp ono.fukui.jp sabae.fukui.jp sakai.fukui.jp takahama.fukui.jp tsuruga.fukui.jp wakasa.fukui.jp ashiya.fukuoka.jp buzen.fukuoka.jp chikugo.fukuoka.jp chikuho.fukuoka.jp chikujo.fukuoka.jp chikushino.fukuoka.jp chikuzen.fukuoka.jp chuo.fukuoka.jp dazaifu.fukuoka.jp fukuchi.fukuoka.jp hakata.fukuoka.jp higashi.fukuoka.jp hirokawa.fukuoka.jp hisayama.fukuoka.jp iizuka.fukuoka.jp inatsuki.fukuoka.jp kaho.fukuoka.jp kasuga.fukuoka.jp kasuya.fukuoka.jp kawara.fukuoka.jp keisen.fukuoka.jp koga.fukuoka.jp kurate.fukuoka.jp kurogi.fukuoka.jp kurume.fukuoka.jp minami.fukuoka.jp miyako.fukuoka.jp miyama.fukuoka.jp miyawaka.fukuoka.jp mizumaki.fukuoka.jp munakata.fukuoka.jp nakagawa.fukuoka.jp nakama.fukuoka.jp nishi.fukuoka.jp nogata.fukuoka.jp ogori.fukuoka.jp okagaki.fukuoka.jp okawa.fukuoka.jp oki.fukuoka.jp omuta.fukuoka.jp onga.fukuoka.jp onojo.fukuoka.jp oto.fukuoka.jp saigawa.fukuoka.jp sasaguri.fukuoka.jp shingu.fukuoka.jp shinyoshitomi.fukuoka.jp shonai.fukuoka.jp soeda.fukuoka.jp sue.fukuoka.jp tachiarai.fukuoka.jp tagawa.fukuoka.jp takata.fukuoka.jp toho.fukuoka.jp toyotsu.fukuoka.jp tsuiki.fukuoka.jp ukiha.fukuoka.jp umi.fukuoka.jp usui.fukuoka.jp yamada.fukuoka.jp yame.fukuoka.jp yanagawa.fukuoka.jp yukuhashi.fukuoka.jp aizubange.fukushima.jp aizumisato.fukushima.jp aizuwakamatsu.fukushima.jp asakawa.fukushima.jp bandai.fukushima.jp date.fukushima.jp fukushima.fukushima.jp furudono.fukushima.jp futaba.fukushima.jp hanawa.fukushima.jp higashi.fukushima.jp hirata.fukushima.jp hirono.fukushima.jp iitate.fukushima.jp inawashiro.fukushima.jp ishikawa.fukushima.jp iwaki.fukushima.jp izumizaki.fukushima.jp kagamiishi.fukushima.jp kaneyama.fukushima.jp kawamata.fukushima.jp kitakata.fukushima.jp kitashiobara.fukushima.jp koori.fukushima.jp koriyama.fukushima.jp kunimi.fukushima.jp miharu.fukushima.jp mishima.fukushima.jp namie.fukushima.jp nango.fukushima.jp nishiaizu.fukushima.jp nishigo.fukushima.jp okuma.fukushima.jp omotego.fukushima.jp ono.fukushima.jp otama.fukushima.jp samegawa.fukushima.jp shimogo.fukushima.jp shirakawa.fukushima.jp showa.fukushima.jp soma.fukushima.jp sukagawa.fukushima.jp taishin.fukushima.jp tamakawa.fukushima.jp tanagura.fukushima.jp tenei.fukushima.jp yabuki.fukushima.jp yamato.fukushima.jp yamatsuri.fukushima.jp yanaizu.fukushima.jp yugawa.fukushima.jp anpachi.gifu.jp ena.gifu.jp gifu.gifu.jp ginan.gifu.jp godo.gifu.jp gujo.gifu.jp hashima.gifu.jp hichiso.gifu.jp hida.gifu.jp higashishirakawa.gifu.jp ibigawa.gifu.jp ikeda.gifu.jp kakamigahara.gifu.jp kani.gifu.jp kasahara.gifu.jp kasamatsu.gifu.jp kawaue.gifu.jp kitagata.gifu.jp mino.gifu.jp minokamo.gifu.jp mitake.gifu.jp mizunami.gifu.jp motosu.gifu.jp nakatsugawa.gifu.jp ogaki.gifu.jp sakahogi.gifu.jp seki.gifu.jp sekigahara.gifu.jp shirakawa.gifu.jp tajimi.gifu.jp takayama.gifu.jp tarui.gifu.jp toki.gifu.jp tomika.gifu.jp wanouchi.gifu.jp yamagata.gifu.jp yaotsu.gifu.jp yoro.gifu.jp annaka.gunma.jp chiyoda.gunma.jp fujioka.gunma.jp higashiagatsuma.gunma.jp isesaki.gunma.jp itakura.gunma.jp kanna.gunma.jp kanra.gunma.jp katashina.gunma.jp kawaba.gunma.jp kiryu.gunma.jp kusatsu.gunma.jp maebashi.gunma.jp meiwa.gunma.jp midori.gunma.jp minakami.gunma.jp naganohara.gunma.jp nakanojo.gunma.jp nanmoku.gunma.jp numata.gunma.jp oizumi.gunma.jp ora.gunma.jp ota.gunma.jp shibukawa.gunma.jp shimonita.gunma.jp shinto.gunma.jp showa.gunma.jp takasaki.gunma.jp takayama.gunma.jp tamamura.gunma.jp tatebayashi.gunma.jp tomioka.gunma.jp tsukiyono.gunma.jp tsumagoi.gunma.jp ueno.gunma.jp yoshioka.gunma.jp asaminami.hiroshima.jp daiwa.hiroshima.jp etajima.hiroshima.jp fuchu.hiroshima.jp fukuyama.hiroshima.jp hatsukaichi.hiroshima.jp higashihiroshima.hiroshima.jp hongo.hiroshima.jp jinsekikogen.hiroshima.jp kaita.hiroshima.jp kui.hiroshima.jp kumano.hiroshima.jp kure.hiroshima.jp mihara.hiroshima.jp miyoshi.hiroshima.jp naka.hiroshima.jp onomichi.hiroshima.jp osakikamijima.hiroshima.jp otake.hiroshima.jp saka.hiroshima.jp sera.hiroshima.jp seranishi.hiroshima.jp shinichi.hiroshima.jp shobara.hiroshima.jp takehara.hiroshima.jp abashiri.hokkaido.jp abira.hokkaido.jp aibetsu.hokkaido.jp akabira.hokkaido.jp akkeshi.hokkaido.jp asahikawa.hokkaido.jp ashibetsu.hokkaido.jp ashoro.hokkaido.jp assabu.hokkaido.jp atsuma.hokkaido.jp bibai.hokkaido.jp biei.hokkaido.jp bifuka.hokkaido.jp bihoro.hokkaido.jp biratori.hokkaido.jp chippubetsu.hokkaido.jp chitose.hokkaido.jp date.hokkaido.jp ebetsu.hokkaido.jp embetsu.hokkaido.jp eniwa.hokkaido.jp erimo.hokkaido.jp esan.hokkaido.jp esashi.hokkaido.jp fukagawa.hokkaido.jp fukushima.hokkaido.jp furano.hokkaido.jp furubira.hokkaido.jp haboro.hokkaido.jp hakodate.hokkaido.jp hamatonbetsu.hokkaido.jp hidaka.hokkaido.jp higashikagura.hokkaido.jp higashikawa.hokkaido.jp hiroo.hokkaido.jp hokuryu.hokkaido.jp hokuto.hokkaido.jp honbetsu.hokkaido.jp horokanai.hokkaido.jp horonobe.hokkaido.jp ikeda.hokkaido.jp imakane.hokkaido.jp ishikari.hokkaido.jp iwamizawa.hokkaido.jp iwanai.hokkaido.jp kamifurano.hokkaido.jp kamikawa.hokkaido.jp kamishihoro.hokkaido.jp kamisunagawa.hokkaido.jp kamoenai.hokkaido.jp kayabe.hokkaido.jp kembuchi.hokkaido.jp kikonai.hokkaido.jp kimobetsu.hokkaido.jp kitahiroshima.hokkaido.jp kitami.hokkaido.jp kiyosato.hokkaido.jp koshimizu.hokkaido.jp kunneppu.hokkaido.jp kuriyama.hokkaido.jp kuromatsunai.hokkaido.jp kushiro.hokkaido.jp kutchan.hokkaido.jp kyowa.hokkaido.jp mashike.hokkaido.jp matsumae.hokkaido.jp mikasa.hokkaido.jp minamifurano.hokkaido.jp mombetsu.hokkaido.jp moseushi.hokkaido.jp mukawa.hokkaido.jp muroran.hokkaido.jp naie.hokkaido.jp nakagawa.hokkaido.jp nakasatsunai.hokkaido.jp nakatombetsu.hokkaido.jp nanae.hokkaido.jp nanporo.hokkaido.jp nayoro.hokkaido.jp nemuro.hokkaido.jp niikappu.hokkaido.jp niki.hokkaido.jp nishiokoppe.hokkaido.jp noboribetsu.hokkaido.jp numata.hokkaido.jp obihiro.hokkaido.jp obira.hokkaido.jp oketo.hokkaido.jp okoppe.hokkaido.jp otaru.hokkaido.jp otobe.hokkaido.jp otofuke.hokkaido.jp otoineppu.hokkaido.jp oumu.hokkaido.jp ozora.hokkaido.jp pippu.hokkaido.jp rankoshi.hokkaido.jp rebun.hokkaido.jp rikubetsu.hokkaido.jp rishiri.hokkaido.jp rishirifuji.hokkaido.jp saroma.hokkaido.jp sarufutsu.hokkaido.jp shakotan.hokkaido.jp shari.hokkaido.jp shibecha.hokkaido.jp shibetsu.hokkaido.jp shikabe.hokkaido.jp shikaoi.hokkaido.jp shimamaki.hokkaido.jp shimizu.hokkaido.jp shimokawa.hokkaido.jp shinshinotsu.hokkaido.jp shintoku.hokkaido.jp shiranuka.hokkaido.jp shiraoi.hokkaido.jp shiriuchi.hokkaido.jp sobetsu.hokkaido.jp sunagawa.hokkaido.jp taiki.hokkaido.jp takasu.hokkaido.jp takikawa.hokkaido.jp takinoue.hokkaido.jp teshikaga.hokkaido.jp tobetsu.hokkaido.jp tohma.hokkaido.jp tomakomai.hokkaido.jp tomari.hokkaido.jp toya.hokkaido.jp toyako.hokkaido.jp toyotomi.hokkaido.jp toyoura.hokkaido.jp tsubetsu.hokkaido.jp tsukigata.hokkaido.jp urakawa.hokkaido.jp urausu.hokkaido.jp uryu.hokkaido.jp utashinai.hokkaido.jp wakkanai.hokkaido.jp wassamu.hokkaido.jp yakumo.hokkaido.jp yoichi.hokkaido.jp aioi.hyogo.jp akashi.hyogo.jp ako.hyogo.jp amagasaki.hyogo.jp aogaki.hyogo.jp asago.hyogo.jp ashiya.hyogo.jp awaji.hyogo.jp fukusaki.hyogo.jp goshiki.hyogo.jp harima.hyogo.jp himeji.hyogo.jp ichikawa.hyogo.jp inagawa.hyogo.jp itami.hyogo.jp kakogawa.hyogo.jp kamigori.hyogo.jp kamikawa.hyogo.jp kasai.hyogo.jp kasuga.hyogo.jp kawanishi.hyogo.jp miki.hyogo.jp minamiawaji.hyogo.jp nishinomiya.hyogo.jp nishiwaki.hyogo.jp ono.hyogo.jp sanda.hyogo.jp sannan.hyogo.jp sasayama.hyogo.jp sayo.hyogo.jp shingu.hyogo.jp shinonsen.hyogo.jp shiso.hyogo.jp sumoto.hyogo.jp taishi.hyogo.jp taka.hyogo.jp takarazuka.hyogo.jp takasago.hyogo.jp takino.hyogo.jp tamba.hyogo.jp tatsuno.hyogo.jp toyooka.hyogo.jp yabu.hyogo.jp yashiro.hyogo.jp yoka.hyogo.jp yokawa.hyogo.jp ami.ibaraki.jp asahi.ibaraki.jp bando.ibaraki.jp chikusei.ibaraki.jp daigo.ibaraki.jp fujishiro.ibaraki.jp hitachi.ibaraki.jp hitachinaka.ibaraki.jp hitachiomiya.ibaraki.jp hitachiota.ibaraki.jp ibaraki.ibaraki.jp ina.ibaraki.jp inashiki.ibaraki.jp itako.ibaraki.jp iwama.ibaraki.jp joso.ibaraki.jp kamisu.ibaraki.jp kasama.ibaraki.jp kashima.ibaraki.jp kasumigaura.ibaraki.jp koga.ibaraki.jp miho.ibaraki.jp mito.ibaraki.jp moriya.ibaraki.jp naka.ibaraki.jp namegata.ibaraki.jp oarai.ibaraki.jp ogawa.ibaraki.jp omitama.ibaraki.jp ryugasaki.ibaraki.jp sakai.ibaraki.jp sakuragawa.ibaraki.jp shimodate.ibaraki.jp shimotsuma.ibaraki.jp shirosato.ibaraki.jp sowa.ibaraki.jp suifu.ibaraki.jp takahagi.ibaraki.jp tamatsukuri.ibaraki.jp tokai.ibaraki.jp tomobe.ibaraki.jp tone.ibaraki.jp toride.ibaraki.jp tsuchiura.ibaraki.jp tsukuba.ibaraki.jp uchihara.ibaraki.jp ushiku.ibaraki.jp yachiyo.ibaraki.jp yamagata.ibaraki.jp yawara.ibaraki.jp yuki.ibaraki.jp anamizu.ishikawa.jp hakui.ishikawa.jp hakusan.ishikawa.jp kaga.ishikawa.jp kahoku.ishikawa.jp kanazawa.ishikawa.jp kawakita.ishikawa.jp komatsu.ishikawa.jp nakanoto.ishikawa.jp nanao.ishikawa.jp nomi.ishikawa.jp nonoichi.ishikawa.jp noto.ishikawa.jp shika.ishikawa.jp suzu.ishikawa.jp tsubata.ishikawa.jp tsurugi.ishikawa.jp uchinada.ishikawa.jp wajima.ishikawa.jp fudai.iwate.jp fujisawa.iwate.jp hanamaki.iwate.jp hiraizumi.iwate.jp hirono.iwate.jp ichinohe.iwate.jp ichinoseki.iwate.jp iwaizumi.iwate.jp iwate.iwate.jp joboji.iwate.jp kamaishi.iwate.jp kanegasaki.iwate.jp karumai.iwate.jp kawai.iwate.jp kitakami.iwate.jp kuji.iwate.jp kunohe.iwate.jp kuzumaki.iwate.jp miyako.iwate.jp mizusawa.iwate.jp morioka.iwate.jp ninohe.iwate.jp noda.iwate.jp ofunato.iwate.jp oshu.iwate.jp otsuchi.iwate.jp rikuzentakata.iwate.jp shiwa.iwate.jp shizukuishi.iwate.jp sumita.iwate.jp tanohata.iwate.jp tono.iwate.jp yahaba.iwate.jp yamada.iwate.jp ayagawa.kagawa.jp higashikagawa.kagawa.jp kanonji.kagawa.jp kotohira.kagawa.jp manno.kagawa.jp marugame.kagawa.jp mitoyo.kagawa.jp naoshima.kagawa.jp sanuki.kagawa.jp tadotsu.kagawa.jp takamatsu.kagawa.jp tonosho.kagawa.jp uchinomi.kagawa.jp utazu.kagawa.jp zentsuji.kagawa.jp akune.kagoshima.jp amami.kagoshima.jp hioki.kagoshima.jp isa.kagoshima.jp isen.kagoshima.jp izumi.kagoshima.jp kagoshima.kagoshima.jp kanoya.kagoshima.jp kawanabe.kagoshima.jp kinko.kagoshima.jp kouyama.kagoshima.jp makurazaki.kagoshima.jp matsumoto.kagoshima.jp minamitane.kagoshima.jp nakatane.kagoshima.jp nishinoomote.kagoshima.jp satsumasendai.kagoshima.jp soo.kagoshima.jp tarumizu.kagoshima.jp yusui.kagoshima.jp aikawa.kanagawa.jp atsugi.kanagawa.jp ayase.kanagawa.jp chigasaki.kanagawa.jp ebina.kanagawa.jp fujisawa.kanagawa.jp hadano.kanagawa.jp hakone.kanagawa.jp hiratsuka.kanagawa.jp isehara.kanagawa.jp kaisei.kanagawa.jp kamakura.kanagawa.jp kiyokawa.kanagawa.jp matsuda.kanagawa.jp minamiashigara.kanagawa.jp miura.kanagawa.jp nakai.kanagawa.jp ninomiya.kanagawa.jp odawara.kanagawa.jp oi.kanagawa.jp oiso.kanagawa.jp sagamihara.kanagawa.jp samukawa.kanagawa.jp tsukui.kanagawa.jp yamakita.kanagawa.jp yamato.kanagawa.jp yokosuka.kanagawa.jp yugawara.kanagawa.jp zama.kanagawa.jp zushi.kanagawa.jp aki.kochi.jp geisei.kochi.jp hidaka.kochi.jp higashitsuno.kochi.jp ino.kochi.jp kagami.kochi.jp kami.kochi.jp kitagawa.kochi.jp kochi.kochi.jp mihara.kochi.jp motoyama.kochi.jp muroto.kochi.jp nahari.kochi.jp nakamura.kochi.jp nankoku.kochi.jp nishitosa.kochi.jp niyodogawa.kochi.jp ochi.kochi.jp okawa.kochi.jp otoyo.kochi.jp otsuki.kochi.jp sakawa.kochi.jp sukumo.kochi.jp susaki.kochi.jp tosa.kochi.jp tosashimizu.kochi.jp toyo.kochi.jp tsuno.kochi.jp umaji.kochi.jp yasuda.kochi.jp yusuhara.kochi.jp amakusa.kumamoto.jp arao.kumamoto.jp aso.kumamoto.jp choyo.kumamoto.jp gyokuto.kumamoto.jp kamiamakusa.kumamoto.jp kikuchi.kumamoto.jp kumamoto.kumamoto.jp mashiki.kumamoto.jp mifune.kumamoto.jp minamata.kumamoto.jp minamioguni.kumamoto.jp nagasu.kumamoto.jp nishihara.kumamoto.jp oguni.kumamoto.jp ozu.kumamoto.jp sumoto.kumamoto.jp takamori.kumamoto.jp uki.kumamoto.jp uto.kumamoto.jp yamaga.kumamoto.jp yamato.kumamoto.jp yatsushiro.kumamoto.jp ayabe.kyoto.jp fukuchiyama.kyoto.jp higashiyama.kyoto.jp ide.kyoto.jp ine.kyoto.jp joyo.kyoto.jp kameoka.kyoto.jp kamo.kyoto.jp kita.kyoto.jp kizu.kyoto.jp kumiyama.kyoto.jp kyotamba.kyoto.jp kyotanabe.kyoto.jp kyotango.kyoto.jp maizuru.kyoto.jp minami.kyoto.jp minamiyamashiro.kyoto.jp miyazu.kyoto.jp muko.kyoto.jp nagaokakyo.kyoto.jp nakagyo.kyoto.jp nantan.kyoto.jp oyamazaki.kyoto.jp sakyo.kyoto.jp seika.kyoto.jp tanabe.kyoto.jp uji.kyoto.jp ujitawara.kyoto.jp wazuka.kyoto.jp yamashina.kyoto.jp yawata.kyoto.jp asahi.mie.jp inabe.mie.jp ise.mie.jp kameyama.mie.jp kawagoe.mie.jp kiho.mie.jp kisosaki.mie.jp kiwa.mie.jp komono.mie.jp kumano.mie.jp kuwana.mie.jp matsusaka.mie.jp meiwa.mie.jp mihama.mie.jp minamiise.mie.jp misugi.mie.jp miyama.mie.jp nabari.mie.jp shima.mie.jp suzuka.mie.jp tado.mie.jp taiki.mie.jp taki.mie.jp tamaki.mie.jp toba.mie.jp tsu.mie.jp udono.mie.jp ureshino.mie.jp watarai.mie.jp yokkaichi.mie.jp furukawa.miyagi.jp higashimatsushima.miyagi.jp ishinomaki.miyagi.jp iwanuma.miyagi.jp kakuda.miyagi.jp kami.miyagi.jp kawasaki.miyagi.jp marumori.miyagi.jp matsushima.miyagi.jp minamisanriku.miyagi.jp misato.miyagi.jp murata.miyagi.jp natori.miyagi.jp ogawara.miyagi.jp ohira.miyagi.jp onagawa.miyagi.jp osaki.miyagi.jp rifu.miyagi.jp semine.miyagi.jp shibata.miyagi.jp shichikashuku.miyagi.jp shikama.miyagi.jp shiogama.miyagi.jp shiroishi.miyagi.jp tagajo.miyagi.jp taiwa.miyagi.jp tome.miyagi.jp tomiya.miyagi.jp wakuya.miyagi.jp watari.miyagi.jp yamamoto.miyagi.jp zao.miyagi.jp aya.miyazaki.jp ebino.miyazaki.jp gokase.miyazaki.jp hyuga.miyazaki.jp kadogawa.miyazaki.jp kawaminami.miyazaki.jp kijo.miyazaki.jp kitagawa.miyazaki.jp kitakata.miyazaki.jp kitaura.miyazaki.jp kobayashi.miyazaki.jp kunitomi.miyazaki.jp kushima.miyazaki.jp mimata.miyazaki.jp miyakonojo.miyazaki.jp miyazaki.miyazaki.jp morotsuka.miyazaki.jp nichinan.miyazaki.jp nishimera.miyazaki.jp nobeoka.miyazaki.jp saito.miyazaki.jp shiiba.miyazaki.jp shintomi.miyazaki.jp takaharu.miyazaki.jp takanabe.miyazaki.jp takazaki.miyazaki.jp tsuno.miyazaki.jp achi.nagano.jp agematsu.nagano.jp anan.nagano.jp aoki.nagano.jp asahi.nagano.jp azumino.nagano.jp chikuhoku.nagano.jp chikuma.nagano.jp chino.nagano.jp fujimi.nagano.jp hakuba.nagano.jp hara.nagano.jp hiraya.nagano.jp iida.nagano.jp iijima.nagano.jp iiyama.nagano.jp iizuna.nagano.jp ikeda.nagano.jp ikusaka.nagano.jp ina.nagano.jp karuizawa.nagano.jp kawakami.nagano.jp kiso.nagano.jp kisofukushima.nagano.jp kitaaiki.nagano.jp komagane.nagano.jp komoro.nagano.jp matsukawa.nagano.jp matsumoto.nagano.jp miasa.nagano.jp minamiaiki.nagano.jp minamimaki.nagano.jp minamiminowa.nagano.jp minowa.nagano.jp miyada.nagano.jp miyota.nagano.jp mochizuki.nagano.jp nagano.nagano.jp nagawa.nagano.jp nagiso.nagano.jp nakagawa.nagano.jp nakano.nagano.jp nozawaonsen.nagano.jp obuse.nagano.jp ogawa.nagano.jp okaya.nagano.jp omachi.nagano.jp omi.nagano.jp ookuwa.nagano.jp ooshika.nagano.jp otaki.nagano.jp otari.nagano.jp sakae.nagano.jp sakaki.nagano.jp saku.nagano.jp sakuho.nagano.jp shimosuwa.nagano.jp shinanomachi.nagano.jp shiojiri.nagano.jp suwa.nagano.jp suzaka.nagano.jp takagi.nagano.jp takamori.nagano.jp takayama.nagano.jp tateshina.nagano.jp tatsuno.nagano.jp togakushi.nagano.jp togura.nagano.jp tomi.nagano.jp ueda.nagano.jp wada.nagano.jp yamagata.nagano.jp yamanouchi.nagano.jp yasaka.nagano.jp yasuoka.nagano.jp chijiwa.nagasaki.jp futsu.nagasaki.jp goto.nagasaki.jp hasami.nagasaki.jp hirado.nagasaki.jp iki.nagasaki.jp isahaya.nagasaki.jp kawatana.nagasaki.jp kuchinotsu.nagasaki.jp matsuura.nagasaki.jp nagasaki.nagasaki.jp obama.nagasaki.jp omura.nagasaki.jp oseto.nagasaki.jp saikai.nagasaki.jp sasebo.nagasaki.jp seihi.nagasaki.jp shimabara.nagasaki.jp shinkamigoto.nagasaki.jp togitsu.nagasaki.jp tsushima.nagasaki.jp unzen.nagasaki.jp ando.nara.jp gose.nara.jp heguri.nara.jp higashiyoshino.nara.jp ikaruga.nara.jp ikoma.nara.jp kamikitayama.nara.jp kanmaki.nara.jp kashiba.nara.jp kashihara.nara.jp katsuragi.nara.jp kawai.nara.jp kawakami.nara.jp kawanishi.nara.jp koryo.nara.jp kurotaki.nara.jp mitsue.nara.jp miyake.nara.jp nara.nara.jp nosegawa.nara.jp oji.nara.jp ouda.nara.jp oyodo.nara.jp sakurai.nara.jp sango.nara.jp shimoichi.nara.jp shimokitayama.nara.jp shinjo.nara.jp soni.nara.jp takatori.nara.jp tawaramoto.nara.jp tenkawa.nara.jp tenri.nara.jp uda.nara.jp yamatokoriyama.nara.jp yamatotakada.nara.jp yamazoe.nara.jp yoshino.nara.jp aga.niigata.jp agano.niigata.jp gosen.niigata.jp itoigawa.niigata.jp izumozaki.niigata.jp joetsu.niigata.jp kamo.niigata.jp kariwa.niigata.jp kashiwazaki.niigata.jp minamiuonuma.niigata.jp mitsuke.niigata.jp muika.niigata.jp murakami.niigata.jp myoko.niigata.jp nagaoka.niigata.jp niigata.niigata.jp ojiya.niigata.jp omi.niigata.jp sado.niigata.jp sanjo.niigata.jp seiro.niigata.jp seirou.niigata.jp sekikawa.niigata.jp shibata.niigata.jp tagami.niigata.jp tainai.niigata.jp tochio.niigata.jp tokamachi.niigata.jp tsubame.niigata.jp tsunan.niigata.jp uonuma.niigata.jp yahiko.niigata.jp yoita.niigata.jp yuzawa.niigata.jp beppu.oita.jp bungoono.oita.jp bungotakada.oita.jp hasama.oita.jp hiji.oita.jp himeshima.oita.jp hita.oita.jp kamitsue.oita.jp kokonoe.oita.jp kuju.oita.jp kunisaki.oita.jp kusu.oita.jp oita.oita.jp saiki.oita.jp taketa.oita.jp tsukumi.oita.jp usa.oita.jp usuki.oita.jp yufu.oita.jp akaiwa.okayama.jp asakuchi.okayama.jp bizen.okayama.jp hayashima.okayama.jp ibara.okayama.jp kagamino.okayama.jp kasaoka.okayama.jp kibichuo.okayama.jp kumenan.okayama.jp kurashiki.okayama.jp maniwa.okayama.jp misaki.okayama.jp nagi.okayama.jp niimi.okayama.jp nishiawakura.okayama.jp okayama.okayama.jp satosho.okayama.jp setouchi.okayama.jp shinjo.okayama.jp shoo.okayama.jp soja.okayama.jp takahashi.okayama.jp tamano.okayama.jp tsuyama.okayama.jp wake.okayama.jp yakage.okayama.jp aguni.okinawa.jp ginowan.okinawa.jp ginoza.okinawa.jp gushikami.okinawa.jp haebaru.okinawa.jp higashi.okinawa.jp hirara.okinawa.jp iheya.okinawa.jp ishigaki.okinawa.jp ishikawa.okinawa.jp itoman.okinawa.jp izena.okinawa.jp kadena.okinawa.jp kin.okinawa.jp kitadaito.okinawa.jp kitanakagusuku.okinawa.jp kumejima.okinawa.jp kunigami.okinawa.jp minamidaito.okinawa.jp motobu.okinawa.jp nago.okinawa.jp naha.okinawa.jp nakagusuku.okinawa.jp nakijin.okinawa.jp nanjo.okinawa.jp nishihara.okinawa.jp ogimi.okinawa.jp okinawa.okinawa.jp onna.okinawa.jp shimoji.okinawa.jp taketomi.okinawa.jp tarama.okinawa.jp tokashiki.okinawa.jp tomigusuku.okinawa.jp tonaki.okinawa.jp urasoe.okinawa.jp uruma.okinawa.jp yaese.okinawa.jp yomitan.okinawa.jp yonabaru.okinawa.jp yonaguni.okinawa.jp zamami.okinawa.jp abeno.osaka.jp chihayaakasaka.osaka.jp chuo.osaka.jp daito.osaka.jp fujiidera.osaka.jp habikino.osaka.jp hannan.osaka.jp higashiosaka.osaka.jp higashisumiyoshi.osaka.jp higashiyodogawa.osaka.jp hirakata.osaka.jp ibaraki.osaka.jp ikeda.osaka.jp izumi.osaka.jp izumiotsu.osaka.jp izumisano.osaka.jp kadoma.osaka.jp kaizuka.osaka.jp kanan.osaka.jp kashiwara.osaka.jp katano.osaka.jp kawachinagano.osaka.jp kishiwada.osaka.jp kita.osaka.jp kumatori.osaka.jp matsubara.osaka.jp minato.osaka.jp minoh.osaka.jp misaki.osaka.jp moriguchi.osaka.jp neyagawa.osaka.jp nishi.osaka.jp nose.osaka.jp osakasayama.osaka.jp sakai.osaka.jp sayama.osaka.jp sennan.osaka.jp settsu.osaka.jp shijonawate.osaka.jp shimamoto.osaka.jp suita.osaka.jp tadaoka.osaka.jp taishi.osaka.jp tajiri.osaka.jp takaishi.osaka.jp takatsuki.osaka.jp tondabayashi.osaka.jp toyonaka.osaka.jp toyono.osaka.jp yao.osaka.jp ariake.saga.jp arita.saga.jp fukudomi.saga.jp genkai.saga.jp hamatama.saga.jp hizen.saga.jp imari.saga.jp kamimine.saga.jp kanzaki.saga.jp karatsu.saga.jp kashima.saga.jp kitagata.saga.jp kitahata.saga.jp kiyama.saga.jp kouhoku.saga.jp kyuragi.saga.jp nishiarita.saga.jp ogi.saga.jp omachi.saga.jp ouchi.saga.jp saga.saga.jp shiroishi.saga.jp taku.saga.jp tara.saga.jp tosu.saga.jp yoshinogari.saga.jp arakawa.saitama.jp asaka.saitama.jp chichibu.saitama.jp fujimi.saitama.jp fujimino.saitama.jp fukaya.saitama.jp hanno.saitama.jp hanyu.saitama.jp hasuda.saitama.jp hatogaya.saitama.jp hatoyama.saitama.jp hidaka.saitama.jp higashichichibu.saitama.jp higashimatsuyama.saitama.jp honjo.saitama.jp ina.saitama.jp iruma.saitama.jp iwatsuki.saitama.jp kamiizumi.saitama.jp kamikawa.saitama.jp kamisato.saitama.jp kasukabe.saitama.jp kawagoe.saitama.jp kawaguchi.saitama.jp kawajima.saitama.jp kazo.saitama.jp kitamoto.saitama.jp koshigaya.saitama.jp kounosu.saitama.jp kuki.saitama.jp kumagaya.saitama.jp matsubushi.saitama.jp minano.saitama.jp misato.saitama.jp miyashiro.saitama.jp miyoshi.saitama.jp moroyama.saitama.jp nagatoro.saitama.jp namegawa.saitama.jp niiza.saitama.jp ogano.saitama.jp ogawa.saitama.jp ogose.saitama.jp okegawa.saitama.jp omiya.saitama.jp otaki.saitama.jp ranzan.saitama.jp ryokami.saitama.jp saitama.saitama.jp sakado.saitama.jp satte.saitama.jp sayama.saitama.jp shiki.saitama.jp shiraoka.saitama.jp soka.saitama.jp sugito.saitama.jp toda.saitama.jp tokigawa.saitama.jp tokorozawa.saitama.jp tsurugashima.saitama.jp urawa.saitama.jp warabi.saitama.jp yashio.saitama.jp yokoze.saitama.jp yono.saitama.jp yorii.saitama.jp yoshida.saitama.jp yoshikawa.saitama.jp yoshimi.saitama.jp aisho.shiga.jp gamo.shiga.jp higashiomi.shiga.jp hikone.shiga.jp koka.shiga.jp konan.shiga.jp kosei.shiga.jp koto.shiga.jp kusatsu.shiga.jp maibara.shiga.jp moriyama.shiga.jp nagahama.shiga.jp nishiazai.shiga.jp notogawa.shiga.jp omihachiman.shiga.jp otsu.shiga.jp ritto.shiga.jp ryuoh.shiga.jp takashima.shiga.jp takatsuki.shiga.jp torahime.shiga.jp toyosato.shiga.jp yasu.shiga.jp akagi.shimane.jp ama.shimane.jp gotsu.shimane.jp hamada.shimane.jp higashiizumo.shimane.jp hikawa.shimane.jp hikimi.shimane.jp izumo.shimane.jp kakinoki.shimane.jp masuda.shimane.jp matsue.shimane.jp misato.shimane.jp nishinoshima.shimane.jp ohda.shimane.jp okinoshima.shimane.jp okuizumo.shimane.jp shimane.shimane.jp tamayu.shimane.jp tsuwano.shimane.jp unnan.shimane.jp yakumo.shimane.jp yasugi.shimane.jp yatsuka.shimane.jp arai.shizuoka.jp atami.shizuoka.jp fuji.shizuoka.jp fujieda.shizuoka.jp fujikawa.shizuoka.jp fujinomiya.shizuoka.jp fukuroi.shizuoka.jp gotemba.shizuoka.jp haibara.shizuoka.jp hamamatsu.shizuoka.jp higashiizu.shizuoka.jp ito.shizuoka.jp iwata.shizuoka.jp izu.shizuoka.jp izunokuni.shizuoka.jp kakegawa.shizuoka.jp kannami.shizuoka.jp kawanehon.shizuoka.jp kawazu.shizuoka.jp kikugawa.shizuoka.jp kosai.shizuoka.jp makinohara.shizuoka.jp matsuzaki.shizuoka.jp minamiizu.shizuoka.jp mishima.shizuoka.jp morimachi.shizuoka.jp nishiizu.shizuoka.jp numazu.shizuoka.jp omaezaki.shizuoka.jp shimada.shizuoka.jp shimizu.shizuoka.jp shimoda.shizuoka.jp shizuoka.shizuoka.jp susono.shizuoka.jp yaizu.shizuoka.jp yoshida.shizuoka.jp ashikaga.tochigi.jp bato.tochigi.jp haga.tochigi.jp ichikai.tochigi.jp iwafune.tochigi.jp kaminokawa.tochigi.jp kanuma.tochigi.jp karasuyama.tochigi.jp kuroiso.tochigi.jp mashiko.tochigi.jp mibu.tochigi.jp moka.tochigi.jp motegi.tochigi.jp nasu.tochigi.jp nasushiobara.tochigi.jp nikko.tochigi.jp nishikata.tochigi.jp nogi.tochigi.jp ohira.tochigi.jp ohtawara.tochigi.jp oyama.tochigi.jp sakura.tochigi.jp sano.tochigi.jp shimotsuke.tochigi.jp shioya.tochigi.jp takanezawa.tochigi.jp tochigi.tochigi.jp tsuga.tochigi.jp ujiie.tochigi.jp utsunomiya.tochigi.jp yaita.tochigi.jp aizumi.tokushima.jp anan.tokushima.jp ichiba.tokushima.jp itano.tokushima.jp kainan.tokushima.jp komatsushima.tokushima.jp matsushige.tokushima.jp mima.tokushima.jp minami.tokushima.jp miyoshi.tokushima.jp mugi.tokushima.jp nakagawa.tokushima.jp naruto.tokushima.jp sanagochi.tokushima.jp shishikui.tokushima.jp tokushima.tokushima.jp wajiki.tokushima.jp adachi.tokyo.jp akiruno.tokyo.jp akishima.tokyo.jp aogashima.tokyo.jp arakawa.tokyo.jp bunkyo.tokyo.jp chiyoda.tokyo.jp chofu.tokyo.jp chuo.tokyo.jp edogawa.tokyo.jp fuchu.tokyo.jp fussa.tokyo.jp hachijo.tokyo.jp hachioji.tokyo.jp hamura.tokyo.jp higashikurume.tokyo.jp higashimurayama.tokyo.jp higashiyamato.tokyo.jp hino.tokyo.jp hinode.tokyo.jp hinohara.tokyo.jp inagi.tokyo.jp itabashi.tokyo.jp katsushika.tokyo.jp kita.tokyo.jp kiyose.tokyo.jp kodaira.tokyo.jp koganei.tokyo.jp kokubunji.tokyo.jp komae.tokyo.jp koto.tokyo.jp kouzushima.tokyo.jp kunitachi.tokyo.jp machida.tokyo.jp meguro.tokyo.jp minato.tokyo.jp mitaka.tokyo.jp mizuho.tokyo.jp musashimurayama.tokyo.jp musashino.tokyo.jp nakano.tokyo.jp nerima.tokyo.jp ogasawara.tokyo.jp okutama.tokyo.jp ome.tokyo.jp oshima.tokyo.jp ota.tokyo.jp setagaya.tokyo.jp shibuya.tokyo.jp shinagawa.tokyo.jp shinjuku.tokyo.jp suginami.tokyo.jp sumida.tokyo.jp tachikawa.tokyo.jp taito.tokyo.jp tama.tokyo.jp toshima.tokyo.jp chizu.tottori.jp hino.tottori.jp kawahara.tottori.jp koge.tottori.jp kotoura.tottori.jp misasa.tottori.jp nanbu.tottori.jp nichinan.tottori.jp sakaiminato.tottori.jp tottori.tottori.jp wakasa.tottori.jp yazu.tottori.jp yonago.tottori.jp asahi.toyama.jp fuchu.toyama.jp fukumitsu.toyama.jp funahashi.toyama.jp himi.toyama.jp imizu.toyama.jp inami.toyama.jp johana.toyama.jp kamiichi.toyama.jp kurobe.toyama.jp nakaniikawa.toyama.jp namerikawa.toyama.jp nanto.toyama.jp nyuzen.toyama.jp oyabe.toyama.jp taira.toyama.jp takaoka.toyama.jp tateyama.toyama.jp toga.toyama.jp tonami.toyama.jp toyama.toyama.jp unazuki.toyama.jp uozu.toyama.jp yamada.toyama.jp arida.wakayama.jp aridagawa.wakayama.jp gobo.wakayama.jp hashimoto.wakayama.jp hidaka.wakayama.jp hirogawa.wakayama.jp inami.wakayama.jp iwade.wakayama.jp kainan.wakayama.jp kamitonda.wakayama.jp katsuragi.wakayama.jp kimino.wakayama.jp kinokawa.wakayama.jp kitayama.wakayama.jp koya.wakayama.jp koza.wakayama.jp kozagawa.wakayama.jp kudoyama.wakayama.jp kushimoto.wakayama.jp mihama.wakayama.jp misato.wakayama.jp nachikatsuura.wakayama.jp shingu.wakayama.jp shirahama.wakayama.jp taiji.wakayama.jp tanabe.wakayama.jp wakayama.wakayama.jp yuasa.wakayama.jp yura.wakayama.jp asahi.yamagata.jp funagata.yamagata.jp higashine.yamagata.jp iide.yamagata.jp kahoku.yamagata.jp kaminoyama.yamagata.jp kaneyama.yamagata.jp kawanishi.yamagata.jp mamurogawa.yamagata.jp mikawa.yamagata.jp murayama.yamagata.jp nagai.yamagata.jp nakayama.yamagata.jp nanyo.yamagata.jp nishikawa.yamagata.jp obanazawa.yamagata.jp oe.yamagata.jp oguni.yamagata.jp ohkura.yamagata.jp oishida.yamagata.jp sagae.yamagata.jp sakata.yamagata.jp sakegawa.yamagata.jp shinjo.yamagata.jp shirataka.yamagata.jp shonai.yamagata.jp takahata.yamagata.jp tendo.yamagata.jp tozawa.yamagata.jp tsuruoka.yamagata.jp yamagata.yamagata.jp yamanobe.yamagata.jp yonezawa.yamagata.jp yuza.yamagata.jp abu.yamaguchi.jp hagi.yamaguchi.jp hikari.yamaguchi.jp hofu.yamaguchi.jp iwakuni.yamaguchi.jp kudamatsu.yamaguchi.jp mitou.yamaguchi.jp nagato.yamaguchi.jp oshima.yamaguchi.jp shimonoseki.yamaguchi.jp shunan.yamaguchi.jp tabuse.yamaguchi.jp tokuyama.yamaguchi.jp toyota.yamaguchi.jp ube.yamaguchi.jp yuu.yamaguchi.jp chuo.yamanashi.jp doshi.yamanashi.jp fuefuki.yamanashi.jp fujikawa.yamanashi.jp fujikawaguchiko.yamanashi.jp fujiyoshida.yamanashi.jp hayakawa.yamanashi.jp hokuto.yamanashi.jp ichikawamisato.yamanashi.jp kai.yamanashi.jp kofu.yamanashi.jp koshu.yamanashi.jp kosuge.yamanashi.jp minami-alps.yamanashi.jp minobu.yamanashi.jp nakamichi.yamanashi.jp nanbu.yamanashi.jp narusawa.yamanashi.jp nirasaki.yamanashi.jp nishikatsura.yamanashi.jp oshino.yamanashi.jp otsuki.yamanashi.jp showa.yamanashi.jp tabayama.yamanashi.jp tsuru.yamanashi.jp uenohara.yamanashi.jp yamanakako.yamanashi.jp yamanashi.yamanashi.jp // ke : http://www.kenic.or.ke/index.php/en/ke-domains/ke-domains ke ac.ke co.ke go.ke info.ke me.ke mobi.ke ne.ke or.ke sc.ke // kg : http://www.domain.kg/dmn_n.html kg org.kg net.kg com.kg edu.kg gov.kg mil.kg // kh : http://www.mptc.gov.kh/dns_registration.htm *.kh // ki : http://www.ki/dns/index.html ki edu.ki biz.ki net.ki org.ki gov.ki info.ki com.ki // km : https://en.wikipedia.org/wiki/.km // http://www.domaine.km/documents/charte.doc km org.km nom.km gov.km prd.km tm.km edu.km mil.km ass.km com.km // These are only mentioned as proposed suggestions at domaine.km, but // https://en.wikipedia.org/wiki/.km says they're available for registration: coop.km asso.km presse.km medecin.km notaires.km pharmaciens.km veterinaire.km gouv.km // kn : https://en.wikipedia.org/wiki/.kn // http://www.dot.kn/domainRules.html kn net.kn org.kn edu.kn gov.kn // kp : http://www.kcce.kp/en_index.php kp com.kp edu.kp gov.kp org.kp rep.kp tra.kp // kr : https://en.wikipedia.org/wiki/.kr // see also: http://domain.nida.or.kr/eng/registration.jsp kr ac.kr co.kr es.kr go.kr hs.kr kg.kr mil.kr ms.kr ne.kr or.kr pe.kr re.kr sc.kr // kr geographical names busan.kr chungbuk.kr chungnam.kr daegu.kr daejeon.kr gangwon.kr gwangju.kr gyeongbuk.kr gyeonggi.kr gyeongnam.kr incheon.kr jeju.kr jeonbuk.kr jeonnam.kr seoul.kr ulsan.kr // kw : https://www.nic.kw/policies/ // Confirmed by registry kw com.kw edu.kw emb.kw gov.kw ind.kw net.kw org.kw // ky : http://www.icta.ky/da_ky_reg_dom.php // Confirmed by registry 2008-06-17 ky com.ky edu.ky net.ky org.ky // kz : https://en.wikipedia.org/wiki/.kz // see also: http://www.nic.kz/rules/index.jsp kz org.kz edu.kz net.kz gov.kz mil.kz com.kz // la : https://en.wikipedia.org/wiki/.la // Submitted by registry la int.la net.la info.la edu.la gov.la per.la com.la org.la // lb : https://en.wikipedia.org/wiki/.lb // Submitted by registry lb com.lb edu.lb gov.lb net.lb org.lb // lc : https://en.wikipedia.org/wiki/.lc // see also: http://www.nic.lc/rules.htm lc com.lc net.lc co.lc org.lc edu.lc gov.lc // li : https://en.wikipedia.org/wiki/.li li // lk : https://www.nic.lk/index.php/domain-registration/lk-domain-naming-structure lk gov.lk sch.lk net.lk int.lk com.lk org.lk edu.lk ngo.lk soc.lk web.lk ltd.lk assn.lk grp.lk hotel.lk ac.lk // lr : http://psg.com/dns/lr/lr.txt // Submitted by registry lr com.lr edu.lr gov.lr org.lr net.lr // ls : http://www.nic.ls/ // Confirmed by registry ls ac.ls biz.ls co.ls edu.ls gov.ls info.ls net.ls org.ls sc.ls // lt : https://en.wikipedia.org/wiki/.lt lt // gov.lt : http://www.gov.lt/index_en.php gov.lt // lu : http://www.dns.lu/en/ lu // lv : http://www.nic.lv/DNS/En/generic.php lv com.lv edu.lv gov.lv org.lv mil.lv id.lv net.lv asn.lv conf.lv // ly : http://www.nic.ly/regulations.php ly com.ly net.ly gov.ly plc.ly edu.ly sch.ly med.ly org.ly id.ly // ma : https://en.wikipedia.org/wiki/.ma // http://www.anrt.ma/fr/admin/download/upload/file_fr782.pdf ma co.ma net.ma gov.ma org.ma ac.ma press.ma // mc : http://www.nic.mc/ mc tm.mc asso.mc // md : https://en.wikipedia.org/wiki/.md md // me : https://en.wikipedia.org/wiki/.me me co.me net.me org.me edu.me ac.me gov.me its.me priv.me // mg : http://nic.mg/nicmg/?page_id=39 mg org.mg nom.mg gov.mg prd.mg tm.mg edu.mg mil.mg com.mg co.mg // mh : https://en.wikipedia.org/wiki/.mh mh // mil : https://en.wikipedia.org/wiki/.mil mil // mk : https://en.wikipedia.org/wiki/.mk // see also: http://dns.marnet.net.mk/postapka.php mk com.mk org.mk net.mk edu.mk gov.mk inf.mk name.mk // ml : http://www.gobin.info/domainname/ml-template.doc // see also: https://en.wikipedia.org/wiki/.ml ml com.ml edu.ml gouv.ml gov.ml net.ml org.ml presse.ml // mm : https://en.wikipedia.org/wiki/.mm *.mm // mn : https://en.wikipedia.org/wiki/.mn mn gov.mn edu.mn org.mn // mo : http://www.monic.net.mo/ mo com.mo net.mo org.mo edu.mo gov.mo // mobi : https://en.wikipedia.org/wiki/.mobi mobi // mp : http://www.dot.mp/ // Confirmed by registry 2008-06-17 mp // mq : https://en.wikipedia.org/wiki/.mq mq // mr : https://en.wikipedia.org/wiki/.mr mr gov.mr // ms : http://www.nic.ms/pdf/MS_Domain_Name_Rules.pdf ms com.ms edu.ms gov.ms net.ms org.ms // mt : https://www.nic.org.mt/go/policy // Submitted by registry mt com.mt edu.mt net.mt org.mt // mu : https://en.wikipedia.org/wiki/.mu mu com.mu net.mu org.mu gov.mu ac.mu co.mu or.mu // museum : https://welcome.museum/wp-content/uploads/2018/05/20180525-Registration-Policy-MUSEUM-EN_VF-2.pdf https://welcome.museum/buy-your-dot-museum-2/ museum // mv : https://en.wikipedia.org/wiki/.mv // "mv" included because, contra Wikipedia, google.mv exists. mv aero.mv biz.mv com.mv coop.mv edu.mv gov.mv info.mv int.mv mil.mv museum.mv name.mv net.mv org.mv pro.mv // mw : http://www.registrar.mw/ mw ac.mw biz.mw co.mw com.mw coop.mw edu.mw gov.mw int.mw museum.mw net.mw org.mw // mx : http://www.nic.mx/ // Submitted by registry mx com.mx org.mx gob.mx edu.mx net.mx // my : http://www.mynic.my/ // Available strings: https://mynic.my/resources/domains/buying-a-domain/ my biz.my com.my edu.my gov.my mil.my name.my net.my org.my // mz : http://www.uem.mz/ // Submitted by registry mz ac.mz adv.mz co.mz edu.mz gov.mz mil.mz net.mz org.mz // na : http://www.na-nic.com.na/ // http://www.info.na/domain/ na info.na pro.na name.na school.na or.na dr.na us.na mx.na ca.na in.na cc.na tv.na ws.na mobi.na co.na com.na org.na // name : has 2nd-level tlds, but there's no list of them name // nc : http://www.cctld.nc/ nc asso.nc nom.nc // ne : https://en.wikipedia.org/wiki/.ne ne // net : https://en.wikipedia.org/wiki/.net net // nf : https://en.wikipedia.org/wiki/.nf nf com.nf net.nf per.nf rec.nf web.nf arts.nf firm.nf info.nf other.nf store.nf // ng : http://www.nira.org.ng/index.php/join-us/register-ng-domain/189-nira-slds ng com.ng edu.ng gov.ng i.ng mil.ng mobi.ng name.ng net.ng org.ng sch.ng // ni : http://www.nic.ni/ ni ac.ni biz.ni co.ni com.ni edu.ni gob.ni in.ni info.ni int.ni mil.ni net.ni nom.ni org.ni web.ni // nl : https://en.wikipedia.org/wiki/.nl // https://www.sidn.nl/ // ccTLD for the Netherlands nl // no : https://www.norid.no/en/om-domenenavn/regelverk-for-no/ // Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/ // Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/ // Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/ // RSS feed: https://teknisk.norid.no/en/feed/ no // Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/ fhs.no vgs.no fylkesbibl.no folkebibl.no museum.no idrett.no priv.no // Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/ mil.no stat.no dep.no kommune.no herad.no // Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/ // counties aa.no ah.no bu.no fm.no hl.no hm.no jan-mayen.no mr.no nl.no nt.no of.no ol.no oslo.no rl.no sf.no st.no svalbard.no tm.no tr.no va.no vf.no // primary and lower secondary schools per county gs.aa.no gs.ah.no gs.bu.no gs.fm.no gs.hl.no gs.hm.no gs.jan-mayen.no gs.mr.no gs.nl.no gs.nt.no gs.of.no gs.ol.no gs.oslo.no gs.rl.no gs.sf.no gs.st.no gs.svalbard.no gs.tm.no gs.tr.no gs.va.no gs.vf.no // cities akrehamn.no åkrehamn.no algard.no ålgård.no arna.no brumunddal.no bryne.no bronnoysund.no brønnøysund.no drobak.no drøbak.no egersund.no fetsund.no floro.no florø.no fredrikstad.no hokksund.no honefoss.no hønefoss.no jessheim.no jorpeland.no jørpeland.no kirkenes.no kopervik.no krokstadelva.no langevag.no langevåg.no leirvik.no mjondalen.no mjøndalen.no mo-i-rana.no mosjoen.no mosjøen.no nesoddtangen.no orkanger.no osoyro.no osøyro.no raholt.no råholt.no sandnessjoen.no sandnessjøen.no skedsmokorset.no slattum.no spjelkavik.no stathelle.no stavern.no stjordalshalsen.no stjørdalshalsen.no tananger.no tranby.no vossevangen.no // communities afjord.no åfjord.no agdenes.no al.no ål.no alesund.no ålesund.no alstahaug.no alta.no áltá.no alaheadju.no álaheadju.no alvdal.no amli.no åmli.no amot.no åmot.no andebu.no andoy.no andøy.no andasuolo.no ardal.no årdal.no aremark.no arendal.no ås.no aseral.no åseral.no asker.no askim.no askvoll.no askoy.no askøy.no asnes.no åsnes.no audnedaln.no aukra.no aure.no aurland.no aurskog-holand.no aurskog-høland.no austevoll.no austrheim.no averoy.no averøy.no balestrand.no ballangen.no balat.no bálát.no balsfjord.no bahccavuotna.no báhccavuotna.no bamble.no bardu.no beardu.no beiarn.no bajddar.no bájddar.no baidar.no báidár.no berg.no bergen.no berlevag.no berlevåg.no bearalvahki.no bearalváhki.no bindal.no birkenes.no bjarkoy.no bjarkøy.no bjerkreim.no bjugn.no bodo.no bodø.no badaddja.no bådåddjå.no budejju.no bokn.no bremanger.no bronnoy.no brønnøy.no bygland.no bykle.no barum.no bærum.no bo.telemark.no bø.telemark.no bo.nordland.no bø.nordland.no bievat.no bievát.no bomlo.no bømlo.no batsfjord.no båtsfjord.no bahcavuotna.no báhcavuotna.no dovre.no drammen.no drangedal.no dyroy.no dyrøy.no donna.no dønna.no eid.no eidfjord.no eidsberg.no eidskog.no eidsvoll.no eigersund.no elverum.no enebakk.no engerdal.no etne.no etnedal.no evenes.no evenassi.no evenášši.no evje-og-hornnes.no farsund.no fauske.no fuossko.no fuoisku.no fedje.no fet.no finnoy.no finnøy.no fitjar.no fjaler.no fjell.no flakstad.no flatanger.no flekkefjord.no flesberg.no flora.no fla.no flå.no folldal.no forsand.no fosnes.no frei.no frogn.no froland.no frosta.no frana.no fræna.no froya.no frøya.no fusa.no fyresdal.no forde.no førde.no gamvik.no gangaviika.no gáŋgaviika.no gaular.no gausdal.no gildeskal.no gildeskål.no giske.no gjemnes.no gjerdrum.no gjerstad.no gjesdal.no gjovik.no gjøvik.no gloppen.no gol.no gran.no grane.no granvin.no gratangen.no grimstad.no grong.no kraanghke.no kråanghke.no grue.no gulen.no hadsel.no halden.no halsa.no hamar.no hamaroy.no habmer.no hábmer.no hapmir.no hápmir.no hammerfest.no hammarfeasta.no hámmárfeasta.no haram.no hareid.no harstad.no hasvik.no aknoluokta.no ákŋoluokta.no hattfjelldal.no aarborte.no haugesund.no hemne.no hemnes.no hemsedal.no heroy.more-og-romsdal.no herøy.møre-og-romsdal.no heroy.nordland.no herøy.nordland.no hitra.no hjartdal.no hjelmeland.no hobol.no hobøl.no hof.no hol.no hole.no holmestrand.no holtalen.no holtålen.no hornindal.no horten.no hurdal.no hurum.no hvaler.no hyllestad.no hagebostad.no hægebostad.no hoyanger.no høyanger.no hoylandet.no høylandet.no ha.no hå.no ibestad.no inderoy.no inderøy.no iveland.no jevnaker.no jondal.no jolster.no jølster.no karasjok.no karasjohka.no kárášjohka.no karlsoy.no galsa.no gálsá.no karmoy.no karmøy.no kautokeino.no guovdageaidnu.no klepp.no klabu.no klæbu.no kongsberg.no kongsvinger.no kragero.no kragerø.no kristiansand.no kristiansund.no krodsherad.no krødsherad.no kvalsund.no rahkkeravju.no ráhkkerávju.no kvam.no kvinesdal.no kvinnherad.no kviteseid.no kvitsoy.no kvitsøy.no kvafjord.no kvæfjord.no giehtavuoatna.no kvanangen.no kvænangen.no navuotna.no návuotna.no kafjord.no kåfjord.no gaivuotna.no gáivuotna.no larvik.no lavangen.no lavagis.no loabat.no loabát.no lebesby.no davvesiida.no leikanger.no leirfjord.no leka.no leksvik.no lenvik.no leangaviika.no leaŋgaviika.no lesja.no levanger.no lier.no lierne.no lillehammer.no lillesand.no lindesnes.no lindas.no lindås.no lom.no loppa.no lahppi.no láhppi.no lund.no lunner.no luroy.no lurøy.no luster.no lyngdal.no lyngen.no ivgu.no lardal.no lerdal.no lærdal.no lodingen.no lødingen.no lorenskog.no lørenskog.no loten.no løten.no malvik.no masoy.no måsøy.no muosat.no muosát.no mandal.no marker.no marnardal.no masfjorden.no meland.no meldal.no melhus.no meloy.no meløy.no meraker.no meråker.no moareke.no moåreke.no midsund.no midtre-gauldal.no modalen.no modum.no molde.no moskenes.no moss.no mosvik.no malselv.no målselv.no malatvuopmi.no málatvuopmi.no namdalseid.no aejrie.no namsos.no namsskogan.no naamesjevuemie.no nååmesjevuemie.no laakesvuemie.no nannestad.no narvik.no narviika.no naustdal.no nedre-eiker.no nes.akershus.no nes.buskerud.no nesna.no nesodden.no nesseby.no unjarga.no unjárga.no nesset.no nissedal.no nittedal.no nord-aurdal.no nord-fron.no nord-odal.no norddal.no nordkapp.no davvenjarga.no davvenjárga.no nordre-land.no nordreisa.no raisa.no ráisa.no nore-og-uvdal.no notodden.no naroy.no nærøy.no notteroy.no nøtterøy.no odda.no oksnes.no øksnes.no oppdal.no oppegard.no oppegård.no orkdal.no orland.no ørland.no orskog.no ørskog.no orsta.no ørsta.no os.hedmark.no os.hordaland.no osen.no osteroy.no osterøy.no ostre-toten.no østre-toten.no overhalla.no ovre-eiker.no øvre-eiker.no oyer.no øyer.no oygarden.no øygarden.no oystre-slidre.no øystre-slidre.no porsanger.no porsangu.no porsáŋgu.no porsgrunn.no radoy.no radøy.no rakkestad.no rana.no ruovat.no randaberg.no rauma.no rendalen.no rennebu.no rennesoy.no rennesøy.no rindal.no ringebu.no ringerike.no ringsaker.no rissa.no risor.no risør.no roan.no rollag.no rygge.no ralingen.no rælingen.no rodoy.no rødøy.no romskog.no rømskog.no roros.no røros.no rost.no røst.no royken.no røyken.no royrvik.no røyrvik.no rade.no råde.no salangen.no siellak.no saltdal.no salat.no sálát.no sálat.no samnanger.no sande.more-og-romsdal.no sande.møre-og-romsdal.no sande.vestfold.no sandefjord.no sandnes.no sandoy.no sandøy.no sarpsborg.no sauda.no sauherad.no sel.no selbu.no selje.no seljord.no sigdal.no siljan.no sirdal.no skaun.no skedsmo.no ski.no skien.no skiptvet.no skjervoy.no skjervøy.no skierva.no skiervá.no skjak.no skjåk.no skodje.no skanland.no skånland.no skanit.no skánit.no smola.no smøla.no snillfjord.no snasa.no snåsa.no snoasa.no snaase.no snåase.no sogndal.no sokndal.no sola.no solund.no songdalen.no sortland.no spydeberg.no stange.no stavanger.no steigen.no steinkjer.no stjordal.no stjørdal.no stokke.no stor-elvdal.no stord.no stordal.no storfjord.no omasvuotna.no strand.no stranda.no stryn.no sula.no suldal.no sund.no sunndal.no surnadal.no sveio.no svelvik.no sykkylven.no sogne.no søgne.no somna.no sømna.no sondre-land.no søndre-land.no sor-aurdal.no sør-aurdal.no sor-fron.no sør-fron.no sor-odal.no sør-odal.no sor-varanger.no sør-varanger.no matta-varjjat.no mátta-várjjat.no sorfold.no sørfold.no sorreisa.no sørreisa.no sorum.no sørum.no tana.no deatnu.no time.no tingvoll.no tinn.no tjeldsund.no dielddanuorri.no tjome.no tjøme.no tokke.no tolga.no torsken.no tranoy.no tranøy.no tromso.no tromsø.no tromsa.no romsa.no trondheim.no troandin.no trysil.no trana.no træna.no trogstad.no trøgstad.no tvedestrand.no tydal.no tynset.no tysfjord.no divtasvuodna.no divttasvuotna.no tysnes.no tysvar.no tysvær.no tonsberg.no tønsberg.no ullensaker.no ullensvang.no ulvik.no utsira.no vadso.no vadsø.no cahcesuolo.no čáhcesuolo.no vaksdal.no valle.no vang.no vanylven.no vardo.no vardø.no varggat.no várggát.no vefsn.no vaapste.no vega.no vegarshei.no vegårshei.no vennesla.no verdal.no verran.no vestby.no vestnes.no vestre-slidre.no vestre-toten.no vestvagoy.no vestvågøy.no vevelstad.no vik.no vikna.no vindafjord.no volda.no voss.no varoy.no værøy.no vagan.no vågan.no voagat.no vagsoy.no vågsøy.no vaga.no vågå.no valer.ostfold.no våler.østfold.no valer.hedmark.no våler.hedmark.no // np : http://www.mos.com.np/register.html *.np // nr : http://cenpac.net.nr/dns/index.html // Submitted by registry nr biz.nr info.nr gov.nr edu.nr org.nr net.nr com.nr // nu : https://en.wikipedia.org/wiki/.nu nu // nz : https://en.wikipedia.org/wiki/.nz // Submitted by registry nz ac.nz co.nz cri.nz geek.nz gen.nz govt.nz health.nz iwi.nz kiwi.nz maori.nz mil.nz māori.nz net.nz org.nz parliament.nz school.nz // om : https://en.wikipedia.org/wiki/.om om co.om com.om edu.om gov.om med.om museum.om net.om org.om pro.om // onion : https://tools.ietf.org/html/rfc7686 onion // org : https://en.wikipedia.org/wiki/.org org // pa : http://www.nic.pa/ // Some additional second level "domains" resolve directly as hostnames, such as // pannet.pa, so we add a rule for "pa". pa ac.pa gob.pa com.pa org.pa sld.pa edu.pa net.pa ing.pa abo.pa med.pa nom.pa // pe : https://www.nic.pe/InformeFinalComision.pdf pe edu.pe gob.pe nom.pe mil.pe org.pe com.pe net.pe // pf : http://www.gobin.info/domainname/formulaire-pf.pdf pf com.pf org.pf edu.pf // pg : https://en.wikipedia.org/wiki/.pg *.pg // ph : http://www.domains.ph/FAQ2.asp // Submitted by registry ph com.ph net.ph org.ph gov.ph edu.ph ngo.ph mil.ph i.ph // pk : http://pk5.pknic.net.pk/pk5/msgNamepk.PK pk com.pk net.pk edu.pk org.pk fam.pk biz.pk web.pk gov.pk gob.pk gok.pk gon.pk gop.pk gos.pk info.pk // pl http://www.dns.pl/english/index.html // Submitted by registry pl com.pl net.pl org.pl // pl functional domains (http://www.dns.pl/english/index.html) aid.pl agro.pl atm.pl auto.pl biz.pl edu.pl gmina.pl gsm.pl info.pl mail.pl miasta.pl media.pl mil.pl nieruchomosci.pl nom.pl pc.pl powiat.pl priv.pl realestate.pl rel.pl sex.pl shop.pl sklep.pl sos.pl szkola.pl targi.pl tm.pl tourism.pl travel.pl turystyka.pl // Government domains gov.pl ap.gov.pl griw.gov.pl ic.gov.pl is.gov.pl kmpsp.gov.pl konsulat.gov.pl kppsp.gov.pl kwp.gov.pl kwpsp.gov.pl mup.gov.pl mw.gov.pl oia.gov.pl oirm.gov.pl oke.gov.pl oow.gov.pl oschr.gov.pl oum.gov.pl pa.gov.pl pinb.gov.pl piw.gov.pl po.gov.pl pr.gov.pl psp.gov.pl psse.gov.pl pup.gov.pl rzgw.gov.pl sa.gov.pl sdn.gov.pl sko.gov.pl so.gov.pl sr.gov.pl starostwo.gov.pl ug.gov.pl ugim.gov.pl um.gov.pl umig.gov.pl upow.gov.pl uppo.gov.pl us.gov.pl uw.gov.pl uzs.gov.pl wif.gov.pl wiih.gov.pl winb.gov.pl wios.gov.pl witd.gov.pl wiw.gov.pl wkz.gov.pl wsa.gov.pl wskr.gov.pl wsse.gov.pl wuoz.gov.pl wzmiuw.gov.pl zp.gov.pl zpisdn.gov.pl // pl regional domains (http://www.dns.pl/english/index.html) augustow.pl babia-gora.pl bedzin.pl beskidy.pl bialowieza.pl bialystok.pl bielawa.pl bieszczady.pl boleslawiec.pl bydgoszcz.pl bytom.pl cieszyn.pl czeladz.pl czest.pl dlugoleka.pl elblag.pl elk.pl glogow.pl gniezno.pl gorlice.pl grajewo.pl ilawa.pl jaworzno.pl jelenia-gora.pl jgora.pl kalisz.pl kazimierz-dolny.pl karpacz.pl kartuzy.pl kaszuby.pl katowice.pl kepno.pl ketrzyn.pl klodzko.pl kobierzyce.pl kolobrzeg.pl konin.pl konskowola.pl kutno.pl lapy.pl lebork.pl legnica.pl lezajsk.pl limanowa.pl lomza.pl lowicz.pl lubin.pl lukow.pl malbork.pl malopolska.pl mazowsze.pl mazury.pl mielec.pl mielno.pl mragowo.pl naklo.pl nowaruda.pl nysa.pl olawa.pl olecko.pl olkusz.pl olsztyn.pl opoczno.pl opole.pl ostroda.pl ostroleka.pl ostrowiec.pl ostrowwlkp.pl pila.pl pisz.pl podhale.pl podlasie.pl polkowice.pl pomorze.pl pomorskie.pl prochowice.pl pruszkow.pl przeworsk.pl pulawy.pl radom.pl rawa-maz.pl rybnik.pl rzeszow.pl sanok.pl sejny.pl slask.pl slupsk.pl sosnowiec.pl stalowa-wola.pl skoczow.pl starachowice.pl stargard.pl suwalki.pl swidnica.pl swiebodzin.pl swinoujscie.pl szczecin.pl szczytno.pl tarnobrzeg.pl tgory.pl turek.pl tychy.pl ustka.pl walbrzych.pl warmia.pl warszawa.pl waw.pl wegrow.pl wielun.pl wlocl.pl wloclawek.pl wodzislaw.pl wolomin.pl wroclaw.pl zachpomor.pl zagan.pl zarow.pl zgora.pl zgorzelec.pl // pm : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf pm // pn : http://www.government.pn/PnRegistry/policies.htm pn gov.pn co.pn org.pn edu.pn net.pn // post : https://en.wikipedia.org/wiki/.post post // pr : http://www.nic.pr/index.asp?f=1 pr com.pr net.pr org.pr gov.pr edu.pr isla.pr pro.pr biz.pr info.pr name.pr // these aren't mentioned on nic.pr, but on https://en.wikipedia.org/wiki/.pr est.pr prof.pr ac.pr // pro : http://registry.pro/get-pro pro aaa.pro aca.pro acct.pro avocat.pro bar.pro cpa.pro eng.pro jur.pro law.pro med.pro recht.pro // ps : https://en.wikipedia.org/wiki/.ps // http://www.nic.ps/registration/policy.html#reg ps edu.ps gov.ps sec.ps plo.ps com.ps org.ps net.ps // pt : https://www.dns.pt/en/domain/pt-terms-and-conditions-registration-rules/ pt net.pt gov.pt org.pt edu.pt int.pt publ.pt com.pt nome.pt // pw : https://en.wikipedia.org/wiki/.pw pw co.pw ne.pw or.pw ed.pw go.pw belau.pw // py : http://www.nic.py/pautas.html#seccion_9 // Submitted by registry py com.py coop.py edu.py gov.py mil.py net.py org.py // qa : http://domains.qa/en/ qa com.qa edu.qa gov.qa mil.qa name.qa net.qa org.qa sch.qa // re : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf re asso.re com.re nom.re // ro : http://www.rotld.ro/ ro arts.ro com.ro firm.ro info.ro nom.ro nt.ro org.ro rec.ro store.ro tm.ro www.ro // rs : https://www.rnids.rs/en/domains/national-domains rs ac.rs co.rs edu.rs gov.rs in.rs org.rs // ru : https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf // Submitted by George Georgievsky ru // rw : https://www.ricta.org.rw/sites/default/files/resources/registry_registrar_contract_0.pdf rw ac.rw co.rw coop.rw gov.rw mil.rw net.rw org.rw // sa : http://www.nic.net.sa/ sa com.sa net.sa org.sa gov.sa med.sa pub.sa edu.sa sch.sa // sb : http://www.sbnic.net.sb/ // Submitted by registry sb com.sb edu.sb gov.sb net.sb org.sb // sc : http://www.nic.sc/ sc com.sc gov.sc net.sc org.sc edu.sc // sd : http://www.isoc.sd/sudanic.isoc.sd/billing_pricing.htm // Submitted by registry sd com.sd net.sd org.sd edu.sd med.sd tv.sd gov.sd info.sd // se : https://en.wikipedia.org/wiki/.se // Submitted by registry se a.se ac.se b.se bd.se brand.se c.se d.se e.se f.se fh.se fhsk.se fhv.se g.se h.se i.se k.se komforb.se kommunalforbund.se komvux.se l.se lanbib.se m.se n.se naturbruksgymn.se o.se org.se p.se parti.se pp.se press.se r.se s.se t.se tm.se u.se w.se x.se y.se z.se // sg : http://www.nic.net.sg/page/registration-policies-procedures-and-guidelines sg com.sg net.sg org.sg gov.sg edu.sg per.sg // sh : http://nic.sh/rules.htm sh com.sh net.sh gov.sh org.sh mil.sh // si : https://en.wikipedia.org/wiki/.si si // sj : No registrations at this time. // Submitted by registry sj // sk : https://en.wikipedia.org/wiki/.sk // list of 2nd level domains ? sk // sl : http://www.nic.sl // Submitted by registry sl com.sl net.sl edu.sl gov.sl org.sl // sm : https://en.wikipedia.org/wiki/.sm sm // sn : https://en.wikipedia.org/wiki/.sn sn art.sn com.sn edu.sn gouv.sn org.sn perso.sn univ.sn // so : http://sonic.so/policies/ so com.so edu.so gov.so me.so net.so org.so // sr : https://en.wikipedia.org/wiki/.sr sr // ss : https://registry.nic.ss/ // Submitted by registry ss biz.ss com.ss edu.ss gov.ss me.ss net.ss org.ss sch.ss // st : http://www.nic.st/html/policyrules/ st co.st com.st consulado.st edu.st embaixada.st mil.st net.st org.st principe.st saotome.st store.st // su : https://en.wikipedia.org/wiki/.su su // sv : http://www.svnet.org.sv/niveldos.pdf sv com.sv edu.sv gob.sv org.sv red.sv // sx : https://en.wikipedia.org/wiki/.sx // Submitted by registry sx gov.sx // sy : https://en.wikipedia.org/wiki/.sy // see also: http://www.gobin.info/domainname/sy.doc sy edu.sy gov.sy net.sy mil.sy com.sy org.sy // sz : https://en.wikipedia.org/wiki/.sz // http://www.sispa.org.sz/ sz co.sz ac.sz org.sz // tc : https://en.wikipedia.org/wiki/.tc tc // td : https://en.wikipedia.org/wiki/.td td // tel: https://en.wikipedia.org/wiki/.tel // http://www.telnic.org/ tel // tf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf tf // tg : https://en.wikipedia.org/wiki/.tg // http://www.nic.tg/ tg // th : https://en.wikipedia.org/wiki/.th // Submitted by registry th ac.th co.th go.th in.th mi.th net.th or.th // tj : http://www.nic.tj/policy.html tj ac.tj biz.tj co.tj com.tj edu.tj go.tj gov.tj int.tj mil.tj name.tj net.tj nic.tj org.tj test.tj web.tj // tk : https://en.wikipedia.org/wiki/.tk tk // tl : https://en.wikipedia.org/wiki/.tl tl gov.tl // tm : http://www.nic.tm/local.html tm com.tm co.tm org.tm net.tm nom.tm gov.tm mil.tm edu.tm // tn : http://www.registre.tn/fr/ // https://whois.ati.tn/ tn com.tn ens.tn fin.tn gov.tn ind.tn info.tn intl.tn mincom.tn nat.tn net.tn org.tn perso.tn tourism.tn // to : https://en.wikipedia.org/wiki/.to // Submitted by registry to com.to gov.to net.to org.to edu.to mil.to // tr : https://nic.tr/ // https://nic.tr/forms/eng/policies.pdf // https://nic.tr/index.php?USRACTN=PRICELST tr av.tr bbs.tr bel.tr biz.tr com.tr dr.tr edu.tr gen.tr gov.tr info.tr mil.tr k12.tr kep.tr name.tr net.tr org.tr pol.tr tel.tr tsk.tr tv.tr web.tr // Used by Northern Cyprus nc.tr // Used by government agencies of Northern Cyprus gov.nc.tr // tt : http://www.nic.tt/ tt co.tt com.tt org.tt net.tt biz.tt info.tt pro.tt int.tt coop.tt jobs.tt mobi.tt travel.tt museum.tt aero.tt name.tt gov.tt edu.tt // tv : https://en.wikipedia.org/wiki/.tv // Not listing any 2LDs as reserved since none seem to exist in practice, // Wikipedia notwithstanding. tv // tw : https://en.wikipedia.org/wiki/.tw tw edu.tw gov.tw mil.tw com.tw net.tw org.tw idv.tw game.tw ebiz.tw club.tw 網路.tw 組織.tw 商業.tw // tz : http://www.tznic.or.tz/index.php/domains // Submitted by registry tz ac.tz co.tz go.tz hotel.tz info.tz me.tz mil.tz mobi.tz ne.tz or.tz sc.tz tv.tz // ua : https://hostmaster.ua/policy/?ua // Submitted by registry ua // ua 2LD com.ua edu.ua gov.ua in.ua net.ua org.ua // ua geographic names // https://hostmaster.ua/2ld/ cherkassy.ua cherkasy.ua chernigov.ua chernihiv.ua chernivtsi.ua chernovtsy.ua ck.ua cn.ua cr.ua crimea.ua cv.ua dn.ua dnepropetrovsk.ua dnipropetrovsk.ua donetsk.ua dp.ua if.ua ivano-frankivsk.ua kh.ua kharkiv.ua kharkov.ua kherson.ua khmelnitskiy.ua khmelnytskyi.ua kiev.ua kirovograd.ua km.ua kr.ua kropyvnytskyi.ua krym.ua ks.ua kv.ua kyiv.ua lg.ua lt.ua lugansk.ua lutsk.ua lv.ua lviv.ua mk.ua mykolaiv.ua nikolaev.ua od.ua odesa.ua odessa.ua pl.ua poltava.ua rivne.ua rovno.ua rv.ua sb.ua sebastopol.ua sevastopol.ua sm.ua sumy.ua te.ua ternopil.ua uz.ua uzhgorod.ua vinnica.ua vinnytsia.ua vn.ua volyn.ua yalta.ua zaporizhzhe.ua zaporizhzhia.ua zhitomir.ua zhytomyr.ua zp.ua zt.ua // ug : https://www.registry.co.ug/ ug co.ug or.ug ac.ug sc.ug go.ug ne.ug com.ug org.ug // uk : https://en.wikipedia.org/wiki/.uk // Submitted by registry uk ac.uk co.uk gov.uk ltd.uk me.uk net.uk nhs.uk org.uk plc.uk police.uk *.sch.uk // us : https://en.wikipedia.org/wiki/.us us dni.us fed.us isa.us kids.us nsn.us // us geographic names ak.us al.us ar.us as.us az.us ca.us co.us ct.us dc.us de.us fl.us ga.us gu.us hi.us ia.us id.us il.us in.us ks.us ky.us la.us ma.us md.us me.us mi.us mn.us mo.us ms.us mt.us nc.us nd.us ne.us nh.us nj.us nm.us nv.us ny.us oh.us ok.us or.us pa.us pr.us ri.us sc.us sd.us tn.us tx.us ut.us vi.us vt.us va.us wa.us wi.us wv.us wy.us // The registrar notes several more specific domains available in each state, // such as state.*.us, dst.*.us, etc., but resolution of these is somewhat // haphazard; in some states these domains resolve as addresses, while in others // only subdomains are available, or even nothing at all. We include the // most common ones where it's clear that different sites are different // entities. k12.ak.us k12.al.us k12.ar.us k12.as.us k12.az.us k12.ca.us k12.co.us k12.ct.us k12.dc.us k12.de.us k12.fl.us k12.ga.us k12.gu.us // k12.hi.us Bug 614565 - Hawaii has a state-wide DOE login k12.ia.us k12.id.us k12.il.us k12.in.us k12.ks.us k12.ky.us k12.la.us k12.ma.us k12.md.us k12.me.us k12.mi.us k12.mn.us k12.mo.us k12.ms.us k12.mt.us k12.nc.us // k12.nd.us Bug 1028347 - Removed at request of Travis Rosso k12.ne.us k12.nh.us k12.nj.us k12.nm.us k12.nv.us k12.ny.us k12.oh.us k12.ok.us k12.or.us k12.pa.us k12.pr.us // k12.ri.us Removed at request of Kim Cournoyer k12.sc.us // k12.sd.us Bug 934131 - Removed at request of James Booze k12.tn.us k12.tx.us k12.ut.us k12.vi.us k12.vt.us k12.va.us k12.wa.us k12.wi.us // k12.wv.us Bug 947705 - Removed at request of Verne Britton k12.wy.us cc.ak.us cc.al.us cc.ar.us cc.as.us cc.az.us cc.ca.us cc.co.us cc.ct.us cc.dc.us cc.de.us cc.fl.us cc.ga.us cc.gu.us cc.hi.us cc.ia.us cc.id.us cc.il.us cc.in.us cc.ks.us cc.ky.us cc.la.us cc.ma.us cc.md.us cc.me.us cc.mi.us cc.mn.us cc.mo.us cc.ms.us cc.mt.us cc.nc.us cc.nd.us cc.ne.us cc.nh.us cc.nj.us cc.nm.us cc.nv.us cc.ny.us cc.oh.us cc.ok.us cc.or.us cc.pa.us cc.pr.us cc.ri.us cc.sc.us cc.sd.us cc.tn.us cc.tx.us cc.ut.us cc.vi.us cc.vt.us cc.va.us cc.wa.us cc.wi.us cc.wv.us cc.wy.us lib.ak.us lib.al.us lib.ar.us lib.as.us lib.az.us lib.ca.us lib.co.us lib.ct.us lib.dc.us // lib.de.us Issue #243 - Moved to Private section at request of Ed Moore lib.fl.us lib.ga.us lib.gu.us lib.hi.us lib.ia.us lib.id.us lib.il.us lib.in.us lib.ks.us lib.ky.us lib.la.us lib.ma.us lib.md.us lib.me.us lib.mi.us lib.mn.us lib.mo.us lib.ms.us lib.mt.us lib.nc.us lib.nd.us lib.ne.us lib.nh.us lib.nj.us lib.nm.us lib.nv.us lib.ny.us lib.oh.us lib.ok.us lib.or.us lib.pa.us lib.pr.us lib.ri.us lib.sc.us lib.sd.us lib.tn.us lib.tx.us lib.ut.us lib.vi.us lib.vt.us lib.va.us lib.wa.us lib.wi.us // lib.wv.us Bug 941670 - Removed at request of Larry W Arnold lib.wy.us // k12.ma.us contains school districts in Massachusetts. The 4LDs are // managed independently except for private (PVT), charter (CHTR) and // parochial (PAROCH) schools. Those are delegated directly to the // 5LD operators. pvt.k12.ma.us chtr.k12.ma.us paroch.k12.ma.us // Merit Network, Inc. maintains the registry for =~ /(k12|cc|lib).mi.us/ and the following // see also: http://domreg.merit.edu // see also: whois -h whois.domreg.merit.edu help ann-arbor.mi.us cog.mi.us dst.mi.us eaton.mi.us gen.mi.us mus.mi.us tec.mi.us washtenaw.mi.us // uy : http://www.nic.org.uy/ uy com.uy edu.uy gub.uy mil.uy net.uy org.uy // uz : http://www.reg.uz/ uz co.uz com.uz net.uz org.uz // va : https://en.wikipedia.org/wiki/.va va // vc : https://en.wikipedia.org/wiki/.vc // Submitted by registry vc com.vc net.vc org.vc gov.vc mil.vc edu.vc // ve : https://registro.nic.ve/ // Submitted by registry nic@nic.ve and nicve@conatel.gob.ve ve arts.ve bib.ve co.ve com.ve e12.ve edu.ve firm.ve gob.ve gov.ve info.ve int.ve mil.ve net.ve nom.ve org.ve rar.ve rec.ve store.ve tec.ve web.ve // vg : https://en.wikipedia.org/wiki/.vg vg // vi : http://www.nic.vi/newdomainform.htm // http://www.nic.vi/Domain_Rules/body_domain_rules.html indicates some other // TLDs are "reserved", such as edu.vi and gov.vi, but doesn't actually say they // are available for registration (which they do not seem to be). vi co.vi com.vi k12.vi net.vi org.vi // vn : https://www.vnnic.vn/en/domain/cctld-vn // https://vnnic.vn/sites/default/files/tailieu/vn.cctld.domains.txt vn ac.vn ai.vn biz.vn com.vn edu.vn gov.vn health.vn id.vn info.vn int.vn io.vn name.vn net.vn org.vn pro.vn // vn geographical names angiang.vn bacgiang.vn backan.vn baclieu.vn bacninh.vn baria-vungtau.vn bentre.vn binhdinh.vn binhduong.vn binhphuoc.vn binhthuan.vn camau.vn cantho.vn caobang.vn daklak.vn daknong.vn danang.vn dienbien.vn dongnai.vn dongthap.vn gialai.vn hagiang.vn haiduong.vn haiphong.vn hanam.vn hanoi.vn hatinh.vn haugiang.vn hoabinh.vn hungyen.vn khanhhoa.vn kiengiang.vn kontum.vn laichau.vn lamdong.vn langson.vn laocai.vn longan.vn namdinh.vn nghean.vn ninhbinh.vn ninhthuan.vn phutho.vn phuyen.vn quangbinh.vn quangnam.vn quangngai.vn quangninh.vn quangtri.vn soctrang.vn sonla.vn tayninh.vn thaibinh.vn thainguyen.vn thanhhoa.vn thanhphohochiminh.vn thuathienhue.vn tiengiang.vn travinh.vn tuyenquang.vn vinhlong.vn vinhphuc.vn yenbai.vn // vu : https://en.wikipedia.org/wiki/.vu // http://www.vunic.vu/ vu com.vu edu.vu net.vu org.vu // wf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf wf // ws : https://en.wikipedia.org/wiki/.ws // http://samoanic.ws/index.dhtml ws com.ws net.ws org.ws gov.ws edu.ws // yt : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf yt // IDN ccTLDs // When submitting patches, please maintain a sort by ISO 3166 ccTLD, then // U-label, and follow this format: // // A-Label ("", [, variant info]) : // // [sponsoring org] // U-Label // xn--mgbaam7a8h ("Emerat", Arabic) : AE // http://nic.ae/english/arabicdomain/rules.jsp امارات // xn--y9a3aq ("hye", Armenian) : AM // ISOC AM (operated by .am Registry) հայ // xn--54b7fta0cc ("Bangla", Bangla) : BD বাংলা // xn--90ae ("bg", Bulgarian) : BG бг // xn--mgbcpq6gpa1a ("albahrain", Arabic) : BH البحرين // xn--90ais ("bel", Belarusian/Russian Cyrillic) : BY // Operated by .by registry бел // xn--fiqs8s ("Zhongguo/China", Chinese, Simplified) : CN // CNNIC // http://cnnic.cn/html/Dir/2005/10/11/3218.htm 中国 // xn--fiqz9s ("Zhongguo/China", Chinese, Traditional) : CN // CNNIC // http://cnnic.cn/html/Dir/2005/10/11/3218.htm 中國 // xn--lgbbat1ad8j ("Algeria/Al Jazair", Arabic) : DZ الجزائر // xn--wgbh1c ("Egypt/Masr", Arabic) : EG // http://www.dotmasr.eg/ مصر // xn--e1a4c ("eu", Cyrillic) : EU // https://eurid.eu ею // xn--qxa6a ("eu", Greek) : EU // https://eurid.eu ευ // xn--mgbah1a3hjkrd ("Mauritania", Arabic) : MR موريتانيا // xn--node ("ge", Georgian Mkhedruli) : GE გე // xn--qxam ("el", Greek) : GR // Hellenic Ministry of Infrastructure, Transport, and Networks ελ // xn--j6w193g ("Hong Kong", Chinese) : HK // https://www.hkirc.hk // Submitted by registry // https://www.hkirc.hk/content.jsp?id=30#!/34 香港 公司.香港 教育.香港 政府.香港 個人.香港 網絡.香港 組織.香港 // xn--2scrj9c ("Bharat", Kannada) : IN // India ಭಾರತ // xn--3hcrj9c ("Bharat", Oriya) : IN // India ଭାରତ // xn--45br5cyl ("Bharatam", Assamese) : IN // India ভাৰত // xn--h2breg3eve ("Bharatam", Sanskrit) : IN // India भारतम् // xn--h2brj9c8c ("Bharot", Santali) : IN // India भारोत // xn--mgbgu82a ("Bharat", Sindhi) : IN // India ڀارت // xn--rvc1e0am3e ("Bharatam", Malayalam) : IN // India ഭാരതം // xn--h2brj9c ("Bharat", Devanagari) : IN // India भारत // xn--mgbbh1a ("Bharat", Kashmiri) : IN // India بارت // xn--mgbbh1a71e ("Bharat", Arabic) : IN // India بھارت // xn--fpcrj9c3d ("Bharat", Telugu) : IN // India భారత్ // xn--gecrj9c ("Bharat", Gujarati) : IN // India ભારત // xn--s9brj9c ("Bharat", Gurmukhi) : IN // India ਭਾਰਤ // xn--45brj9c ("Bharat", Bengali) : IN // India ভারত // xn--xkc2dl3a5ee0h ("India", Tamil) : IN // India இந்தியா // xn--mgba3a4f16a ("Iran", Persian) : IR ایران // xn--mgba3a4fra ("Iran", Arabic) : IR ايران // xn--mgbtx2b ("Iraq", Arabic) : IQ // Communications and Media Commission عراق // xn--mgbayh7gpa ("al-Ordon", Arabic) : JO // National Information Technology Center (NITC) // Royal Scientific Society, Al-Jubeiha الاردن // xn--3e0b707e ("Republic of Korea", Hangul) : KR 한국 // xn--80ao21a ("Kaz", Kazakh) : KZ қаз // xn--q7ce6a ("Lao", Lao) : LA ລາວ // xn--fzc2c9e2c ("Lanka", Sinhalese-Sinhala) : LK // https://nic.lk ලංකා // xn--xkc2al3hye2a ("Ilangai", Tamil) : LK // https://nic.lk இலங்கை // xn--mgbc0a9azcg ("Morocco/al-Maghrib", Arabic) : MA المغرب // xn--d1alf ("mkd", Macedonian) : MK // MARnet мкд // xn--l1acc ("mon", Mongolian) : MN мон // xn--mix891f ("Macao", Chinese, Traditional) : MO // MONIC / HNET Asia (Registry Operator for .mo) 澳門 // xn--mix082f ("Macao", Chinese, Simplified) : MO 澳门 // xn--mgbx4cd0ab ("Malaysia", Malay) : MY مليسيا // xn--mgb9awbf ("Oman", Arabic) : OM عمان // xn--mgbai9azgqp6j ("Pakistan", Urdu/Arabic) : PK پاکستان // xn--mgbai9a5eva00b ("Pakistan", Urdu/Arabic, variant) : PK پاكستان // xn--ygbi2ammx ("Falasteen", Arabic) : PS // The Palestinian National Internet Naming Authority (PNINA) // http://www.pnina.ps فلسطين // xn--90a3ac ("srb", Cyrillic) : RS // https://www.rnids.rs/en/domains/national-domains срб пр.срб орг.срб обр.срб од.срб упр.срб ак.срб // xn--p1ai ("rf", Russian-Cyrillic) : RU // https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf // Submitted by George Georgievsky рф // xn--wgbl6a ("Qatar", Arabic) : QA // http://www.ict.gov.qa/ قطر // xn--mgberp4a5d4ar ("AlSaudiah", Arabic) : SA // http://www.nic.net.sa/ السعودية // xn--mgberp4a5d4a87g ("AlSaudiah", Arabic, variant) : SA السعودیة // xn--mgbqly7c0a67fbc ("AlSaudiah", Arabic, variant) : SA السعودیۃ // xn--mgbqly7cvafr ("AlSaudiah", Arabic, variant) : SA السعوديه // xn--mgbpl2fh ("sudan", Arabic) : SD // Operated by .sd registry سودان // xn--yfro4i67o Singapore ("Singapore", Chinese) : SG 新加坡 // xn--clchc0ea0b2g2a9gcd ("Singapore", Tamil) : SG சிங்கப்பூர் // xn--ogbpf8fl ("Syria", Arabic) : SY سورية // xn--mgbtf8fl ("Syria", Arabic, variant) : SY سوريا // xn--o3cw4h ("Thai", Thai) : TH // http://www.thnic.co.th ไทย ศึกษา.ไทย ธุรกิจ.ไทย รัฐบาล.ไทย ทหาร.ไทย เน็ต.ไทย องค์กร.ไทย // xn--pgbs0dh ("Tunisia", Arabic) : TN // http://nic.tn تونس // xn--kpry57d ("Taiwan", Chinese, Traditional) : TW // http://www.twnic.net/english/dn/dn_07a.htm 台灣 // xn--kprw13d ("Taiwan", Chinese, Simplified) : TW // http://www.twnic.net/english/dn/dn_07a.htm 台湾 // xn--nnx388a ("Taiwan", Chinese, variant) : TW 臺灣 // xn--j1amh ("ukr", Cyrillic) : UA укр // xn--mgb2ddes ("AlYemen", Arabic) : YE اليمن // xxx : http://icmregistry.com xxx // ye : http://www.y.net.ye/services/domain_name.htm ye com.ye edu.ye gov.ye net.ye mil.ye org.ye // za : https://www.zadna.org.za/content/page/domain-information/ ac.za agric.za alt.za co.za edu.za gov.za grondar.za law.za mil.za net.za ngo.za nic.za nis.za nom.za org.za school.za tm.za web.za // zm : https://zicta.zm/ // Submitted by registry zm ac.zm biz.zm co.zm com.zm edu.zm gov.zm info.zm mil.zm net.zm org.zm sch.zm // zw : https://www.potraz.gov.zw/ // Confirmed by registry 2017-01-25 zw ac.zw co.zw gov.zw mil.zw org.zw // newGTLDs // List of new gTLDs imported from https://www.icann.org/resources/registries/gtlds/v2/gtlds.json on 2023-07-01T15:13:06Z // This list is auto-generated, don't edit it manually. // aaa : 2015-02-26 American Automobile Association, Inc. aaa // aarp : 2015-05-21 AARP aarp // abb : 2014-10-24 ABB Ltd abb // abbott : 2014-07-24 Abbott Laboratories, Inc. abbott // abbvie : 2015-07-30 AbbVie Inc. abbvie // abc : 2015-07-30 Disney Enterprises, Inc. abc // able : 2015-06-25 Able Inc. able // abogado : 2014-04-24 Registry Services, LLC abogado // abudhabi : 2015-07-30 Abu Dhabi Systems and Information Centre abudhabi // academy : 2013-11-07 Binky Moon, LLC academy // accenture : 2014-08-15 Accenture plc accenture // accountant : 2014-11-20 dot Accountant Limited accountant // accountants : 2014-03-20 Binky Moon, LLC accountants // aco : 2015-01-08 ACO Severin Ahlmann GmbH & Co. KG aco // actor : 2013-12-12 Dog Beach, LLC actor // ads : 2014-12-04 Charleston Road Registry Inc. ads // adult : 2014-10-16 ICM Registry AD LLC adult // aeg : 2015-03-19 Aktiebolaget Electrolux aeg // aetna : 2015-05-21 Aetna Life Insurance Company aetna // afl : 2014-10-02 Australian Football League afl // africa : 2014-03-24 ZA Central Registry NPC trading as Registry.Africa africa // agakhan : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation) agakhan // agency : 2013-11-14 Binky Moon, LLC agency // aig : 2014-12-18 American International Group, Inc. aig // airbus : 2015-07-30 Airbus S.A.S. airbus // airforce : 2014-03-06 Dog Beach, LLC airforce // airtel : 2014-10-24 Bharti Airtel Limited airtel // akdn : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation) akdn // alibaba : 2015-01-15 Alibaba Group Holding Limited alibaba // alipay : 2015-01-15 Alibaba Group Holding Limited alipay // allfinanz : 2014-07-03 Allfinanz Deutsche Vermögensberatung Aktiengesellschaft allfinanz // allstate : 2015-07-31 Allstate Fire and Casualty Insurance Company allstate // ally : 2015-06-18 Ally Financial Inc. ally // alsace : 2014-07-02 Region Grand Est alsace // alstom : 2015-07-30 ALSTOM alstom // amazon : 2019-12-19 Amazon Registry Services, Inc. amazon // americanexpress : 2015-07-31 American Express Travel Related Services Company, Inc. americanexpress // americanfamily : 2015-07-23 AmFam, Inc. americanfamily // amex : 2015-07-31 American Express Travel Related Services Company, Inc. amex // amfam : 2015-07-23 AmFam, Inc. amfam // amica : 2015-05-28 Amica Mutual Insurance Company amica // amsterdam : 2014-07-24 Gemeente Amsterdam amsterdam // analytics : 2014-12-18 Campus IP LLC analytics // android : 2014-08-07 Charleston Road Registry Inc. android // anquan : 2015-01-08 Beijing Qihu Keji Co., Ltd. anquan // anz : 2015-07-31 Australia and New Zealand Banking Group Limited anz // aol : 2015-09-17 Oath Inc. aol // apartments : 2014-12-11 Binky Moon, LLC apartments // app : 2015-05-14 Charleston Road Registry Inc. app // apple : 2015-05-14 Apple Inc. apple // aquarelle : 2014-07-24 Aquarelle.com aquarelle // arab : 2015-11-12 League of Arab States arab // aramco : 2014-11-20 Aramco Services Company aramco // archi : 2014-02-06 Identity Digital Limited archi // army : 2014-03-06 Dog Beach, LLC army // art : 2016-03-24 UK Creative Ideas Limited art // arte : 2014-12-11 Association Relative à la Télévision Européenne G.E.I.E. arte // asda : 2015-07-31 Wal-Mart Stores, Inc. asda // associates : 2014-03-06 Binky Moon, LLC associates // athleta : 2015-07-30 The Gap, Inc. athleta // attorney : 2014-03-20 Dog Beach, LLC attorney // auction : 2014-03-20 Dog Beach, LLC auction // audi : 2015-05-21 AUDI Aktiengesellschaft audi // audible : 2015-06-25 Amazon Registry Services, Inc. audible // audio : 2014-03-20 XYZ.COM LLC audio // auspost : 2015-08-13 Australian Postal Corporation auspost // author : 2014-12-18 Amazon Registry Services, Inc. author // auto : 2014-11-13 XYZ.COM LLC auto // autos : 2014-01-09 XYZ.COM LLC autos // avianca : 2015-01-08 Avianca Inc. avianca // aws : 2015-06-25 AWS Registry LLC aws // axa : 2013-12-19 AXA Group Operations SAS axa // azure : 2014-12-18 Microsoft Corporation azure // baby : 2015-04-09 XYZ.COM LLC baby // baidu : 2015-01-08 Baidu, Inc. baidu // banamex : 2015-07-30 Citigroup Inc. banamex // bananarepublic : 2015-07-31 The Gap, Inc. bananarepublic // band : 2014-06-12 Dog Beach, LLC band // bank : 2014-09-25 fTLD Registry Services LLC bank // bar : 2013-12-12 Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable bar // barcelona : 2014-07-24 Municipi de Barcelona barcelona // barclaycard : 2014-11-20 Barclays Bank PLC barclaycard // barclays : 2014-11-20 Barclays Bank PLC barclays // barefoot : 2015-06-11 Gallo Vineyards, Inc. barefoot // bargains : 2013-11-14 Binky Moon, LLC bargains // baseball : 2015-10-29 MLB Advanced Media DH, LLC baseball // basketball : 2015-08-20 Fédération Internationale de Basketball (FIBA) basketball // bauhaus : 2014-04-17 Werkhaus GmbH bauhaus // bayern : 2014-01-23 Bayern Connect GmbH bayern // bbc : 2014-12-18 British Broadcasting Corporation bbc // bbt : 2015-07-23 BB&T Corporation bbt // bbva : 2014-10-02 BANCO BILBAO VIZCAYA ARGENTARIA, S.A. bbva // bcg : 2015-04-02 The Boston Consulting Group, Inc. bcg // bcn : 2014-07-24 Municipi de Barcelona bcn // beats : 2015-05-14 Beats Electronics, LLC beats // beauty : 2015-12-03 XYZ.COM LLC beauty // beer : 2014-01-09 Registry Services, LLC beer // bentley : 2014-12-18 Bentley Motors Limited bentley // berlin : 2013-10-31 dotBERLIN GmbH & Co. KG berlin // best : 2013-12-19 BestTLD Pty Ltd best // bestbuy : 2015-07-31 BBY Solutions, Inc. bestbuy // bet : 2015-05-07 Identity Digital Limited bet // bharti : 2014-01-09 Bharti Enterprises (Holding) Private Limited bharti // bible : 2014-06-19 American Bible Society bible // bid : 2013-12-19 dot Bid Limited bid // bike : 2013-08-27 Binky Moon, LLC bike // bing : 2014-12-18 Microsoft Corporation bing // bingo : 2014-12-04 Binky Moon, LLC bingo // bio : 2014-03-06 Identity Digital Limited bio // black : 2014-01-16 Identity Digital Limited black // blackfriday : 2014-01-16 Registry Services, LLC blackfriday // blockbuster : 2015-07-30 Dish DBS Corporation blockbuster // blog : 2015-05-14 Knock Knock WHOIS There, LLC blog // bloomberg : 2014-07-17 Bloomberg IP Holdings LLC bloomberg // blue : 2013-11-07 Identity Digital Limited blue // bms : 2014-10-30 Bristol-Myers Squibb Company bms // bmw : 2014-01-09 Bayerische Motoren Werke Aktiengesellschaft bmw // bnpparibas : 2014-05-29 BNP Paribas bnpparibas // boats : 2014-12-04 XYZ.COM LLC boats // boehringer : 2015-07-09 Boehringer Ingelheim International GmbH boehringer // bofa : 2015-07-31 Bank of America Corporation bofa // bom : 2014-10-16 Núcleo de Informação e Coordenação do Ponto BR - NIC.br bom // bond : 2014-06-05 ShortDot SA bond // boo : 2014-01-30 Charleston Road Registry Inc. boo // book : 2015-08-27 Amazon Registry Services, Inc. book // booking : 2015-07-16 Booking.com B.V. booking // bosch : 2015-06-18 Robert Bosch GMBH bosch // bostik : 2015-05-28 Bostik SA bostik // boston : 2015-12-10 Registry Services, LLC boston // bot : 2014-12-18 Amazon Registry Services, Inc. bot // boutique : 2013-11-14 Binky Moon, LLC boutique // box : 2015-11-12 Intercap Registry Inc. box // bradesco : 2014-12-18 Banco Bradesco S.A. bradesco // bridgestone : 2014-12-18 Bridgestone Corporation bridgestone // broadway : 2014-12-22 Celebrate Broadway, Inc. broadway // broker : 2014-12-11 Dog Beach, LLC broker // brother : 2015-01-29 Brother Industries, Ltd. brother // brussels : 2014-02-06 DNS.be vzw brussels // build : 2013-11-07 Plan Bee LLC build // builders : 2013-11-07 Binky Moon, LLC builders // business : 2013-11-07 Binky Moon, LLC business // buy : 2014-12-18 Amazon Registry Services, Inc. buy // buzz : 2013-10-02 DOTSTRATEGY CO. buzz // bzh : 2014-02-27 Association www.bzh bzh // cab : 2013-10-24 Binky Moon, LLC cab // cafe : 2015-02-11 Binky Moon, LLC cafe // cal : 2014-07-24 Charleston Road Registry Inc. cal // call : 2014-12-18 Amazon Registry Services, Inc. call // calvinklein : 2015-07-30 PVH gTLD Holdings LLC calvinklein // cam : 2016-04-21 Cam Connecting SARL cam // camera : 2013-08-27 Binky Moon, LLC camera // camp : 2013-11-07 Binky Moon, LLC camp // canon : 2014-09-12 Canon Inc. canon // capetown : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry capetown // capital : 2014-03-06 Binky Moon, LLC capital // capitalone : 2015-08-06 Capital One Financial Corporation capitalone // car : 2015-01-22 XYZ.COM LLC car // caravan : 2013-12-12 Caravan International, Inc. caravan // cards : 2013-12-05 Binky Moon, LLC cards // care : 2014-03-06 Binky Moon, LLC care // career : 2013-10-09 dotCareer LLC career // careers : 2013-10-02 Binky Moon, LLC careers // cars : 2014-11-13 XYZ.COM LLC cars // casa : 2013-11-21 Registry Services, LLC casa // case : 2015-09-03 Digity, LLC case // cash : 2014-03-06 Binky Moon, LLC cash // casino : 2014-12-18 Binky Moon, LLC casino // catering : 2013-12-05 Binky Moon, LLC catering // catholic : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) catholic // cba : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA cba // cbn : 2014-08-22 The Christian Broadcasting Network, Inc. cbn // cbre : 2015-07-02 CBRE, Inc. cbre // cbs : 2015-08-06 CBS Domains Inc. cbs // center : 2013-11-07 Binky Moon, LLC center // ceo : 2013-11-07 CEOTLD Pty Ltd ceo // cern : 2014-06-05 European Organization for Nuclear Research ("CERN") cern // cfa : 2014-08-28 CFA Institute cfa // cfd : 2014-12-11 ShortDot SA cfd // chanel : 2015-04-09 Chanel International B.V. chanel // channel : 2014-05-08 Charleston Road Registry Inc. channel // charity : 2018-04-11 Public Interest Registry charity // chase : 2015-04-30 JPMorgan Chase Bank, National Association chase // chat : 2014-12-04 Binky Moon, LLC chat // cheap : 2013-11-14 Binky Moon, LLC cheap // chintai : 2015-06-11 CHINTAI Corporation chintai // christmas : 2013-11-21 XYZ.COM LLC christmas // chrome : 2014-07-24 Charleston Road Registry Inc. chrome // church : 2014-02-06 Binky Moon, LLC church // cipriani : 2015-02-19 Hotel Cipriani Srl cipriani // circle : 2014-12-18 Amazon Registry Services, Inc. circle // cisco : 2014-12-22 Cisco Technology, Inc. cisco // citadel : 2015-07-23 Citadel Domain LLC citadel // citi : 2015-07-30 Citigroup Inc. citi // citic : 2014-01-09 CITIC Group Corporation citic // city : 2014-05-29 Binky Moon, LLC city // cityeats : 2014-12-11 Lifestyle Domain Holdings, Inc. cityeats // claims : 2014-03-20 Binky Moon, LLC claims // cleaning : 2013-12-05 Binky Moon, LLC cleaning // click : 2014-06-05 Internet Naming Company LLC click // clinic : 2014-03-20 Binky Moon, LLC clinic // clinique : 2015-10-01 The Estée Lauder Companies Inc. clinique // clothing : 2013-08-27 Binky Moon, LLC clothing // cloud : 2015-04-16 Aruba PEC S.p.A. cloud // club : 2013-11-08 Registry Services, LLC club // clubmed : 2015-06-25 Club Méditerranée S.A. clubmed // coach : 2014-10-09 Binky Moon, LLC coach // codes : 2013-10-31 Binky Moon, LLC codes // coffee : 2013-10-17 Binky Moon, LLC coffee // college : 2014-01-16 XYZ.COM LLC college // cologne : 2014-02-05 dotKoeln GmbH cologne // comcast : 2015-07-23 Comcast IP Holdings I, LLC comcast // commbank : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA commbank // community : 2013-12-05 Binky Moon, LLC community // company : 2013-11-07 Binky Moon, LLC company // compare : 2015-10-08 Registry Services, LLC compare // computer : 2013-10-24 Binky Moon, LLC computer // comsec : 2015-01-08 VeriSign, Inc. comsec // condos : 2013-12-05 Binky Moon, LLC condos // construction : 2013-09-16 Binky Moon, LLC construction // consulting : 2013-12-05 Dog Beach, LLC consulting // contact : 2015-01-08 Dog Beach, LLC contact // contractors : 2013-09-10 Binky Moon, LLC contractors // cooking : 2013-11-21 Registry Services, LLC cooking // cool : 2013-11-14 Binky Moon, LLC cool // corsica : 2014-09-25 Collectivité de Corse corsica // country : 2013-12-19 Internet Naming Company LLC country // coupon : 2015-02-26 Amazon Registry Services, Inc. coupon // coupons : 2015-03-26 Binky Moon, LLC coupons // courses : 2014-12-04 Registry Services, LLC courses // cpa : 2019-06-10 American Institute of Certified Public Accountants cpa // credit : 2014-03-20 Binky Moon, LLC credit // creditcard : 2014-03-20 Binky Moon, LLC creditcard // creditunion : 2015-01-22 DotCooperation LLC creditunion // cricket : 2014-10-09 dot Cricket Limited cricket // crown : 2014-10-24 Crown Equipment Corporation crown // crs : 2014-04-03 Federated Co-operatives Limited crs // cruise : 2015-12-10 Viking River Cruises (Bermuda) Ltd. cruise // cruises : 2013-12-05 Binky Moon, LLC cruises // cuisinella : 2014-04-03 SCHMIDT GROUPE S.A.S. cuisinella // cymru : 2014-05-08 Nominet UK cymru // cyou : 2015-01-22 ShortDot SA cyou // dabur : 2014-02-06 Dabur India Limited dabur // dad : 2014-01-23 Charleston Road Registry Inc. dad // dance : 2013-10-24 Dog Beach, LLC dance // data : 2016-06-02 Dish DBS Corporation data // date : 2014-11-20 dot Date Limited date // dating : 2013-12-05 Binky Moon, LLC dating // datsun : 2014-03-27 NISSAN MOTOR CO., LTD. datsun // day : 2014-01-30 Charleston Road Registry Inc. day // dclk : 2014-11-20 Charleston Road Registry Inc. dclk // dds : 2015-05-07 Registry Services, LLC dds // deal : 2015-06-25 Amazon Registry Services, Inc. deal // dealer : 2014-12-22 Intercap Registry Inc. dealer // deals : 2014-05-22 Binky Moon, LLC deals // degree : 2014-03-06 Dog Beach, LLC degree // delivery : 2014-09-11 Binky Moon, LLC delivery // dell : 2014-10-24 Dell Inc. dell // deloitte : 2015-07-31 Deloitte Touche Tohmatsu deloitte // delta : 2015-02-19 Delta Air Lines, Inc. delta // democrat : 2013-10-24 Dog Beach, LLC democrat // dental : 2014-03-20 Binky Moon, LLC dental // dentist : 2014-03-20 Dog Beach, LLC dentist // desi : 2013-11-14 Desi Networks LLC desi // design : 2014-11-07 Registry Services, LLC design // dev : 2014-10-16 Charleston Road Registry Inc. dev // dhl : 2015-07-23 Deutsche Post AG dhl // diamonds : 2013-09-22 Binky Moon, LLC diamonds // diet : 2014-06-26 XYZ.COM LLC diet // digital : 2014-03-06 Binky Moon, LLC digital // direct : 2014-04-10 Binky Moon, LLC direct // directory : 2013-09-20 Binky Moon, LLC directory // discount : 2014-03-06 Binky Moon, LLC discount // discover : 2015-07-23 Discover Financial Services discover // dish : 2015-07-30 Dish DBS Corporation dish // diy : 2015-11-05 Lifestyle Domain Holdings, Inc. diy // dnp : 2013-12-13 Dai Nippon Printing Co., Ltd. dnp // docs : 2014-10-16 Charleston Road Registry Inc. docs // doctor : 2016-06-02 Binky Moon, LLC doctor // dog : 2014-12-04 Binky Moon, LLC dog // domains : 2013-10-17 Binky Moon, LLC domains // dot : 2015-05-21 Dish DBS Corporation dot // download : 2014-11-20 dot Support Limited download // drive : 2015-03-05 Charleston Road Registry Inc. drive // dtv : 2015-06-04 Dish DBS Corporation dtv // dubai : 2015-01-01 Dubai Smart Government Department dubai // dunlop : 2015-07-02 The Goodyear Tire & Rubber Company dunlop // dupont : 2015-06-25 DuPont Specialty Products USA, LLC dupont // durban : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry durban // dvag : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG dvag // dvr : 2016-05-26 DISH Technologies L.L.C. dvr // earth : 2014-12-04 Interlink Systems Innovation Institute K.K. earth // eat : 2014-01-23 Charleston Road Registry Inc. eat // eco : 2016-07-08 Big Room Inc. eco // edeka : 2014-12-18 EDEKA Verband kaufmännischer Genossenschaften e.V. edeka // education : 2013-11-07 Binky Moon, LLC education // email : 2013-10-31 Binky Moon, LLC email // emerck : 2014-04-03 Merck KGaA emerck // energy : 2014-09-11 Binky Moon, LLC energy // engineer : 2014-03-06 Dog Beach, LLC engineer // engineering : 2014-03-06 Binky Moon, LLC engineering // enterprises : 2013-09-20 Binky Moon, LLC enterprises // epson : 2014-12-04 Seiko Epson Corporation epson // equipment : 2013-08-27 Binky Moon, LLC equipment // ericsson : 2015-07-09 Telefonaktiebolaget L M Ericsson ericsson // erni : 2014-04-03 ERNI Group Holding AG erni // esq : 2014-05-08 Charleston Road Registry Inc. esq // estate : 2013-08-27 Binky Moon, LLC estate // etisalat : 2015-09-03 Emirates Telecommunications Corporation (trading as Etisalat) etisalat // eurovision : 2014-04-24 European Broadcasting Union (EBU) eurovision // eus : 2013-12-12 Puntueus Fundazioa eus // events : 2013-12-05 Binky Moon, LLC events // exchange : 2014-03-06 Binky Moon, LLC exchange // expert : 2013-11-21 Binky Moon, LLC expert // exposed : 2013-12-05 Binky Moon, LLC exposed // express : 2015-02-11 Binky Moon, LLC express // extraspace : 2015-05-14 Extra Space Storage LLC extraspace // fage : 2014-12-18 Fage International S.A. fage // fail : 2014-03-06 Binky Moon, LLC fail // fairwinds : 2014-11-13 FairWinds Partners, LLC fairwinds // faith : 2014-11-20 dot Faith Limited faith // family : 2015-04-02 Dog Beach, LLC family // fan : 2014-03-06 Dog Beach, LLC fan // fans : 2014-11-07 ZDNS International Limited fans // farm : 2013-11-07 Binky Moon, LLC farm // farmers : 2015-07-09 Farmers Insurance Exchange farmers // fashion : 2014-07-03 Registry Services, LLC fashion // fast : 2014-12-18 Amazon Registry Services, Inc. fast // fedex : 2015-08-06 Federal Express Corporation fedex // feedback : 2013-12-19 Top Level Spectrum, Inc. feedback // ferrari : 2015-07-31 Fiat Chrysler Automobiles N.V. ferrari // ferrero : 2014-12-18 Ferrero Trading Lux S.A. ferrero // fidelity : 2015-07-30 Fidelity Brokerage Services LLC fidelity // fido : 2015-08-06 Rogers Communications Canada Inc. fido // film : 2015-01-08 Motion Picture Domain Registry Pty Ltd film // final : 2014-10-16 Núcleo de Informação e Coordenação do Ponto BR - NIC.br final // finance : 2014-03-20 Binky Moon, LLC finance // financial : 2014-03-06 Binky Moon, LLC financial // fire : 2015-06-25 Amazon Registry Services, Inc. fire // firestone : 2014-12-18 Bridgestone Licensing Services, Inc firestone // firmdale : 2014-03-27 Firmdale Holdings Limited firmdale // fish : 2013-12-12 Binky Moon, LLC fish // fishing : 2013-11-21 Registry Services, LLC fishing // fit : 2014-11-07 Registry Services, LLC fit // fitness : 2014-03-06 Binky Moon, LLC fitness // flickr : 2015-04-02 Flickr, Inc. flickr // flights : 2013-12-05 Binky Moon, LLC flights // flir : 2015-07-23 FLIR Systems, Inc. flir // florist : 2013-11-07 Binky Moon, LLC florist // flowers : 2014-10-09 XYZ.COM LLC flowers // fly : 2014-05-08 Charleston Road Registry Inc. fly // foo : 2014-01-23 Charleston Road Registry Inc. foo // food : 2016-04-21 Lifestyle Domain Holdings, Inc. food // football : 2014-12-18 Binky Moon, LLC football // ford : 2014-11-13 Ford Motor Company ford // forex : 2014-12-11 Dog Beach, LLC forex // forsale : 2014-05-22 Dog Beach, LLC forsale // forum : 2015-04-02 Fegistry, LLC forum // foundation : 2013-12-05 Public Interest Registry foundation // fox : 2015-09-11 FOX Registry, LLC fox // free : 2015-12-10 Amazon Registry Services, Inc. free // fresenius : 2015-07-30 Fresenius Immobilien-Verwaltungs-GmbH fresenius // frl : 2014-05-15 FRLregistry B.V. frl // frogans : 2013-12-19 OP3FT frogans // frontdoor : 2015-07-02 Lifestyle Domain Holdings, Inc. frontdoor // frontier : 2015-02-05 Frontier Communications Corporation frontier // ftr : 2015-07-16 Frontier Communications Corporation ftr // fujitsu : 2015-07-30 Fujitsu Limited fujitsu // fun : 2016-01-14 Radix FZC fun // fund : 2014-03-20 Binky Moon, LLC fund // furniture : 2014-03-20 Binky Moon, LLC furniture // futbol : 2013-09-20 Dog Beach, LLC futbol // fyi : 2015-04-02 Binky Moon, LLC fyi // gal : 2013-11-07 Asociación puntoGAL gal // gallery : 2013-09-13 Binky Moon, LLC gallery // gallo : 2015-06-11 Gallo Vineyards, Inc. gallo // gallup : 2015-02-19 Gallup, Inc. gallup // game : 2015-05-28 XYZ.COM LLC game // games : 2015-05-28 Dog Beach, LLC games // gap : 2015-07-31 The Gap, Inc. gap // garden : 2014-06-26 Registry Services, LLC garden // gay : 2019-05-23 Registry Services, LLC gay // gbiz : 2014-07-17 Charleston Road Registry Inc. gbiz // gdn : 2014-07-31 Joint Stock Company "Navigation-information systems" gdn // gea : 2014-12-04 GEA Group Aktiengesellschaft gea // gent : 2014-01-23 Easyhost BV gent // genting : 2015-03-12 Resorts World Inc Pte. Ltd. genting // george : 2015-07-31 Wal-Mart Stores, Inc. george // ggee : 2014-01-09 GMO Internet, Inc. ggee // gift : 2013-10-17 DotGift, LLC gift // gifts : 2014-07-03 Binky Moon, LLC gifts // gives : 2014-03-06 Public Interest Registry gives // giving : 2014-11-13 Public Interest Registry giving // glass : 2013-11-07 Binky Moon, LLC glass // gle : 2014-07-24 Charleston Road Registry Inc. gle // global : 2014-04-17 Identity Digital Limited global // globo : 2013-12-19 Globo Comunicação e Participações S.A globo // gmail : 2014-05-01 Charleston Road Registry Inc. gmail // gmbh : 2016-01-29 Binky Moon, LLC gmbh // gmo : 2014-01-09 GMO Internet, Inc. gmo // gmx : 2014-04-24 1&1 Mail & Media GmbH gmx // godaddy : 2015-07-23 Go Daddy East, LLC godaddy // gold : 2015-01-22 Binky Moon, LLC gold // goldpoint : 2014-11-20 YODOBASHI CAMERA CO.,LTD. goldpoint // golf : 2014-12-18 Binky Moon, LLC golf // goo : 2014-12-18 NTT Resonant Inc. goo // goodyear : 2015-07-02 The Goodyear Tire & Rubber Company goodyear // goog : 2014-11-20 Charleston Road Registry Inc. goog // google : 2014-07-24 Charleston Road Registry Inc. google // gop : 2014-01-16 Republican State Leadership Committee, Inc. gop // got : 2014-12-18 Amazon Registry Services, Inc. got // grainger : 2015-05-07 Grainger Registry Services, LLC grainger // graphics : 2013-09-13 Binky Moon, LLC graphics // gratis : 2014-03-20 Binky Moon, LLC gratis // green : 2014-05-08 Identity Digital Limited green // gripe : 2014-03-06 Binky Moon, LLC gripe // grocery : 2016-06-16 Wal-Mart Stores, Inc. grocery // group : 2014-08-15 Binky Moon, LLC group // guardian : 2015-07-30 The Guardian Life Insurance Company of America guardian // gucci : 2014-11-13 Guccio Gucci S.p.a. gucci // guge : 2014-08-28 Charleston Road Registry Inc. guge // guide : 2013-09-13 Binky Moon, LLC guide // guitars : 2013-11-14 XYZ.COM LLC guitars // guru : 2013-08-27 Binky Moon, LLC guru // hair : 2015-12-03 XYZ.COM LLC hair // hamburg : 2014-02-20 Hamburg Top-Level-Domain GmbH hamburg // hangout : 2014-11-13 Charleston Road Registry Inc. hangout // haus : 2013-12-05 Dog Beach, LLC haus // hbo : 2015-07-30 HBO Registry Services, Inc. hbo // hdfc : 2015-07-30 HOUSING DEVELOPMENT FINANCE CORPORATION LIMITED hdfc // hdfcbank : 2015-02-12 HDFC Bank Limited hdfcbank // health : 2015-02-11 Registry Services, LLC health // healthcare : 2014-06-12 Binky Moon, LLC healthcare // help : 2014-06-26 Innovation service Limited help // helsinki : 2015-02-05 City of Helsinki helsinki // here : 2014-02-06 Charleston Road Registry Inc. here // hermes : 2014-07-10 HERMES INTERNATIONAL hermes // hiphop : 2014-03-06 Dot Hip Hop, LLC hiphop // hisamitsu : 2015-07-16 Hisamitsu Pharmaceutical Co.,Inc. hisamitsu // hitachi : 2014-10-31 Hitachi, Ltd. hitachi // hiv : 2014-03-13 Internet Naming Company LLC hiv // hkt : 2015-05-14 PCCW-HKT DataCom Services Limited hkt // hockey : 2015-03-19 Binky Moon, LLC hockey // holdings : 2013-08-27 Binky Moon, LLC holdings // holiday : 2013-11-07 Binky Moon, LLC holiday // homedepot : 2015-04-02 Home Depot Product Authority, LLC homedepot // homegoods : 2015-07-16 The TJX Companies, Inc. homegoods // homes : 2014-01-09 XYZ.COM LLC homes // homesense : 2015-07-16 The TJX Companies, Inc. homesense // honda : 2014-12-18 Honda Motor Co., Ltd. honda // horse : 2013-11-21 Registry Services, LLC horse // hospital : 2016-10-20 Binky Moon, LLC hospital // host : 2014-04-17 Radix FZC host // hosting : 2014-05-29 XYZ.COM LLC hosting // hot : 2015-08-27 Amazon Registry Services, Inc. hot // hoteles : 2015-03-05 Travel Reservations SRL hoteles // hotels : 2016-04-07 Booking.com B.V. hotels // hotmail : 2014-12-18 Microsoft Corporation hotmail // house : 2013-11-07 Binky Moon, LLC house // how : 2014-01-23 Charleston Road Registry Inc. how // hsbc : 2014-10-24 HSBC Global Services (UK) Limited hsbc // hughes : 2015-07-30 Hughes Satellite Systems Corporation hughes // hyatt : 2015-07-30 Hyatt GTLD, L.L.C. hyatt // hyundai : 2015-07-09 Hyundai Motor Company hyundai // ibm : 2014-07-31 International Business Machines Corporation ibm // icbc : 2015-02-19 Industrial and Commercial Bank of China Limited icbc // ice : 2014-10-30 IntercontinentalExchange, Inc. ice // icu : 2015-01-08 ShortDot SA icu // ieee : 2015-07-23 IEEE Global LLC ieee // ifm : 2014-01-30 ifm electronic gmbh ifm // ikano : 2015-07-09 Ikano S.A. ikano // imamat : 2015-08-06 Fondation Aga Khan (Aga Khan Foundation) imamat // imdb : 2015-06-25 Amazon Registry Services, Inc. imdb // immo : 2014-07-10 Binky Moon, LLC immo // immobilien : 2013-11-07 Dog Beach, LLC immobilien // inc : 2018-03-10 Intercap Registry Inc. inc // industries : 2013-12-05 Binky Moon, LLC industries // infiniti : 2014-03-27 NISSAN MOTOR CO., LTD. infiniti // ing : 2014-01-23 Charleston Road Registry Inc. ing // ink : 2013-12-05 Registry Services, LLC ink // institute : 2013-11-07 Binky Moon, LLC institute // insurance : 2015-02-19 fTLD Registry Services LLC insurance // insure : 2014-03-20 Binky Moon, LLC insure // international : 2013-11-07 Binky Moon, LLC international // intuit : 2015-07-30 Intuit Administrative Services, Inc. intuit // investments : 2014-03-20 Binky Moon, LLC investments // ipiranga : 2014-08-28 Ipiranga Produtos de Petroleo S.A. ipiranga // irish : 2014-08-07 Binky Moon, LLC irish // ismaili : 2015-08-06 Fondation Aga Khan (Aga Khan Foundation) ismaili // ist : 2014-08-28 Istanbul Metropolitan Municipality ist // istanbul : 2014-08-28 Istanbul Metropolitan Municipality istanbul // itau : 2014-10-02 Itau Unibanco Holding S.A. itau // itv : 2015-07-09 ITV Services Limited itv // jaguar : 2014-11-13 Jaguar Land Rover Ltd jaguar // java : 2014-06-19 Oracle Corporation java // jcb : 2014-11-20 JCB Co., Ltd. jcb // jeep : 2015-07-30 FCA US LLC. jeep // jetzt : 2014-01-09 Binky Moon, LLC jetzt // jewelry : 2015-03-05 Binky Moon, LLC jewelry // jio : 2015-04-02 Reliance Industries Limited jio // jll : 2015-04-02 Jones Lang LaSalle Incorporated jll // jmp : 2015-03-26 Matrix IP LLC jmp // jnj : 2015-06-18 Johnson & Johnson Services, Inc. jnj // joburg : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry joburg // jot : 2014-12-18 Amazon Registry Services, Inc. jot // joy : 2014-12-18 Amazon Registry Services, Inc. joy // jpmorgan : 2015-04-30 JPMorgan Chase Bank, National Association jpmorgan // jprs : 2014-09-18 Japan Registry Services Co., Ltd. jprs // juegos : 2014-03-20 Internet Naming Company LLC juegos // juniper : 2015-07-30 JUNIPER NETWORKS, INC. juniper // kaufen : 2013-11-07 Dog Beach, LLC kaufen // kddi : 2014-09-12 KDDI CORPORATION kddi // kerryhotels : 2015-04-30 Kerry Trading Co. Limited kerryhotels // kerrylogistics : 2015-04-09 Kerry Trading Co. Limited kerrylogistics // kerryproperties : 2015-04-09 Kerry Trading Co. Limited kerryproperties // kfh : 2014-12-04 Kuwait Finance House kfh // kia : 2015-07-09 KIA MOTORS CORPORATION kia // kids : 2021-08-13 DotKids Foundation Limited kids // kim : 2013-09-23 Identity Digital Limited kim // kinder : 2014-11-07 Ferrero Trading Lux S.A. kinder // kindle : 2015-06-25 Amazon Registry Services, Inc. kindle // kitchen : 2013-09-20 Binky Moon, LLC kitchen // kiwi : 2013-09-20 DOT KIWI LIMITED kiwi // koeln : 2014-01-09 dotKoeln GmbH koeln // komatsu : 2015-01-08 Komatsu Ltd. komatsu // kosher : 2015-08-20 Kosher Marketing Assets LLC kosher // kpmg : 2015-04-23 KPMG International Cooperative (KPMG International Genossenschaft) kpmg // kpn : 2015-01-08 Koninklijke KPN N.V. kpn // krd : 2013-12-05 KRG Department of Information Technology krd // kred : 2013-12-19 KredTLD Pty Ltd kred // kuokgroup : 2015-04-09 Kerry Trading Co. Limited kuokgroup // kyoto : 2014-11-07 Academic Institution: Kyoto Jyoho Gakuen kyoto // lacaixa : 2014-01-09 Fundación Bancaria Caixa d’Estalvis i Pensions de Barcelona, “la Caixa” lacaixa // lamborghini : 2015-06-04 Automobili Lamborghini S.p.A. lamborghini // lamer : 2015-10-01 The Estée Lauder Companies Inc. lamer // lancaster : 2015-02-12 LANCASTER lancaster // land : 2013-09-10 Binky Moon, LLC land // landrover : 2014-11-13 Jaguar Land Rover Ltd landrover // lanxess : 2015-07-30 LANXESS Corporation lanxess // lasalle : 2015-04-02 Jones Lang LaSalle Incorporated lasalle // lat : 2014-10-16 XYZ.COM LLC lat // latino : 2015-07-30 Dish DBS Corporation latino // latrobe : 2014-06-16 La Trobe University latrobe // law : 2015-01-22 Registry Services, LLC law // lawyer : 2014-03-20 Dog Beach, LLC lawyer // lds : 2014-03-20 IRI Domain Management, LLC lds // lease : 2014-03-06 Binky Moon, LLC lease // leclerc : 2014-08-07 A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc leclerc // lefrak : 2015-07-16 LeFrak Organization, Inc. lefrak // legal : 2014-10-16 Binky Moon, LLC legal // lego : 2015-07-16 LEGO Juris A/S lego // lexus : 2015-04-23 TOYOTA MOTOR CORPORATION lexus // lgbt : 2014-05-08 Identity Digital Limited lgbt // lidl : 2014-09-18 Schwarz Domains und Services GmbH & Co. KG lidl // life : 2014-02-06 Binky Moon, LLC life // lifeinsurance : 2015-01-15 American Council of Life Insurers lifeinsurance // lifestyle : 2014-12-11 Lifestyle Domain Holdings, Inc. lifestyle // lighting : 2013-08-27 Binky Moon, LLC lighting // like : 2014-12-18 Amazon Registry Services, Inc. like // lilly : 2015-07-31 Eli Lilly and Company lilly // limited : 2014-03-06 Binky Moon, LLC limited // limo : 2013-10-17 Binky Moon, LLC limo // lincoln : 2014-11-13 Ford Motor Company lincoln // link : 2013-11-14 Nova Registry Ltd link // lipsy : 2015-06-25 Lipsy Ltd lipsy // live : 2014-12-04 Dog Beach, LLC live // living : 2015-07-30 Lifestyle Domain Holdings, Inc. living // llc : 2017-12-14 Identity Digital Limited llc // llp : 2019-08-26 Intercap Registry Inc. llp // loan : 2014-11-20 dot Loan Limited loan // loans : 2014-03-20 Binky Moon, LLC loans // locker : 2015-06-04 Dish DBS Corporation locker // locus : 2015-06-25 Locus Analytics LLC locus // lol : 2015-01-30 XYZ.COM LLC lol // london : 2013-11-14 Dot London Domains Limited london // lotte : 2014-11-07 Lotte Holdings Co., Ltd. lotte // lotto : 2014-04-10 Identity Digital Limited lotto // love : 2014-12-22 Merchant Law Group LLP love // lpl : 2015-07-30 LPL Holdings, Inc. lpl // lplfinancial : 2015-07-30 LPL Holdings, Inc. lplfinancial // ltd : 2014-09-25 Binky Moon, LLC ltd // ltda : 2014-04-17 InterNetX, Corp ltda // lundbeck : 2015-08-06 H. Lundbeck A/S lundbeck // luxe : 2014-01-09 Registry Services, LLC luxe // luxury : 2013-10-17 Luxury Partners, LLC luxury // madrid : 2014-05-01 Comunidad de Madrid madrid // maif : 2014-10-02 Mutuelle Assurance Instituteur France (MAIF) maif // maison : 2013-12-05 Binky Moon, LLC maison // makeup : 2015-01-15 XYZ.COM LLC makeup // man : 2014-12-04 MAN SE man // management : 2013-11-07 Binky Moon, LLC management // mango : 2013-10-24 PUNTO FA S.L. mango // map : 2016-06-09 Charleston Road Registry Inc. map // market : 2014-03-06 Dog Beach, LLC market // marketing : 2013-11-07 Binky Moon, LLC marketing // markets : 2014-12-11 Dog Beach, LLC markets // marriott : 2014-10-09 Marriott Worldwide Corporation marriott // marshalls : 2015-07-16 The TJX Companies, Inc. marshalls // mattel : 2015-08-06 Mattel Sites, Inc. mattel // mba : 2015-04-02 Binky Moon, LLC mba // mckinsey : 2015-07-31 McKinsey Holdings, Inc. mckinsey // med : 2015-08-06 Medistry LLC med // media : 2014-03-06 Binky Moon, LLC media // meet : 2014-01-16 Charleston Road Registry Inc. meet // melbourne : 2014-05-29 The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation melbourne // meme : 2014-01-30 Charleston Road Registry Inc. meme // memorial : 2014-10-16 Dog Beach, LLC memorial // men : 2015-02-26 Exclusive Registry Limited men // menu : 2013-09-11 Dot Menu Registry, LLC menu // merckmsd : 2016-07-14 MSD Registry Holdings, Inc. merckmsd // miami : 2013-12-19 Registry Services, LLC miami // microsoft : 2014-12-18 Microsoft Corporation microsoft // mini : 2014-01-09 Bayerische Motoren Werke Aktiengesellschaft mini // mint : 2015-07-30 Intuit Administrative Services, Inc. mint // mit : 2015-07-02 Massachusetts Institute of Technology mit // mitsubishi : 2015-07-23 Mitsubishi Corporation mitsubishi // mlb : 2015-05-21 MLB Advanced Media DH, LLC mlb // mls : 2015-04-23 The Canadian Real Estate Association mls // mma : 2014-11-07 MMA IARD mma // mobile : 2016-06-02 Dish DBS Corporation mobile // moda : 2013-11-07 Dog Beach, LLC moda // moe : 2013-11-13 Interlink Systems Innovation Institute K.K. moe // moi : 2014-12-18 Amazon Registry Services, Inc. moi // mom : 2015-04-16 XYZ.COM LLC mom // monash : 2013-09-30 Monash University monash // money : 2014-10-16 Binky Moon, LLC money // monster : 2015-09-11 XYZ.COM LLC monster // mormon : 2013-12-05 IRI Domain Management, LLC mormon // mortgage : 2014-03-20 Dog Beach, LLC mortgage // moscow : 2013-12-19 Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) moscow // moto : 2015-06-04 Motorola Trademark Holdings, LLC moto // motorcycles : 2014-01-09 XYZ.COM LLC motorcycles // mov : 2014-01-30 Charleston Road Registry Inc. mov // movie : 2015-02-05 Binky Moon, LLC movie // msd : 2015-07-23 MSD Registry Holdings, Inc. msd // mtn : 2014-12-04 MTN Dubai Limited mtn // mtr : 2015-03-12 MTR Corporation Limited mtr // music : 2021-05-04 DotMusic Limited music // mutual : 2015-04-02 Northwestern Mutual MU TLD Registry, LLC mutual // nab : 2015-08-20 National Australia Bank Limited nab // nagoya : 2013-10-24 GMO Registry, Inc. nagoya // natura : 2015-03-12 NATURA COSMÉTICOS S.A. natura // navy : 2014-03-06 Dog Beach, LLC navy // nba : 2015-07-31 NBA REGISTRY, LLC nba // nec : 2015-01-08 NEC Corporation nec // netbank : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA netbank // netflix : 2015-06-18 Netflix, Inc. netflix // network : 2013-11-14 Binky Moon, LLC network // neustar : 2013-12-05 NeuStar, Inc. neustar // new : 2014-01-30 Charleston Road Registry Inc. new // news : 2014-12-18 Dog Beach, LLC news // next : 2015-06-18 Next plc next // nextdirect : 2015-06-18 Next plc nextdirect // nexus : 2014-07-24 Charleston Road Registry Inc. nexus // nfl : 2015-07-23 NFL Reg Ops LLC nfl // ngo : 2014-03-06 Public Interest Registry ngo // nhk : 2014-02-13 Japan Broadcasting Corporation (NHK) nhk // nico : 2014-12-04 DWANGO Co., Ltd. nico // nike : 2015-07-23 NIKE, Inc. nike // nikon : 2015-05-21 NIKON CORPORATION nikon // ninja : 2013-11-07 Dog Beach, LLC ninja // nissan : 2014-03-27 NISSAN MOTOR CO., LTD. nissan // nissay : 2015-10-29 Nippon Life Insurance Company nissay // nokia : 2015-01-08 Nokia Corporation nokia // northwesternmutual : 2015-06-18 Northwestern Mutual Registry, LLC northwesternmutual // norton : 2014-12-04 NortonLifeLock Inc. norton // now : 2015-06-25 Amazon Registry Services, Inc. now // nowruz : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. nowruz // nowtv : 2015-05-14 Starbucks (HK) Limited nowtv // nra : 2014-05-22 NRA Holdings Company, INC. nra // nrw : 2013-11-21 Minds + Machines GmbH nrw // ntt : 2014-10-31 NIPPON TELEGRAPH AND TELEPHONE CORPORATION ntt // nyc : 2014-01-23 The City of New York by and through the New York City Department of Information Technology & Telecommunications nyc // obi : 2014-09-25 OBI Group Holding SE & Co. KGaA obi // observer : 2015-04-30 Dog Beach, LLC observer // office : 2015-03-12 Microsoft Corporation office // okinawa : 2013-12-05 BRregistry, Inc. okinawa // olayan : 2015-05-14 Competrol (Luxembourg) Sarl olayan // olayangroup : 2015-05-14 Competrol (Luxembourg) Sarl olayangroup // oldnavy : 2015-07-31 The Gap, Inc. oldnavy // ollo : 2015-06-04 Dish DBS Corporation ollo // omega : 2015-01-08 The Swatch Group Ltd omega // one : 2014-11-07 One.com A/S one // ong : 2014-03-06 Public Interest Registry ong // onl : 2013-09-16 iRegistry GmbH onl // online : 2015-01-15 Radix FZC online // ooo : 2014-01-09 INFIBEAM AVENUES LIMITED ooo // open : 2015-07-31 American Express Travel Related Services Company, Inc. open // oracle : 2014-06-19 Oracle Corporation oracle // orange : 2015-03-12 Orange Brand Services Limited orange // organic : 2014-03-27 Identity Digital Limited organic // origins : 2015-10-01 The Estée Lauder Companies Inc. origins // osaka : 2014-09-04 Osaka Registry Co., Ltd. osaka // otsuka : 2013-10-11 Otsuka Holdings Co., Ltd. otsuka // ott : 2015-06-04 Dish DBS Corporation ott // ovh : 2014-01-16 MédiaBC ovh // page : 2014-12-04 Charleston Road Registry Inc. page // panasonic : 2015-07-30 Panasonic Holdings Corporation panasonic // paris : 2014-01-30 City of Paris paris // pars : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. pars // partners : 2013-12-05 Binky Moon, LLC partners // parts : 2013-12-05 Binky Moon, LLC parts // party : 2014-09-11 Blue Sky Registry Limited party // passagens : 2015-03-05 Travel Reservations SRL passagens // pay : 2015-08-27 Amazon Registry Services, Inc. pay // pccw : 2015-05-14 PCCW Enterprises Limited pccw // pet : 2015-05-07 Identity Digital Limited pet // pfizer : 2015-09-11 Pfizer Inc. pfizer // pharmacy : 2014-06-19 National Association of Boards of Pharmacy pharmacy // phd : 2016-07-28 Charleston Road Registry Inc. phd // philips : 2014-11-07 Koninklijke Philips N.V. philips // phone : 2016-06-02 Dish DBS Corporation phone // photo : 2013-11-14 Registry Services, LLC photo // photography : 2013-09-20 Binky Moon, LLC photography // photos : 2013-10-17 Binky Moon, LLC photos // physio : 2014-05-01 PhysBiz Pty Ltd physio // pics : 2013-11-14 XYZ.COM LLC pics // pictet : 2014-06-26 Pictet Europe S.A. pictet // pictures : 2014-03-06 Binky Moon, LLC pictures // pid : 2015-01-08 Top Level Spectrum, Inc. pid // pin : 2014-12-18 Amazon Registry Services, Inc. pin // ping : 2015-06-11 Ping Registry Provider, Inc. ping // pink : 2013-10-01 Identity Digital Limited pink // pioneer : 2015-07-16 Pioneer Corporation pioneer // pizza : 2014-06-26 Binky Moon, LLC pizza // place : 2014-04-24 Binky Moon, LLC place // play : 2015-03-05 Charleston Road Registry Inc. play // playstation : 2015-07-02 Sony Interactive Entertainment Inc. playstation // plumbing : 2013-09-10 Binky Moon, LLC plumbing // plus : 2015-02-05 Binky Moon, LLC plus // pnc : 2015-07-02 PNC Domain Co., LLC pnc // pohl : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG pohl // poker : 2014-07-03 Identity Digital Limited poker // politie : 2015-08-20 Politie Nederland politie // porn : 2014-10-16 ICM Registry PN LLC porn // pramerica : 2015-07-30 Prudential Financial, Inc. pramerica // praxi : 2013-12-05 Praxi S.p.A. praxi // press : 2014-04-03 Radix FZC press // prime : 2015-06-25 Amazon Registry Services, Inc. prime // prod : 2014-01-23 Charleston Road Registry Inc. prod // productions : 2013-12-05 Binky Moon, LLC productions // prof : 2014-07-24 Charleston Road Registry Inc. prof // progressive : 2015-07-23 Progressive Casualty Insurance Company progressive // promo : 2014-12-18 Identity Digital Limited promo // properties : 2013-12-05 Binky Moon, LLC properties // property : 2014-05-22 Internet Naming Company LLC property // protection : 2015-04-23 XYZ.COM LLC protection // pru : 2015-07-30 Prudential Financial, Inc. pru // prudential : 2015-07-30 Prudential Financial, Inc. prudential // pub : 2013-12-12 Dog Beach, LLC pub // pwc : 2015-10-29 PricewaterhouseCoopers LLP pwc // qpon : 2013-11-14 dotQPON LLC qpon // quebec : 2013-12-19 PointQuébec Inc quebec // quest : 2015-03-26 XYZ.COM LLC quest // racing : 2014-12-04 Premier Registry Limited racing // radio : 2016-07-21 European Broadcasting Union (EBU) radio // read : 2014-12-18 Amazon Registry Services, Inc. read // realestate : 2015-09-11 dotRealEstate LLC realestate // realtor : 2014-05-29 Real Estate Domains LLC realtor // realty : 2015-03-19 Dog Beach, LLC realty // recipes : 2013-10-17 Binky Moon, LLC recipes // red : 2013-11-07 Identity Digital Limited red // redstone : 2014-10-31 Redstone Haute Couture Co., Ltd. redstone // redumbrella : 2015-03-26 Travelers TLD, LLC redumbrella // rehab : 2014-03-06 Dog Beach, LLC rehab // reise : 2014-03-13 Binky Moon, LLC reise // reisen : 2014-03-06 Binky Moon, LLC reisen // reit : 2014-09-04 National Association of Real Estate Investment Trusts, Inc. reit // reliance : 2015-04-02 Reliance Industries Limited reliance // ren : 2013-12-12 ZDNS International Limited ren // rent : 2014-12-04 XYZ.COM LLC rent // rentals : 2013-12-05 Binky Moon, LLC rentals // repair : 2013-11-07 Binky Moon, LLC repair // report : 2013-12-05 Binky Moon, LLC report // republican : 2014-03-20 Dog Beach, LLC republican // rest : 2013-12-19 Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable rest // restaurant : 2014-07-03 Binky Moon, LLC restaurant // review : 2014-11-20 dot Review Limited review // reviews : 2013-09-13 Dog Beach, LLC reviews // rexroth : 2015-06-18 Robert Bosch GMBH rexroth // rich : 2013-11-21 iRegistry GmbH rich // richardli : 2015-05-14 Pacific Century Asset Management (HK) Limited richardli // ricoh : 2014-11-20 Ricoh Company, Ltd. ricoh // ril : 2015-04-02 Reliance Industries Limited ril // rio : 2014-02-27 Empresa Municipal de Informática SA - IPLANRIO rio // rip : 2014-07-10 Dog Beach, LLC rip // rocher : 2014-12-18 Ferrero Trading Lux S.A. rocher // rocks : 2013-11-14 Dog Beach, LLC rocks // rodeo : 2013-12-19 Registry Services, LLC rodeo // rogers : 2015-08-06 Rogers Communications Canada Inc. rogers // room : 2014-12-18 Amazon Registry Services, Inc. room // rsvp : 2014-05-08 Charleston Road Registry Inc. rsvp // rugby : 2016-12-15 World Rugby Strategic Developments Limited rugby // ruhr : 2013-10-02 dotSaarland GmbH ruhr // run : 2015-03-19 Binky Moon, LLC run // rwe : 2015-04-02 RWE AG rwe // ryukyu : 2014-01-09 BRregistry, Inc. ryukyu // saarland : 2013-12-12 dotSaarland GmbH saarland // safe : 2014-12-18 Amazon Registry Services, Inc. safe // safety : 2015-01-08 Safety Registry Services, LLC. safety // sakura : 2014-12-18 SAKURA Internet Inc. sakura // sale : 2014-10-16 Dog Beach, LLC sale // salon : 2014-12-11 Binky Moon, LLC salon // samsclub : 2015-07-31 Wal-Mart Stores, Inc. samsclub // samsung : 2014-04-03 SAMSUNG SDS CO., LTD samsung // sandvik : 2014-11-13 Sandvik AB sandvik // sandvikcoromant : 2014-11-07 Sandvik AB sandvikcoromant // sanofi : 2014-10-09 Sanofi sanofi // sap : 2014-03-27 SAP AG sap // sarl : 2014-07-03 Binky Moon, LLC sarl // sas : 2015-04-02 Research IP LLC sas // save : 2015-06-25 Amazon Registry Services, Inc. save // saxo : 2014-10-31 Saxo Bank A/S saxo // sbi : 2015-03-12 STATE BANK OF INDIA sbi // sbs : 2014-11-07 ShortDot SA sbs // sca : 2014-03-13 SVENSKA CELLULOSA AKTIEBOLAGET SCA (publ) sca // scb : 2014-02-20 The Siam Commercial Bank Public Company Limited ("SCB") scb // schaeffler : 2015-08-06 Schaeffler Technologies AG & Co. KG schaeffler // schmidt : 2014-04-03 SCHMIDT GROUPE S.A.S. schmidt // scholarships : 2014-04-24 Scholarships.com, LLC scholarships // school : 2014-12-18 Binky Moon, LLC school // schule : 2014-03-06 Binky Moon, LLC schule // schwarz : 2014-09-18 Schwarz Domains und Services GmbH & Co. KG schwarz // science : 2014-09-11 dot Science Limited science // scot : 2014-01-23 Dot Scot Registry Limited scot // search : 2016-06-09 Charleston Road Registry Inc. search // seat : 2014-05-22 SEAT, S.A. (Sociedad Unipersonal) seat // secure : 2015-08-27 Amazon Registry Services, Inc. secure // security : 2015-05-14 XYZ.COM LLC security // seek : 2014-12-04 Seek Limited seek // select : 2015-10-08 Registry Services, LLC select // sener : 2014-10-24 Sener Ingeniería y Sistemas, S.A. sener // services : 2014-02-27 Binky Moon, LLC services // seven : 2015-08-06 Seven West Media Ltd seven // sew : 2014-07-17 SEW-EURODRIVE GmbH & Co KG sew // sex : 2014-11-13 ICM Registry SX LLC sex // sexy : 2013-09-11 Internet Naming Company LLC sexy // sfr : 2015-08-13 Societe Francaise du Radiotelephone - SFR sfr // shangrila : 2015-09-03 Shangri‐La International Hotel Management Limited shangrila // sharp : 2014-05-01 Sharp Corporation sharp // shaw : 2015-04-23 Shaw Cablesystems G.P. shaw // shell : 2015-07-30 Shell Information Technology International Inc shell // shia : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. shia // shiksha : 2013-11-14 Identity Digital Limited shiksha // shoes : 2013-10-02 Binky Moon, LLC shoes // shop : 2016-04-08 GMO Registry, Inc. shop // shopping : 2016-03-31 Binky Moon, LLC shopping // shouji : 2015-01-08 Beijing Qihu Keji Co., Ltd. shouji // show : 2015-03-05 Binky Moon, LLC show // showtime : 2015-08-06 CBS Domains Inc. showtime // silk : 2015-06-25 Amazon Registry Services, Inc. silk // sina : 2015-03-12 Sina Corporation sina // singles : 2013-08-27 Binky Moon, LLC singles // site : 2015-01-15 Radix FZC site // ski : 2015-04-09 Identity Digital Limited ski // skin : 2015-01-15 XYZ.COM LLC skin // sky : 2014-06-19 Sky International AG sky // skype : 2014-12-18 Microsoft Corporation skype // sling : 2015-07-30 DISH Technologies L.L.C. sling // smart : 2015-07-09 Smart Communications, Inc. (SMART) smart // smile : 2014-12-18 Amazon Registry Services, Inc. smile // sncf : 2015-02-19 Société Nationale SNCF sncf // soccer : 2015-03-26 Binky Moon, LLC soccer // social : 2013-11-07 Dog Beach, LLC social // softbank : 2015-07-02 SoftBank Group Corp. softbank // software : 2014-03-20 Dog Beach, LLC software // sohu : 2013-12-19 Sohu.com Limited sohu // solar : 2013-11-07 Binky Moon, LLC solar // solutions : 2013-11-07 Binky Moon, LLC solutions // song : 2015-02-26 Amazon Registry Services, Inc. song // sony : 2015-01-08 Sony Corporation sony // soy : 2014-01-23 Charleston Road Registry Inc. soy // spa : 2019-09-19 Asia Spa and Wellness Promotion Council Limited spa // space : 2014-04-03 Radix FZC space // sport : 2017-11-16 SportAccord sport // spot : 2015-02-26 Amazon Registry Services, Inc. spot // srl : 2015-05-07 InterNetX, Corp srl // stada : 2014-11-13 STADA Arzneimittel AG stada // staples : 2015-07-30 Staples, Inc. staples // star : 2015-01-08 Star India Private Limited star // statebank : 2015-03-12 STATE BANK OF INDIA statebank // statefarm : 2015-07-30 State Farm Mutual Automobile Insurance Company statefarm // stc : 2014-10-09 Saudi Telecom Company stc // stcgroup : 2014-10-09 Saudi Telecom Company stcgroup // stockholm : 2014-12-18 Stockholms kommun stockholm // storage : 2014-12-22 XYZ.COM LLC storage // store : 2015-04-09 Radix FZC store // stream : 2016-01-08 dot Stream Limited stream // studio : 2015-02-11 Dog Beach, LLC studio // study : 2014-12-11 Registry Services, LLC study // style : 2014-12-04 Binky Moon, LLC style // sucks : 2014-12-22 Vox Populi Registry Ltd. sucks // supplies : 2013-12-19 Binky Moon, LLC supplies // supply : 2013-12-19 Binky Moon, LLC supply // support : 2013-10-24 Binky Moon, LLC support // surf : 2014-01-09 Registry Services, LLC surf // surgery : 2014-03-20 Binky Moon, LLC surgery // suzuki : 2014-02-20 SUZUKI MOTOR CORPORATION suzuki // swatch : 2015-01-08 The Swatch Group Ltd swatch // swiss : 2014-10-16 Swiss Confederation swiss // sydney : 2014-09-18 State of New South Wales, Department of Premier and Cabinet sydney // systems : 2013-11-07 Binky Moon, LLC systems // tab : 2014-12-04 Tabcorp Holdings Limited tab // taipei : 2014-07-10 Taipei City Government taipei // talk : 2015-04-09 Amazon Registry Services, Inc. talk // taobao : 2015-01-15 Alibaba Group Holding Limited taobao // target : 2015-07-31 Target Domain Holdings, LLC target // tatamotors : 2015-03-12 Tata Motors Ltd tatamotors // tatar : 2014-04-24 Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic" tatar // tattoo : 2013-08-30 Registry Services, LLC tattoo // tax : 2014-03-20 Binky Moon, LLC tax // taxi : 2015-03-19 Binky Moon, LLC taxi // tci : 2014-09-12 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. tci // tdk : 2015-06-11 TDK Corporation tdk // team : 2015-03-05 Binky Moon, LLC team // tech : 2015-01-30 Radix FZC tech // technology : 2013-09-13 Binky Moon, LLC technology // temasek : 2014-08-07 Temasek Holdings (Private) Limited temasek // tennis : 2014-12-04 Binky Moon, LLC tennis // teva : 2015-07-02 Teva Pharmaceutical Industries Limited teva // thd : 2015-04-02 Home Depot Product Authority, LLC thd // theater : 2015-03-19 Binky Moon, LLC theater // theatre : 2015-05-07 XYZ.COM LLC theatre // tiaa : 2015-07-23 Teachers Insurance and Annuity Association of America tiaa // tickets : 2015-02-05 XYZ.COM LLC tickets // tienda : 2013-11-14 Binky Moon, LLC tienda // tiffany : 2015-01-30 Tiffany and Company tiffany // tips : 2013-09-20 Binky Moon, LLC tips // tires : 2014-11-07 Binky Moon, LLC tires // tirol : 2014-04-24 punkt Tirol GmbH tirol // tjmaxx : 2015-07-16 The TJX Companies, Inc. tjmaxx // tjx : 2015-07-16 The TJX Companies, Inc. tjx // tkmaxx : 2015-07-16 The TJX Companies, Inc. tkmaxx // tmall : 2015-01-15 Alibaba Group Holding Limited tmall // today : 2013-09-20 Binky Moon, LLC today // tokyo : 2013-11-13 GMO Registry, Inc. tokyo // tools : 2013-11-21 Binky Moon, LLC tools // top : 2014-03-20 .TOP Registry top // toray : 2014-12-18 Toray Industries, Inc. toray // toshiba : 2014-04-10 TOSHIBA Corporation toshiba // total : 2015-08-06 TotalEnergies SE total // tours : 2015-01-22 Binky Moon, LLC tours // town : 2014-03-06 Binky Moon, LLC town // toyota : 2015-04-23 TOYOTA MOTOR CORPORATION toyota // toys : 2014-03-06 Binky Moon, LLC toys // trade : 2014-01-23 Elite Registry Limited trade // trading : 2014-12-11 Dog Beach, LLC trading // training : 2013-11-07 Binky Moon, LLC training // travel : 2015-10-09 Dog Beach, LLC travel // travelers : 2015-03-26 Travelers TLD, LLC travelers // travelersinsurance : 2015-03-26 Travelers TLD, LLC travelersinsurance // trust : 2014-10-16 Internet Naming Company LLC trust // trv : 2015-03-26 Travelers TLD, LLC trv // tube : 2015-06-11 Latin American Telecom LLC tube // tui : 2014-07-03 TUI AG tui // tunes : 2015-02-26 Amazon Registry Services, Inc. tunes // tushu : 2014-12-18 Amazon Registry Services, Inc. tushu // tvs : 2015-02-19 T V SUNDRAM IYENGAR & SONS LIMITED tvs // ubank : 2015-08-20 National Australia Bank Limited ubank // ubs : 2014-12-11 UBS AG ubs // unicom : 2015-10-15 China United Network Communications Corporation Limited unicom // university : 2014-03-06 Binky Moon, LLC university // uno : 2013-09-11 Radix FZC uno // uol : 2014-05-01 UBN INTERNET LTDA. uol // ups : 2015-06-25 UPS Market Driver, Inc. ups // vacations : 2013-12-05 Binky Moon, LLC vacations // vana : 2014-12-11 Lifestyle Domain Holdings, Inc. vana // vanguard : 2015-09-03 The Vanguard Group, Inc. vanguard // vegas : 2014-01-16 Dot Vegas, Inc. vegas // ventures : 2013-08-27 Binky Moon, LLC ventures // verisign : 2015-08-13 VeriSign, Inc. verisign // versicherung : 2014-03-20 tldbox GmbH versicherung // vet : 2014-03-06 Dog Beach, LLC vet // viajes : 2013-10-17 Binky Moon, LLC viajes // video : 2014-10-16 Dog Beach, LLC video // vig : 2015-05-14 VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe vig // viking : 2015-04-02 Viking River Cruises (Bermuda) Ltd. viking // villas : 2013-12-05 Binky Moon, LLC villas // vin : 2015-06-18 Binky Moon, LLC vin // vip : 2015-01-22 Registry Services, LLC vip // virgin : 2014-09-25 Virgin Enterprises Limited virgin // visa : 2015-07-30 Visa Worldwide Pte. Limited visa // vision : 2013-12-05 Binky Moon, LLC vision // viva : 2014-11-07 Saudi Telecom Company viva // vivo : 2015-07-31 Telefonica Brasil S.A. vivo // vlaanderen : 2014-02-06 DNS.be vzw vlaanderen // vodka : 2013-12-19 Registry Services, LLC vodka // volkswagen : 2015-05-14 Volkswagen Group of America Inc. volkswagen // volvo : 2015-11-12 Volvo Holding Sverige Aktiebolag volvo // vote : 2013-11-21 Monolith Registry LLC vote // voting : 2013-11-13 Valuetainment Corp. voting // voto : 2013-11-21 Monolith Registry LLC voto // voyage : 2013-08-27 Binky Moon, LLC voyage // vuelos : 2015-03-05 Travel Reservations SRL vuelos // wales : 2014-05-08 Nominet UK wales // walmart : 2015-07-31 Wal-Mart Stores, Inc. walmart // walter : 2014-11-13 Sandvik AB walter // wang : 2013-10-24 Zodiac Wang Limited wang // wanggou : 2014-12-18 Amazon Registry Services, Inc. wanggou // watch : 2013-11-14 Binky Moon, LLC watch // watches : 2014-12-22 Identity Digital Limited watches // weather : 2015-01-08 International Business Machines Corporation weather // weatherchannel : 2015-03-12 International Business Machines Corporation weatherchannel // webcam : 2014-01-23 dot Webcam Limited webcam // weber : 2015-06-04 Saint-Gobain Weber SA weber // website : 2014-04-03 Radix FZC website // wedding : 2014-04-24 Registry Services, LLC wedding // weibo : 2015-03-05 Sina Corporation weibo // weir : 2015-01-29 Weir Group IP Limited weir // whoswho : 2014-02-20 Who's Who Registry whoswho // wien : 2013-10-28 punkt.wien GmbH wien // wiki : 2013-11-07 Registry Services, LLC wiki // williamhill : 2014-03-13 William Hill Organization Limited williamhill // win : 2014-11-20 First Registry Limited win // windows : 2014-12-18 Microsoft Corporation windows // wine : 2015-06-18 Binky Moon, LLC wine // winners : 2015-07-16 The TJX Companies, Inc. winners // wme : 2014-02-13 William Morris Endeavor Entertainment, LLC wme // wolterskluwer : 2015-08-06 Wolters Kluwer N.V. wolterskluwer // woodside : 2015-07-09 Woodside Petroleum Limited woodside // work : 2013-12-19 Registry Services, LLC work // works : 2013-11-14 Binky Moon, LLC works // world : 2014-06-12 Binky Moon, LLC world // wow : 2015-10-08 Amazon Registry Services, Inc. wow // wtc : 2013-12-19 World Trade Centers Association, Inc. wtc // wtf : 2014-03-06 Binky Moon, LLC wtf // xbox : 2014-12-18 Microsoft Corporation xbox // xerox : 2014-10-24 Xerox DNHC LLC xerox // xfinity : 2015-07-09 Comcast IP Holdings I, LLC xfinity // xihuan : 2015-01-08 Beijing Qihu Keji Co., Ltd. xihuan // xin : 2014-12-11 Elegant Leader Limited xin // xn--11b4c3d : 2015-01-15 VeriSign Sarl कॉम // xn--1ck2e1b : 2015-02-26 Amazon Registry Services, Inc. セール // xn--1qqw23a : 2014-01-09 Guangzhou YU Wei Information Technology Co., Ltd. 佛山 // xn--30rr7y : 2014-06-12 Excellent First Limited 慈善 // xn--3bst00m : 2013-09-13 Eagle Horizon Limited 集团 // xn--3ds443g : 2013-09-08 TLD REGISTRY LIMITED OY 在线 // xn--3pxu8k : 2015-01-15 VeriSign Sarl 点看 // xn--42c2d9a : 2015-01-15 VeriSign Sarl คอม // xn--45q11c : 2013-11-21 Zodiac Gemini Ltd 八卦 // xn--4gbrim : 2013-10-04 Helium TLDs Ltd موقع // xn--55qw42g : 2013-11-08 China Organizational Name Administration Center 公益 // xn--55qx5d : 2013-11-14 China Internet Network Information Center (CNNIC) 公司 // xn--5su34j936bgsg : 2015-09-03 Shangri‐La International Hotel Management Limited 香格里拉 // xn--5tzm5g : 2014-12-22 Global Website TLD Asia Limited 网站 // xn--6frz82g : 2013-09-23 Identity Digital Limited 移动 // xn--6qq986b3xl : 2013-09-13 Tycoon Treasure Limited 我爱你 // xn--80adxhks : 2013-12-19 Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) москва // xn--80aqecdr1a : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) католик // xn--80asehdb : 2013-07-14 CORE Association онлайн // xn--80aswg : 2013-07-14 CORE Association сайт // xn--8y0a063a : 2015-03-26 China United Network Communications Corporation Limited 联通 // xn--9dbq2a : 2015-01-15 VeriSign Sarl קום // xn--9et52u : 2014-06-12 RISE VICTORY LIMITED 时尚 // xn--9krt00a : 2015-03-12 Sina Corporation 微博 // xn--b4w605ferd : 2014-08-07 Temasek Holdings (Private) Limited 淡马锡 // xn--bck1b9a5dre4c : 2015-02-26 Amazon Registry Services, Inc. ファッション // xn--c1avg : 2013-11-14 Public Interest Registry орг // xn--c2br7g : 2015-01-15 VeriSign Sarl नेट // xn--cck2b3b : 2015-02-26 Amazon Registry Services, Inc. ストア // xn--cckwcxetd : 2019-12-19 Amazon Registry Services, Inc. アマゾン // xn--cg4bki : 2013-09-27 SAMSUNG SDS CO., LTD 삼성 // xn--czr694b : 2014-01-16 Internet DotTrademark Organisation Limited 商标 // xn--czrs0t : 2013-12-19 Binky Moon, LLC 商店 // xn--czru2d : 2013-11-21 Zodiac Aquarius Limited 商城 // xn--d1acj3b : 2013-11-20 The Foundation for Network Initiatives “The Smart Internet” дети // xn--eckvdtc9d : 2014-12-18 Amazon Registry Services, Inc. ポイント // xn--efvy88h : 2014-08-22 Guangzhou YU Wei Information Technology Co., Ltd. 新闻 // xn--fct429k : 2015-04-09 Amazon Registry Services, Inc. 家電 // xn--fhbei : 2015-01-15 VeriSign Sarl كوم // xn--fiq228c5hs : 2013-09-08 TLD REGISTRY LIMITED OY 中文网 // xn--fiq64b : 2013-10-14 CITIC Group Corporation 中信 // xn--fjq720a : 2014-05-22 Binky Moon, LLC 娱乐 // xn--flw351e : 2014-07-31 Charleston Road Registry Inc. 谷歌 // xn--fzys8d69uvgm : 2015-05-14 PCCW Enterprises Limited 電訊盈科 // xn--g2xx48c : 2015-01-30 Nawang Heli(Xiamen) Network Service Co., LTD. 购物 // xn--gckr3f0f : 2015-02-26 Amazon Registry Services, Inc. クラウド // xn--gk3at1e : 2015-10-08 Amazon Registry Services, Inc. 通販 // xn--hxt814e : 2014-05-15 Zodiac Taurus Limited 网店 // xn--i1b6b1a6a2e : 2013-11-14 Public Interest Registry संगठन // xn--imr513n : 2014-12-11 Internet DotTrademark Organisation Limited 餐厅 // xn--io0a7i : 2013-11-14 China Internet Network Information Center (CNNIC) 网络 // xn--j1aef : 2015-01-15 VeriSign Sarl ком // xn--jlq480n2rg : 2019-12-19 Amazon Registry Services, Inc. 亚马逊 // xn--jvr189m : 2015-02-26 Amazon Registry Services, Inc. 食品 // xn--kcrx77d1x4a : 2014-11-07 Koninklijke Philips N.V. 飞利浦 // xn--kput3i : 2014-02-13 Beijing RITT-Net Technology Development Co., Ltd 手机 // xn--mgba3a3ejt : 2014-11-20 Aramco Services Company ارامكو // xn--mgba7c0bbn0a : 2015-05-14 Competrol (Luxembourg) Sarl العليان // xn--mgbaakc7dvf : 2015-09-03 Emirates Telecommunications Corporation (trading as Etisalat) اتصالات // xn--mgbab2bd : 2013-10-31 CORE Association بازار // xn--mgbca7dzdo : 2015-07-30 Abu Dhabi Systems and Information Centre ابوظبي // xn--mgbi4ecexp : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) كاثوليك // xn--mgbt3dhd : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. همراه // xn--mk1bu44c : 2015-01-15 VeriSign Sarl 닷컴 // xn--mxtq1m : 2014-03-06 Net-Chinese Co., Ltd. 政府 // xn--ngbc5azd : 2013-07-13 International Domain Registry Pty. Ltd. شبكة // xn--ngbe9e0a : 2014-12-04 Kuwait Finance House بيتك // xn--ngbrx : 2015-11-12 League of Arab States عرب // xn--nqv7f : 2013-11-14 Public Interest Registry 机构 // xn--nqv7fs00ema : 2013-11-14 Public Interest Registry 组织机构 // xn--nyqy26a : 2014-11-07 Stable Tone Limited 健康 // xn--otu796d : 2017-08-06 Jiang Yu Liang Cai Technology Company Limited 招聘 // xn--p1acf : 2013-12-12 Rusnames Limited рус // xn--pssy2u : 2015-01-15 VeriSign Sarl 大拿 // xn--q9jyb4c : 2013-09-17 Charleston Road Registry Inc. みんな // xn--qcka1pmc : 2014-07-31 Charleston Road Registry Inc. グーグル // xn--rhqv96g : 2013-09-11 Stable Tone Limited 世界 // xn--rovu88b : 2015-02-26 Amazon Registry Services, Inc. 書籍 // xn--ses554g : 2014-01-16 KNET Co., Ltd. 网址 // xn--t60b56a : 2015-01-15 VeriSign Sarl 닷넷 // xn--tckwe : 2015-01-15 VeriSign Sarl コム // xn--tiq49xqyj : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) 天主教 // xn--unup4y : 2013-07-14 Binky Moon, LLC 游戏 // xn--vermgensberater-ctb : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG vermögensberater // xn--vermgensberatung-pwb : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG vermögensberatung // xn--vhquv : 2013-08-27 Binky Moon, LLC 企业 // xn--vuq861b : 2014-10-16 Beijing Tele-info Technology Co., Ltd. 信息 // xn--w4r85el8fhu5dnra : 2015-04-30 Kerry Trading Co. Limited 嘉里大酒店 // xn--w4rs40l : 2015-07-30 Kerry Trading Co. Limited 嘉里 // xn--xhq521b : 2013-11-14 Guangzhou YU Wei Information Technology Co., Ltd. 广东 // xn--zfr164b : 2013-11-08 China Organizational Name Administration Center 政务 // xyz : 2013-12-05 XYZ.COM LLC xyz // yachts : 2014-01-09 XYZ.COM LLC yachts // yahoo : 2015-04-02 Oath Inc. yahoo // yamaxun : 2014-12-18 Amazon Registry Services, Inc. yamaxun // yandex : 2014-04-10 Yandex Europe B.V. yandex // yodobashi : 2014-11-20 YODOBASHI CAMERA CO.,LTD. yodobashi // yoga : 2014-05-29 Registry Services, LLC yoga // yokohama : 2013-12-12 GMO Registry, Inc. yokohama // you : 2015-04-09 Amazon Registry Services, Inc. you // youtube : 2014-05-01 Charleston Road Registry Inc. youtube // yun : 2015-01-08 Beijing Qihu Keji Co., Ltd. yun // zappos : 2015-06-25 Amazon Registry Services, Inc. zappos // zara : 2014-11-07 Industria de Diseño Textil, S.A. (INDITEX, S.A.) zara // zero : 2014-12-18 Amazon Registry Services, Inc. zero // zip : 2014-05-08 Charleston Road Registry Inc. zip // zone : 2013-11-14 Binky Moon, LLC zone // zuerich : 2014-11-07 Kanton Zürich (Canton of Zurich) zuerich // ===END ICANN DOMAINS=== // ===BEGIN PRIVATE DOMAINS=== // (Note: these are in alphabetical order by company name) // 1GB LLC : https://www.1gb.ua/ // Submitted by 1GB LLC cc.ua inf.ua ltd.ua // 611coin : https://611project.org/ 611.to // Aaron Marais' Gitlab pages: https://lab.aaronleem.co.za // Submitted by Aaron Marais graphox.us // accesso Technology Group, plc. : https://accesso.com/ // Submitted by accesso Team *.devcdnaccesso.com // Acorn Labs : https://acorn.io // Submitted by Craig Jellick *.on-acorn.io // ActiveTrail: https://www.activetrail.biz/ // Submitted by Ofer Kalaora activetrail.biz // Adobe : https://www.adobe.com/ // Submitted by Ian Boston and Lars Trieloff adobeaemcloud.com *.dev.adobeaemcloud.com hlx.live adobeaemcloud.net hlx.page hlx3.page // Adobe Developer Platform : https://developer.adobe.com // Submitted by Jesse MacFadyen adobeio-static.net adobeioruntime.net // Agnat sp. z o.o. : https://domena.pl // Submitted by Przemyslaw Plewa beep.pl // Airkit : https://www.airkit.com/ // Submitted by Grant Cooksey airkitapps.com airkitapps-au.com airkitapps.eu // Aiven: https://aiven.io/ // Submitted by Etienne Stalmans aivencloud.com // Akamai : https://www.akamai.com/ // Submitted by Akamai Team akadns.net akamai.net akamai-staging.net akamaiedge.net akamaiedge-staging.net akamaihd.net akamaihd-staging.net akamaiorigin.net akamaiorigin-staging.net akamaized.net akamaized-staging.net edgekey.net edgekey-staging.net edgesuite.net edgesuite-staging.net // alboto.ca : http://alboto.ca // Submitted by Anton Avramov barsy.ca // Alces Software Ltd : http://alces-software.com // Submitted by Mark J. Titorenko *.compute.estate *.alces.network // all-inkl.com : https://all-inkl.com // Submitted by Werner Kaltofen kasserver.com // Altervista: https://www.altervista.org // Submitted by Carlo Cannas altervista.org // alwaysdata : https://www.alwaysdata.com // Submitted by Cyril alwaysdata.net // Amaze Software : https://amaze.co // Submitted by Domain Admin myamaze.net // Amazon : https://www.amazon.com/ // Submitted by AWS Security // Subsections of Amazon/subsidiaries will appear until "concludes" tag // Amazon CloudFront // Submitted by Donavan Miller // Reference: 54144616-fd49-4435-8535-19c6a601bdb3 cloudfront.net // Amazon EC2 // Submitted by Luke Wells // Reference: 4c38fa71-58ac-4768-99e5-689c1767e537 *.compute.amazonaws.com *.compute-1.amazonaws.com *.compute.amazonaws.com.cn us-east-1.amazonaws.com // Amazon S3 // Submitted by Luke Wells // Reference: d068bd97-f0a9-4838-a6d8-954b622ef4ae s3.cn-north-1.amazonaws.com.cn s3.dualstack.ap-northeast-1.amazonaws.com s3.dualstack.ap-northeast-2.amazonaws.com s3.ap-northeast-2.amazonaws.com s3-website.ap-northeast-2.amazonaws.com s3.dualstack.ap-south-1.amazonaws.com s3.ap-south-1.amazonaws.com s3-website.ap-south-1.amazonaws.com s3.dualstack.ap-southeast-1.amazonaws.com s3.dualstack.ap-southeast-2.amazonaws.com s3.dualstack.ca-central-1.amazonaws.com s3.ca-central-1.amazonaws.com s3-website.ca-central-1.amazonaws.com s3.dualstack.eu-central-1.amazonaws.com s3.eu-central-1.amazonaws.com s3-website.eu-central-1.amazonaws.com s3.dualstack.eu-west-1.amazonaws.com s3.dualstack.eu-west-2.amazonaws.com s3.eu-west-2.amazonaws.com s3-website.eu-west-2.amazonaws.com s3.dualstack.eu-west-3.amazonaws.com s3.eu-west-3.amazonaws.com s3-website.eu-west-3.amazonaws.com s3.amazonaws.com s3-ap-northeast-1.amazonaws.com s3-ap-northeast-2.amazonaws.com s3-ap-south-1.amazonaws.com s3-ap-southeast-1.amazonaws.com s3-ap-southeast-2.amazonaws.com s3-ca-central-1.amazonaws.com s3-eu-central-1.amazonaws.com s3-eu-west-1.amazonaws.com s3-eu-west-2.amazonaws.com s3-eu-west-3.amazonaws.com s3-external-1.amazonaws.com s3-fips-us-gov-west-1.amazonaws.com s3-sa-east-1.amazonaws.com s3-us-east-2.amazonaws.com s3-us-gov-west-1.amazonaws.com s3-us-west-1.amazonaws.com s3-us-west-2.amazonaws.com s3-website-ap-northeast-1.amazonaws.com s3-website-ap-southeast-1.amazonaws.com s3-website-ap-southeast-2.amazonaws.com s3-website-eu-west-1.amazonaws.com s3-website-sa-east-1.amazonaws.com s3-website-us-east-1.amazonaws.com s3-website-us-west-1.amazonaws.com s3-website-us-west-2.amazonaws.com s3.dualstack.sa-east-1.amazonaws.com s3.dualstack.us-east-1.amazonaws.com s3.dualstack.us-east-2.amazonaws.com s3.us-east-2.amazonaws.com s3-website.us-east-2.amazonaws.com // Analytics on AWS // Submitted by AWS Security // Reference: c02c3a80-f8a0-4fd2-b719-48ea8b7c28de analytics-gateway.ap-northeast-1.amazonaws.com analytics-gateway.eu-west-1.amazonaws.com analytics-gateway.us-east-1.amazonaws.com analytics-gateway.us-east-2.amazonaws.com analytics-gateway.us-west-2.amazonaws.com // AWS Cloud9 // Submitted by: AWS Security // Reference: 05c44955-977c-4b57-938a-f2af92733f9f webview-assets.aws-cloud9.af-south-1.amazonaws.com vfs.cloud9.af-south-1.amazonaws.com webview-assets.cloud9.af-south-1.amazonaws.com webview-assets.aws-cloud9.ap-east-1.amazonaws.com vfs.cloud9.ap-east-1.amazonaws.com webview-assets.cloud9.ap-east-1.amazonaws.com webview-assets.aws-cloud9.ap-northeast-1.amazonaws.com vfs.cloud9.ap-northeast-1.amazonaws.com webview-assets.cloud9.ap-northeast-1.amazonaws.com webview-assets.aws-cloud9.ap-northeast-2.amazonaws.com vfs.cloud9.ap-northeast-2.amazonaws.com webview-assets.cloud9.ap-northeast-2.amazonaws.com webview-assets.aws-cloud9.ap-northeast-3.amazonaws.com vfs.cloud9.ap-northeast-3.amazonaws.com webview-assets.cloud9.ap-northeast-3.amazonaws.com webview-assets.aws-cloud9.ap-south-1.amazonaws.com vfs.cloud9.ap-south-1.amazonaws.com webview-assets.cloud9.ap-south-1.amazonaws.com webview-assets.aws-cloud9.ap-southeast-1.amazonaws.com vfs.cloud9.ap-southeast-1.amazonaws.com webview-assets.cloud9.ap-southeast-1.amazonaws.com webview-assets.aws-cloud9.ap-southeast-2.amazonaws.com vfs.cloud9.ap-southeast-2.amazonaws.com webview-assets.cloud9.ap-southeast-2.amazonaws.com webview-assets.aws-cloud9.ca-central-1.amazonaws.com vfs.cloud9.ca-central-1.amazonaws.com webview-assets.cloud9.ca-central-1.amazonaws.com webview-assets.aws-cloud9.eu-central-1.amazonaws.com vfs.cloud9.eu-central-1.amazonaws.com webview-assets.cloud9.eu-central-1.amazonaws.com webview-assets.aws-cloud9.eu-north-1.amazonaws.com vfs.cloud9.eu-north-1.amazonaws.com webview-assets.cloud9.eu-north-1.amazonaws.com webview-assets.aws-cloud9.eu-south-1.amazonaws.com vfs.cloud9.eu-south-1.amazonaws.com webview-assets.cloud9.eu-south-1.amazonaws.com webview-assets.aws-cloud9.eu-west-1.amazonaws.com vfs.cloud9.eu-west-1.amazonaws.com webview-assets.cloud9.eu-west-1.amazonaws.com webview-assets.aws-cloud9.eu-west-2.amazonaws.com vfs.cloud9.eu-west-2.amazonaws.com webview-assets.cloud9.eu-west-2.amazonaws.com webview-assets.aws-cloud9.eu-west-3.amazonaws.com vfs.cloud9.eu-west-3.amazonaws.com webview-assets.cloud9.eu-west-3.amazonaws.com webview-assets.aws-cloud9.me-south-1.amazonaws.com vfs.cloud9.me-south-1.amazonaws.com webview-assets.cloud9.me-south-1.amazonaws.com webview-assets.aws-cloud9.sa-east-1.amazonaws.com vfs.cloud9.sa-east-1.amazonaws.com webview-assets.cloud9.sa-east-1.amazonaws.com webview-assets.aws-cloud9.us-east-1.amazonaws.com vfs.cloud9.us-east-1.amazonaws.com webview-assets.cloud9.us-east-1.amazonaws.com webview-assets.aws-cloud9.us-east-2.amazonaws.com vfs.cloud9.us-east-2.amazonaws.com webview-assets.cloud9.us-east-2.amazonaws.com webview-assets.aws-cloud9.us-west-1.amazonaws.com vfs.cloud9.us-west-1.amazonaws.com webview-assets.cloud9.us-west-1.amazonaws.com webview-assets.aws-cloud9.us-west-2.amazonaws.com vfs.cloud9.us-west-2.amazonaws.com webview-assets.cloud9.us-west-2.amazonaws.com // AWS Elastic Beanstalk // Submitted by Luke Wells // Reference: aa202394-43a0-4857-b245-8db04549137e cn-north-1.eb.amazonaws.com.cn cn-northwest-1.eb.amazonaws.com.cn elasticbeanstalk.com ap-northeast-1.elasticbeanstalk.com ap-northeast-2.elasticbeanstalk.com ap-northeast-3.elasticbeanstalk.com ap-south-1.elasticbeanstalk.com ap-southeast-1.elasticbeanstalk.com ap-southeast-2.elasticbeanstalk.com ca-central-1.elasticbeanstalk.com eu-central-1.elasticbeanstalk.com eu-west-1.elasticbeanstalk.com eu-west-2.elasticbeanstalk.com eu-west-3.elasticbeanstalk.com sa-east-1.elasticbeanstalk.com us-east-1.elasticbeanstalk.com us-east-2.elasticbeanstalk.com us-gov-west-1.elasticbeanstalk.com us-west-1.elasticbeanstalk.com us-west-2.elasticbeanstalk.com // (AWS) Elastic Load Balancing // Submitted by Luke Wells // Reference: 12a3d528-1bac-4433-a359-a395867ffed2 *.elb.amazonaws.com.cn *.elb.amazonaws.com // AWS Global Accelerator // Submitted by Daniel Massaguer // Reference: d916759d-a08b-4241-b536-4db887383a6a awsglobalaccelerator.com // eero // Submitted by Yue Kang // Reference: 264afe70-f62c-4c02-8ab9-b5281ed24461 eero.online eero-stage.online // concludes Amazon // Amune : https://amune.org/ // Submitted by Team Amune t3l3p0rt.net tele.amune.org // Apigee : https://apigee.com/ // Submitted by Apigee Security Team apigee.io // Apphud : https://apphud.com // Submitted by Alexander Selivanov siiites.com // Appspace : https://www.appspace.com // Submitted by Appspace Security Team appspacehosted.com appspaceusercontent.com // Appudo UG (haftungsbeschränkt) : https://www.appudo.com // Submitted by Alexander Hochbaum appudo.net // Aptible : https://www.aptible.com/ // Submitted by Thomas Orozco on-aptible.com // ASEINet : https://www.aseinet.com/ // Submitted by Asei SEKIGUCHI user.aseinet.ne.jp gv.vc d.gv.vc // Asociación Amigos de la Informática "Euskalamiga" : http://encounter.eus/ // Submitted by Hector Martin user.party.eus // Association potager.org : https://potager.org/ // Submitted by Lunar pimienta.org poivron.org potager.org sweetpepper.org // ASUSTOR Inc. : http://www.asustor.com // Submitted by Vincent Tseng myasustor.com // Atlassian : https://atlassian.com // Submitted by Sam Smyth cdn.prod.atlassian-dev.net // Authentick UG (haftungsbeschränkt) : https://authentick.net // Submitted by Lukas Reschke translated.page // Autocode : https://autocode.com // Submitted by Jacob Lee autocode.dev // AVM : https://avm.de // Submitted by Andreas Weise myfritz.net // AVStack Pte. Ltd. : https://avstack.io // Submitted by Jasper Hugo onavstack.net // AW AdvisorWebsites.com Software Inc : https://advisorwebsites.com // Submitted by James Kennedy *.awdev.ca *.advisor.ws // AZ.pl sp. z.o.o: https://az.pl // Submitted by Krzysztof Wolski ecommerce-shop.pl // b-data GmbH : https://www.b-data.io // Submitted by Olivier Benz b-data.io // backplane : https://www.backplane.io // Submitted by Anthony Voutas backplaneapp.io // Balena : https://www.balena.io // Submitted by Petros Angelatos balena-devices.com // University of Banja Luka : https://unibl.org // Domains for Republic of Srpska administrative entity. // Submitted by Marko Ivanovic rs.ba // Banzai Cloud // Submitted by Janos Matyas *.banzai.cloud app.banzaicloud.io *.backyards.banzaicloud.io // BASE, Inc. : https://binc.jp // Submitted by Yuya NAGASAWA base.ec official.ec buyshop.jp fashionstore.jp handcrafted.jp kawaiishop.jp supersale.jp theshop.jp shopselect.net base.shop // BeagleBoard.org Foundation : https://beagleboard.org // Submitted by Jason Kridner beagleboard.io // Beget Ltd // Submitted by Lev Nekrasov *.beget.app // BetaInABox // Submitted by Adrian betainabox.com // BinaryLane : http://www.binarylane.com // Submitted by Nathan O'Sullivan bnr.la // Bitbucket : http://bitbucket.org // Submitted by Andy Ortlieb bitbucket.io // Blackbaud, Inc. : https://www.blackbaud.com // Submitted by Paul Crowder blackbaudcdn.net // Blatech : http://www.blatech.net // Submitted by Luke Bratch of.je // Blue Bite, LLC : https://bluebite.com // Submitted by Joshua Weiss bluebite.io // Boomla : https://boomla.com // Submitted by Tibor Halter boomla.net // Boutir : https://www.boutir.com // Submitted by Eric Ng Ka Ka boutir.com // Boxfuse : https://boxfuse.com // Submitted by Axel Fontaine boxfuse.io // bplaced : https://www.bplaced.net/ // Submitted by Miroslav Bozic square7.ch bplaced.com bplaced.de square7.de bplaced.net square7.net // Brendly : https://brendly.rs // Submitted by Dusan Radovanovic shop.brendly.rs // BrowserSafetyMark // Submitted by Dave Tharp browsersafetymark.io // Bytemark Hosting : https://www.bytemark.co.uk // Submitted by Paul Cammish uk0.bigv.io dh.bytemark.co.uk vm.bytemark.co.uk // Caf.js Labs LLC : https://www.cafjs.com // Submitted by Antonio Lain cafjs.com // callidomus : https://www.callidomus.com/ // Submitted by Marcus Popp mycd.eu // Canva Pty Ltd : https://canva.com/ // Submitted by Joel Aquilina canva-apps.cn canva-apps.com // Carrd : https://carrd.co // Submitted by AJ drr.ac uwu.ai carrd.co crd.co ju.mp // CentralNic : http://www.centralnic.com/names/domains // Submitted by registry ae.org br.com cn.com com.de com.se de.com eu.com gb.net hu.net jp.net jpn.com mex.com ru.com sa.com se.net uk.com uk.net us.com za.bz za.com // No longer operated by CentralNic, these entries should be adopted and/or removed by current operators // Submitted by Gavin Brown ar.com hu.com kr.com no.com qc.com uy.com // Africa.com Web Solutions Ltd : https://registry.africa.com // Submitted by Gavin Brown africa.com // iDOT Services Limited : http://www.domain.gr.com // Submitted by Gavin Brown gr.com // Radix FZC : http://domains.in.net // Submitted by Gavin Brown in.net web.in // US REGISTRY LLC : http://us.org // Submitted by Gavin Brown us.org // co.com Registry, LLC : https://registry.co.com // Submitted by Gavin Brown co.com // Roar Domains LLC : https://roar.basketball/ // Submitted by Gavin Brown aus.basketball nz.basketball // BRS Media : https://brsmedia.com/ // Submitted by Gavin Brown radio.am radio.fm // c.la : http://www.c.la/ c.la // certmgr.org : https://certmgr.org // Submitted by B. Blechschmidt certmgr.org // Cityhost LLC : https://cityhost.ua // Submitted by Maksym Rivtin cx.ua // Civilized Discourse Construction Kit, Inc. : https://www.discourse.org/ // Submitted by Rishabh Nambiar & Michael Brown discourse.group discourse.team // Clever Cloud : https://www.clever-cloud.com/ // Submitted by Quentin Adam cleverapps.io // Clerk : https://www.clerk.dev // Submitted by Colin Sidoti clerk.app clerkstage.app *.lcl.dev *.lclstage.dev *.stg.dev *.stgstage.dev // ClickRising : https://clickrising.com/ // Submitted by Umut Gumeli clickrising.net // Cloud66 : https://www.cloud66.com/ // Submitted by Khash Sajadi c66.me cloud66.ws cloud66.zone // CloudAccess.net : https://www.cloudaccess.net/ // Submitted by Pawel Panek jdevcloud.com wpdevcloud.com cloudaccess.host freesite.host cloudaccess.net // cloudControl : https://www.cloudcontrol.com/ // Submitted by Tobias Wilken cloudcontrolled.com cloudcontrolapp.com // Cloudera, Inc. : https://www.cloudera.com/ // Submitted by Kedarnath Waikar *.cloudera.site // Cloudflare, Inc. : https://www.cloudflare.com/ // Submitted by Cloudflare Team cf-ipfs.com cloudflare-ipfs.com trycloudflare.com pages.dev r2.dev workers.dev // Clovyr : https://clovyr.io // Submitted by Patrick Nielsen wnext.app // co.ca : http://registry.co.ca/ co.ca // Co & Co : https://co-co.nl/ // Submitted by Govert Versluis *.otap.co // i-registry s.r.o. : http://www.i-registry.cz/ // Submitted by Martin Semrad co.cz // CDN77.com : http://www.cdn77.com // Submitted by Jan Krpes c.cdn77.org cdn77-ssl.net r.cdn77.net rsc.cdn77.org ssl.origin.cdn77-secure.org // Cloud DNS Ltd : http://www.cloudns.net // Submitted by Aleksander Hristov cloudns.asia cloudns.biz cloudns.club cloudns.cc cloudns.eu cloudns.in cloudns.info cloudns.org cloudns.pro cloudns.pw cloudns.us // CNPY : https://cnpy.gdn // Submitted by Angelo Gladding cnpy.gdn // Codeberg e. V. : https://codeberg.org // Submitted by Moritz Marquardt codeberg.page // CoDNS B.V. co.nl co.no // Combell.com : https://www.combell.com // Submitted by Thomas Wouters webhosting.be hosting-cluster.nl // Coordination Center for TLD RU and XN--P1AI : https://cctld.ru/en/domains/domens_ru/reserved/ // Submitted by George Georgievsky ac.ru edu.ru gov.ru int.ru mil.ru test.ru // COSIMO GmbH : http://www.cosimo.de // Submitted by Rene Marticke dyn.cosidns.de dynamisches-dns.de dnsupdater.de internet-dns.de l-o-g-i-n.de dynamic-dns.info feste-ip.net knx-server.net static-access.net // Craynic, s.r.o. : http://www.craynic.com/ // Submitted by Ales Krajnik realm.cz // Cryptonomic : https://cryptonomic.net/ // Submitted by Andrew Cady *.cryptonomic.net // Cupcake : https://cupcake.io/ // Submitted by Jonathan Rudenberg cupcake.is // Curv UG : https://curv-labs.de/ // Submitted by Marvin Wiesner curv.dev // Customer OCI - Oracle Dyn https://cloud.oracle.com/home https://dyn.com/dns/ // Submitted by Gregory Drake // Note: This is intended to also include customer-oci.com due to wildcards implicitly including the current label *.customer-oci.com *.oci.customer-oci.com *.ocp.customer-oci.com *.ocs.customer-oci.com // cyon GmbH : https://www.cyon.ch/ // Submitted by Dominic Luechinger cyon.link cyon.site // Danger Science Group: https://dangerscience.com/ // Submitted by Skylar MacDonald fnwk.site folionetwork.site platform0.app // Daplie, Inc : https://daplie.com // Submitted by AJ ONeal daplie.me localhost.daplie.me // Datto, Inc. : https://www.datto.com/ // Submitted by Philipp Heckel dattolocal.com dattorelay.com dattoweb.com mydatto.com dattolocal.net mydatto.net // Dansk.net : http://www.dansk.net/ // Submitted by Anani Voule biz.dk co.dk firm.dk reg.dk store.dk // dappnode.io : https://dappnode.io/ // Submitted by Abel Boldu / DAppNode Team dyndns.dappnode.io // dapps.earth : https://dapps.earth/ // Submitted by Daniil Burdakov *.dapps.earth *.bzz.dapps.earth // Dark, Inc. : https://darklang.com // Submitted by Paul Biggar builtwithdark.com // DataDetect, LLC. : https://datadetect.com // Submitted by Andrew Banchich demo.datadetect.com instance.datadetect.com // Datawire, Inc : https://www.datawire.io // Submitted by Richard Li edgestack.me // DDNS5 : https://ddns5.com // Submitted by Cameron Elliott ddns5.com // Debian : https://www.debian.org/ // Submitted by Peter Palfrader / Debian Sysadmin Team debian.net // Deno Land Inc : https://deno.com/ // Submitted by Luca Casonato deno.dev deno-staging.dev // deSEC : https://desec.io/ // Submitted by Peter Thomassen dedyn.io // Deta: https://www.deta.sh/ // Submitted by Aavash Shrestha deta.app deta.dev // Diher Solutions : https://diher.solutions // Submitted by Didi Hermawan *.rss.my.id *.diher.solutions // Discord Inc : https://discord.com // Submitted by Sahn Lam discordsays.com discordsez.com // DNS Africa Ltd https://dns.business // Submitted by Calvin Browne jozi.biz // DNShome : https://www.dnshome.de/ // Submitted by Norbert Auler dnshome.de // DotArai : https://www.dotarai.com/ // Submitted by Atsadawat Netcharadsang online.th shop.th // DrayTek Corp. : https://www.draytek.com/ // Submitted by Paul Fang drayddns.com // DreamCommerce : https://shoper.pl/ // Submitted by Konrad Kotarba shoparena.pl // DreamHost : http://www.dreamhost.com/ // Submitted by Andrew Farmer dreamhosters.com // Drobo : http://www.drobo.com/ // Submitted by Ricardo Padilha mydrobo.com // Drud Holdings, LLC. : https://www.drud.com/ // Submitted by Kevin Bridges drud.io drud.us // DuckDNS : http://www.duckdns.org/ // Submitted by Richard Harper duckdns.org // Bip : https://bip.sh // Submitted by Joel Kennedy bip.sh // bitbridge.net : Submitted by Craig Welch, abeliidev@gmail.com bitbridge.net // dy.fi : http://dy.fi/ // Submitted by Heikki Hannikainen dy.fi tunk.org // DynDNS.com : http://www.dyndns.com/services/dns/dyndns/ dyndns-at-home.com dyndns-at-work.com dyndns-blog.com dyndns-free.com dyndns-home.com dyndns-ip.com dyndns-mail.com dyndns-office.com dyndns-pics.com dyndns-remote.com dyndns-server.com dyndns-web.com dyndns-wiki.com dyndns-work.com dyndns.biz dyndns.info dyndns.org dyndns.tv at-band-camp.net ath.cx barrel-of-knowledge.info barrell-of-knowledge.info better-than.tv blogdns.com blogdns.net blogdns.org blogsite.org boldlygoingnowhere.org broke-it.net buyshouses.net cechire.com dnsalias.com dnsalias.net dnsalias.org dnsdojo.com dnsdojo.net dnsdojo.org does-it.net doesntexist.com doesntexist.org dontexist.com dontexist.net dontexist.org doomdns.com doomdns.org dvrdns.org dyn-o-saur.com dynalias.com dynalias.net dynalias.org dynathome.net dyndns.ws endofinternet.net endofinternet.org endoftheinternet.org est-a-la-maison.com est-a-la-masion.com est-le-patron.com est-mon-blogueur.com for-better.biz for-more.biz for-our.info for-some.biz for-the.biz forgot.her.name forgot.his.name from-ak.com from-al.com from-ar.com from-az.net from-ca.com from-co.net from-ct.com from-dc.com from-de.com from-fl.com from-ga.com from-hi.com from-ia.com from-id.com from-il.com from-in.com from-ks.com from-ky.com from-la.net from-ma.com from-md.com from-me.org from-mi.com from-mn.com from-mo.com from-ms.com from-mt.com from-nc.com from-nd.com from-ne.com from-nh.com from-nj.com from-nm.com from-nv.com from-ny.net from-oh.com from-ok.com from-or.com from-pa.com from-pr.com from-ri.com from-sc.com from-sd.com from-tn.com from-tx.com from-ut.com from-va.com from-vt.com from-wa.com from-wi.com from-wv.com from-wy.com ftpaccess.cc fuettertdasnetz.de game-host.org game-server.cc getmyip.com gets-it.net go.dyndns.org gotdns.com gotdns.org groks-the.info groks-this.info ham-radio-op.net here-for-more.info hobby-site.com hobby-site.org home.dyndns.org homedns.org homeftp.net homeftp.org homeip.net homelinux.com homelinux.net homelinux.org homeunix.com homeunix.net homeunix.org iamallama.com in-the-band.net is-a-anarchist.com is-a-blogger.com is-a-bookkeeper.com is-a-bruinsfan.org is-a-bulls-fan.com is-a-candidate.org is-a-caterer.com is-a-celticsfan.org is-a-chef.com is-a-chef.net is-a-chef.org is-a-conservative.com is-a-cpa.com is-a-cubicle-slave.com is-a-democrat.com is-a-designer.com is-a-doctor.com is-a-financialadvisor.com is-a-geek.com is-a-geek.net is-a-geek.org is-a-green.com is-a-guru.com is-a-hard-worker.com is-a-hunter.com is-a-knight.org is-a-landscaper.com is-a-lawyer.com is-a-liberal.com is-a-libertarian.com is-a-linux-user.org is-a-llama.com is-a-musician.com is-a-nascarfan.com is-a-nurse.com is-a-painter.com is-a-patsfan.org is-a-personaltrainer.com is-a-photographer.com is-a-player.com is-a-republican.com is-a-rockstar.com is-a-socialist.com is-a-soxfan.org is-a-student.com is-a-teacher.com is-a-techie.com is-a-therapist.com is-an-accountant.com is-an-actor.com is-an-actress.com is-an-anarchist.com is-an-artist.com is-an-engineer.com is-an-entertainer.com is-by.us is-certified.com is-found.org is-gone.com is-into-anime.com is-into-cars.com is-into-cartoons.com is-into-games.com is-leet.com is-lost.org is-not-certified.com is-saved.org is-slick.com is-uberleet.com is-very-bad.org is-very-evil.org is-very-good.org is-very-nice.org is-very-sweet.org is-with-theband.com isa-geek.com isa-geek.net isa-geek.org isa-hockeynut.com issmarterthanyou.com isteingeek.de istmein.de kicks-ass.net kicks-ass.org knowsitall.info land-4-sale.us lebtimnetz.de leitungsen.de likes-pie.com likescandy.com merseine.nu mine.nu misconfused.org mypets.ws myphotos.cc neat-url.com office-on-the.net on-the-web.tv podzone.net podzone.org readmyblog.org saves-the-whales.com scrapper-site.net scrapping.cc selfip.biz selfip.com selfip.info selfip.net selfip.org sells-for-less.com sells-for-u.com sells-it.net sellsyourhome.org servebbs.com servebbs.net servebbs.org serveftp.net serveftp.org servegame.org shacknet.nu simple-url.com space-to-rent.com stuff-4-sale.org stuff-4-sale.us teaches-yoga.com thruhere.net traeumtgerade.de webhop.biz webhop.info webhop.net webhop.org worse-than.tv writesthisblog.com // ddnss.de : https://www.ddnss.de/ // Submitted by Robert Niedziela ddnss.de dyn.ddnss.de dyndns.ddnss.de dyndns1.de dyn-ip24.de home-webserver.de dyn.home-webserver.de myhome-server.de ddnss.org // Definima : http://www.definima.com/ // Submitted by Maxence Bitterli definima.net definima.io // DigitalOcean App Platform : https://www.digitalocean.com/products/app-platform/ // Submitted by Braxton Huggins ondigitalocean.app // DigitalOcean Spaces : https://www.digitalocean.com/products/spaces/ // Submitted by Robin H. Johnson *.digitaloceanspaces.com // dnstrace.pro : https://dnstrace.pro/ // Submitted by Chris Partridge bci.dnstrace.pro // Dynu.com : https://www.dynu.com/ // Submitted by Sue Ye ddnsfree.com ddnsgeek.com giize.com gleeze.com kozow.com loseyourip.com ooguy.com theworkpc.com casacam.net dynu.net accesscam.org camdvr.org freeddns.org mywire.org webredirect.org myddns.rocks blogsite.xyz // dynv6 : https://dynv6.com // Submitted by Dominik Menke dynv6.net // E4YOU spol. s.r.o. : https://e4you.cz/ // Submitted by Vladimir Dudr e4.cz // Easypanel : https://easypanel.io // Submitted by Andrei Canta easypanel.app easypanel.host // Elementor : Elementor Ltd. // Submitted by Anton Barkan elementor.cloud elementor.cool // En root‽ : https://en-root.org // Submitted by Emmanuel Raviart en-root.fr // Enalean SAS: https://www.enalean.com // Submitted by Thomas Cottier mytuleap.com tuleap-partners.com // Encoretivity AB: https://encore.dev // Submitted by André Eriksson encr.app encoreapi.com // ECG Robotics, Inc: https://ecgrobotics.org // Submitted by onred.one staging.onred.one // encoway GmbH : https://www.encoway.de // Submitted by Marcel Daus eu.encoway.cloud // EU.org https://eu.org/ // Submitted by Pierre Beyssac eu.org al.eu.org asso.eu.org at.eu.org au.eu.org be.eu.org bg.eu.org ca.eu.org cd.eu.org ch.eu.org cn.eu.org cy.eu.org cz.eu.org de.eu.org dk.eu.org edu.eu.org ee.eu.org es.eu.org fi.eu.org fr.eu.org gr.eu.org hr.eu.org hu.eu.org ie.eu.org il.eu.org in.eu.org int.eu.org is.eu.org it.eu.org jp.eu.org kr.eu.org lt.eu.org lu.eu.org lv.eu.org mc.eu.org me.eu.org mk.eu.org mt.eu.org my.eu.org net.eu.org ng.eu.org nl.eu.org no.eu.org nz.eu.org paris.eu.org pl.eu.org pt.eu.org q-a.eu.org ro.eu.org ru.eu.org se.eu.org si.eu.org sk.eu.org tr.eu.org uk.eu.org us.eu.org // Eurobyte : https://eurobyte.ru // Submitted by Evgeniy Subbotin eurodir.ru // Evennode : http://www.evennode.com/ // Submitted by Michal Kralik eu-1.evennode.com eu-2.evennode.com eu-3.evennode.com eu-4.evennode.com us-1.evennode.com us-2.evennode.com us-3.evennode.com us-4.evennode.com // eDirect Corp. : https://hosting.url.com.tw/ // Submitted by C.S. chang twmail.cc twmail.net twmail.org mymailer.com.tw url.tw // Fabrica Technologies, Inc. : https://www.fabrica.dev/ // Submitted by Eric Jiang onfabrica.com // Facebook, Inc. // Submitted by Peter Ruibal apps.fbsbx.com // FAITID : https://faitid.org/ // Submitted by Maxim Alzoba // https://www.flexireg.net/stat_info ru.net adygeya.ru bashkiria.ru bir.ru cbg.ru com.ru dagestan.ru grozny.ru kalmykia.ru kustanai.ru marine.ru mordovia.ru msk.ru mytis.ru nalchik.ru nov.ru pyatigorsk.ru spb.ru vladikavkaz.ru vladimir.ru abkhazia.su adygeya.su aktyubinsk.su arkhangelsk.su armenia.su ashgabad.su azerbaijan.su balashov.su bashkiria.su bryansk.su bukhara.su chimkent.su dagestan.su east-kazakhstan.su exnet.su georgia.su grozny.su ivanovo.su jambyl.su kalmykia.su kaluga.su karacol.su karaganda.su karelia.su khakassia.su krasnodar.su kurgan.su kustanai.su lenug.su mangyshlak.su mordovia.su msk.su murmansk.su nalchik.su navoi.su north-kazakhstan.su nov.su obninsk.su penza.su pokrovsk.su sochi.su spb.su tashkent.su termez.su togliatti.su troitsk.su tselinograd.su tula.su tuva.su vladikavkaz.su vladimir.su vologda.su // Fancy Bits, LLC : http://getchannels.com // Submitted by Aman Gupta channelsdvr.net u.channelsdvr.net // Fastly Inc. : http://www.fastly.com/ // Submitted by Fastly Security edgecompute.app fastly-edge.com fastly-terrarium.com fastlylb.net map.fastlylb.net freetls.fastly.net map.fastly.net a.prod.fastly.net global.prod.fastly.net a.ssl.fastly.net b.ssl.fastly.net global.ssl.fastly.net // Fastmail : https://www.fastmail.com/ // Submitted by Marc Bradshaw *.user.fm // FASTVPS EESTI OU : https://fastvps.ru/ // Submitted by Likhachev Vasiliy fastvps-server.com fastvps.host myfast.host fastvps.site myfast.space // Fedora : https://fedoraproject.org/ // submitted by Patrick Uiterwijk fedorainfracloud.org fedorapeople.org cloud.fedoraproject.org app.os.fedoraproject.org app.os.stg.fedoraproject.org // FearWorks Media Ltd. : https://fearworksmedia.co.uk // submitted by Keith Fairley conn.uk copro.uk hosp.uk // Fermax : https://fermax.com/ // submitted by Koen Van Isterdael mydobiss.com // FH Muenster : https://www.fh-muenster.de // Submitted by Robin Naundorf fh-muenster.io // Filegear Inc. : https://www.filegear.com // Submitted by Jason Zhu filegear.me filegear-au.me filegear-de.me filegear-gb.me filegear-ie.me filegear-jp.me filegear-sg.me // Firebase, Inc. // Submitted by Chris Raynor firebaseapp.com // Firewebkit : https://www.firewebkit.com // Submitted by Majid Qureshi fireweb.app // FLAP : https://www.flap.cloud // Submitted by Louis Chemineau flap.id // FlashDrive : https://flashdrive.io // Submitted by Eric Chan onflashdrive.app fldrv.com // fly.io: https://fly.io // Submitted by Kurt Mackey fly.dev edgeapp.net shw.io // Flynn : https://flynn.io // Submitted by Jonathan Rudenberg flynnhosting.net // Forgerock : https://www.forgerock.com // Submitted by Roderick Parr forgeblocks.com id.forgerock.io // Framer : https://www.framer.com // Submitted by Koen Rouwhorst framer.app framercanvas.com framer.media framer.photos framer.website framer.wiki // Frusky MEDIA&PR : https://www.frusky.de // Submitted by Victor Pupynin *.frusky.de // RavPage : https://www.ravpage.co.il // Submitted by Roni Horowitz ravpage.co.il // Frederik Braun https://frederik-braun.com // Submitted by Frederik Braun 0e.vc // Freebox : http://www.freebox.fr // Submitted by Romain Fliedel freebox-os.com freeboxos.com fbx-os.fr fbxos.fr freebox-os.fr freeboxos.fr // freedesktop.org : https://www.freedesktop.org // Submitted by Daniel Stone freedesktop.org // freemyip.com : https://freemyip.com // Submitted by Cadence freemyip.com // FunkFeuer - Verein zur Förderung freier Netze : https://www.funkfeuer.at // Submitted by Daniel A. Maierhofer wien.funkfeuer.at // Futureweb OG : http://www.futureweb.at // Submitted by Andreas Schnederle-Wagner *.futurecms.at *.ex.futurecms.at *.in.futurecms.at futurehosting.at futuremailing.at *.ex.ortsinfo.at *.kunden.ortsinfo.at *.statics.cloud // GDS : https://www.gov.uk/service-manual/technology/managing-domain-names // Submitted by Stephen Ford independent-commission.uk independent-inquest.uk independent-inquiry.uk independent-panel.uk independent-review.uk public-inquiry.uk royal-commission.uk campaign.gov.uk service.gov.uk // CDDO : https://www.gov.uk/guidance/get-an-api-domain-on-govuk // Submitted by Jamie Tanna api.gov.uk // Gehirn Inc. : https://www.gehirn.co.jp/ // Submitted by Kohei YOSHIDA gehirn.ne.jp usercontent.jp // Gentlent, Inc. : https://www.gentlent.com // Submitted by Tom Klein gentapps.com gentlentapis.com lab.ms cdn-edges.net // Ghost Foundation : https://ghost.org // Submitted by Matt Hanley ghost.io // GignoSystemJapan: http://gsj.bz // Submitted by GignoSystemJapan gsj.bz // GitHub, Inc. // Submitted by Patrick Toomey githubusercontent.com githubpreview.dev github.io // GitLab, Inc. // Submitted by Alex Hanselka gitlab.io // Gitplac.si - https://gitplac.si // Submitted by Aljaž Starc gitapp.si gitpage.si // Glitch, Inc : https://glitch.com // Submitted by Mads Hartmann glitch.me // Global NOG Alliance : https://nogalliance.org/ // Submitted by Sander Steffann nog.community // Globe Hosting SRL : https://www.globehosting.com/ // Submitted by Gavin Brown co.ro shop.ro // GMO Pepabo, Inc. : https://pepabo.com/ // Submitted by Hosting Div lolipop.io angry.jp babyblue.jp babymilk.jp backdrop.jp bambina.jp bitter.jp blush.jp boo.jp boy.jp boyfriend.jp but.jp candypop.jp capoo.jp catfood.jp cheap.jp chicappa.jp chillout.jp chips.jp chowder.jp chu.jp ciao.jp cocotte.jp coolblog.jp cranky.jp cutegirl.jp daa.jp deca.jp deci.jp digick.jp egoism.jp fakefur.jp fem.jp flier.jp floppy.jp fool.jp frenchkiss.jp girlfriend.jp girly.jp gloomy.jp gonna.jp greater.jp hacca.jp heavy.jp her.jp hiho.jp hippy.jp holy.jp hungry.jp icurus.jp itigo.jp jellybean.jp kikirara.jp kill.jp kilo.jp kuron.jp littlestar.jp lolipopmc.jp lolitapunk.jp lomo.jp lovepop.jp lovesick.jp main.jp mods.jp mond.jp mongolian.jp moo.jp namaste.jp nikita.jp nobushi.jp noor.jp oops.jp parallel.jp parasite.jp pecori.jp peewee.jp penne.jp pepper.jp perma.jp pigboat.jp pinoko.jp punyu.jp pupu.jp pussycat.jp pya.jp raindrop.jp readymade.jp sadist.jp schoolbus.jp secret.jp staba.jp stripper.jp sub.jp sunnyday.jp thick.jp tonkotsu.jp under.jp upper.jp velvet.jp verse.jp versus.jp vivian.jp watson.jp weblike.jp whitesnow.jp zombie.jp heteml.net // GOV.UK Platform as a Service : https://www.cloud.service.gov.uk/ // Submitted by Tom Whitwell cloudapps.digital london.cloudapps.digital // GOV.UK Pay : https://www.payments.service.gov.uk/ // Submitted by Richard Baker pymnt.uk // UKHomeOffice : https://www.gov.uk/government/organisations/home-office // Submitted by Jon Shanks homeoffice.gov.uk // GlobeHosting, Inc. // Submitted by Zoltan Egresi ro.im // GoIP DNS Services : http://www.goip.de // Submitted by Christian Poulter goip.de // Google, Inc. // Submitted by Eduardo Vela run.app a.run.app web.app *.0emm.com appspot.com *.r.appspot.com codespot.com googleapis.com googlecode.com pagespeedmobilizer.com publishproxy.com withgoogle.com withyoutube.com *.gateway.dev cloud.goog translate.goog *.usercontent.goog cloudfunctions.net blogspot.ae blogspot.al blogspot.am blogspot.ba blogspot.be blogspot.bg blogspot.bj blogspot.ca blogspot.cf blogspot.ch blogspot.cl blogspot.co.at blogspot.co.id blogspot.co.il blogspot.co.ke blogspot.co.nz blogspot.co.uk blogspot.co.za blogspot.com blogspot.com.ar blogspot.com.au blogspot.com.br blogspot.com.by blogspot.com.co blogspot.com.cy blogspot.com.ee blogspot.com.eg blogspot.com.es blogspot.com.mt blogspot.com.ng blogspot.com.tr blogspot.com.uy blogspot.cv blogspot.cz blogspot.de blogspot.dk blogspot.fi blogspot.fr blogspot.gr blogspot.hk blogspot.hr blogspot.hu blogspot.ie blogspot.in blogspot.is blogspot.it blogspot.jp blogspot.kr blogspot.li blogspot.lt blogspot.lu blogspot.md blogspot.mk blogspot.mr blogspot.mx blogspot.my blogspot.nl blogspot.no blogspot.pe blogspot.pt blogspot.qa blogspot.re blogspot.ro blogspot.rs blogspot.ru blogspot.se blogspot.sg blogspot.si blogspot.sk blogspot.sn blogspot.td blogspot.tw blogspot.ug blogspot.vn // Goupile : https://goupile.fr // Submitted by Niels Martignene goupile.fr // Government of the Netherlands: https://www.government.nl // Submitted by gov.nl // Group 53, LLC : https://www.group53.com // Submitted by Tyler Todd awsmppl.com // GünstigBestellen : https://günstigbestellen.de // Submitted by Furkan Akkoc günstigbestellen.de günstigliefern.de // Hakaran group: http://hakaran.cz // Submitted by Arseniy Sokolov fin.ci free.hr caa.li ua.rs conf.se // Handshake : https://handshake.org // Submitted by Mike Damm hs.zone hs.run // Hashbang : https://hashbang.sh hashbang.sh // Hasura : https://hasura.io // Submitted by Shahidh K Muhammed hasura.app hasura-app.io // Heilbronn University of Applied Sciences - Faculty Informatics (GitLab Pages): https://www.hs-heilbronn.de // Submitted by Richard Zowalla pages.it.hs-heilbronn.de // Hepforge : https://www.hepforge.org // Submitted by David Grellscheid hepforge.org // Heroku : https://www.heroku.com/ // Submitted by Tom Maher herokuapp.com herokussl.com // Hibernating Rhinos // Submitted by Oren Eini ravendb.cloud ravendb.community ravendb.me development.run ravendb.run // home.pl S.A.: https://home.pl // Submitted by Krzysztof Wolski homesklep.pl // Hong Kong Productivity Council: https://www.hkpc.org/ // Submitted by SECaaS Team secaas.hk // Hoplix : https://www.hoplix.com // Submitted by Danilo De Franco hoplix.shop // HOSTBIP REGISTRY : https://www.hostbip.com/ // Submitted by Atanunu Igbunuroghene orx.biz biz.gl col.ng firm.ng gen.ng ltd.ng ngo.ng edu.scot sch.so // HostFly : https://www.ie.ua // Submitted by Bohdan Dub ie.ua // HostyHosting (hostyhosting.com) hostyhosting.io // Häkkinen.fi // Submitted by Eero Häkkinen häkkinen.fi // Ici la Lune : http://www.icilalune.com/ // Submitted by Simon Morvan *.moonscale.io moonscale.net // iki.fi // Submitted by Hannu Aronsson iki.fi // iliad italia: https://www.iliad.it // Submitted by Marios Makassikis ibxos.it iliadboxos.it // Impertrix Solutions : // Submitted by Zhixiang Zhao impertrixcdn.com impertrix.com // Incsub, LLC: https://incsub.com/ // Submitted by Aaron Edwards smushcdn.com wphostedmail.com wpmucdn.com tempurl.host wpmudev.host // Individual Network Berlin e.V. : https://www.in-berlin.de/ // Submitted by Christian Seitz dyn-berlin.de in-berlin.de in-brb.de in-butter.de in-dsl.de in-dsl.net in-dsl.org in-vpn.de in-vpn.net in-vpn.org // info.at : http://www.info.at/ biz.at info.at // info.cx : http://info.cx // Submitted by Jacob Slater info.cx // Interlegis : http://www.interlegis.leg.br // Submitted by Gabriel Ferreira ac.leg.br al.leg.br am.leg.br ap.leg.br ba.leg.br ce.leg.br df.leg.br es.leg.br go.leg.br ma.leg.br mg.leg.br ms.leg.br mt.leg.br pa.leg.br pb.leg.br pe.leg.br pi.leg.br pr.leg.br rj.leg.br rn.leg.br ro.leg.br rr.leg.br rs.leg.br sc.leg.br se.leg.br sp.leg.br to.leg.br // intermetrics GmbH : https://pixolino.com/ // Submitted by Wolfgang Schwarz pixolino.com // Internet-Pro, LLP: https://netangels.ru/ // Submitted by Vasiliy Sheredeko na4u.ru // iopsys software solutions AB : https://iopsys.eu/ // Submitted by Roman Azarenko iopsys.se // IPiFony Systems, Inc. : https://www.ipifony.com/ // Submitted by Matthew Hardeman ipifony.net // IServ GmbH : https://iserv.de // Submitted by Mario Hoberg iservschule.de mein-iserv.de schulplattform.de schulserver.de test-iserv.de iserv.dev // I-O DATA DEVICE, INC. : http://www.iodata.com/ // Submitted by Yuji Minagawa iobb.net // Jelastic, Inc. : https://jelastic.com/ // Submitted by Ihor Kolodyuk mel.cloudlets.com.au cloud.interhostsolutions.be users.scale.virtualcloud.com.br mycloud.by alp1.ae.flow.ch appengine.flow.ch es-1.axarnet.cloud diadem.cloud vip.jelastic.cloud jele.cloud it1.eur.aruba.jenv-aruba.cloud it1.jenv-aruba.cloud keliweb.cloud cs.keliweb.cloud oxa.cloud tn.oxa.cloud uk.oxa.cloud primetel.cloud uk.primetel.cloud ca.reclaim.cloud uk.reclaim.cloud us.reclaim.cloud ch.trendhosting.cloud de.trendhosting.cloud jele.club amscompute.com clicketcloud.com dopaas.com hidora.com paas.hosted-by-previder.com rag-cloud.hosteur.com rag-cloud-ch.hosteur.com jcloud.ik-server.com jcloud-ver-jpc.ik-server.com demo.jelastic.com kilatiron.com paas.massivegrid.com jed.wafaicloud.com lon.wafaicloud.com ryd.wafaicloud.com j.scaleforce.com.cy jelastic.dogado.eu fi.cloudplatform.fi demo.datacenter.fi paas.datacenter.fi jele.host mircloud.host paas.beebyte.io sekd1.beebyteapp.io jele.io cloud-fr1.unispace.io jc.neen.it cloud.jelastic.open.tim.it jcloud.kz upaas.kazteleport.kz cloudjiffy.net fra1-de.cloudjiffy.net west1-us.cloudjiffy.net jls-sto1.elastx.net jls-sto2.elastx.net jls-sto3.elastx.net faststacks.net fr-1.paas.massivegrid.net lon-1.paas.massivegrid.net lon-2.paas.massivegrid.net ny-1.paas.massivegrid.net ny-2.paas.massivegrid.net sg-1.paas.massivegrid.net jelastic.saveincloud.net nordeste-idc.saveincloud.net j.scaleforce.net jelastic.tsukaeru.net sdscloud.pl unicloud.pl mircloud.ru jelastic.regruhosting.ru enscaled.sg jele.site jelastic.team orangecloud.tn j.layershift.co.uk phx.enscaled.us mircloud.us // Jino : https://www.jino.ru // Submitted by Sergey Ulyashin myjino.ru *.hosting.myjino.ru *.landing.myjino.ru *.spectrum.myjino.ru *.vps.myjino.ru // Jotelulu S.L. : https://jotelulu.com // Submitted by Daniel Fariña jotelulu.cloud // Joyent : https://www.joyent.com/ // Submitted by Brian Bennett *.triton.zone *.cns.joyent.com // JS.ORG : http://dns.js.org // Submitted by Stefan Keim js.org // KaasHosting : http://www.kaashosting.nl/ // Submitted by Wouter Bakker kaas.gg khplay.nl // Kakao : https://www.kakaocorp.com/ // Submitted by JaeYoong Lee ktistory.com // Kapsi : https://kapsi.fi // Submitted by Tomi Juntunen kapsi.fi // Keyweb AG : https://www.keyweb.de // Submitted by Martin Dannehl keymachine.de // KingHost : https://king.host // Submitted by Felipe Keller Braz kinghost.net uni5.net // KnightPoint Systems, LLC : http://www.knightpoint.com/ // Submitted by Roy Keene knightpoint.systems // KoobinEvent, SL: https://www.koobin.com // Submitted by Iván Oliva koobin.events // KUROKU LTD : https://kuroku.ltd/ // Submitted by DisposaBoy oya.to // Katholieke Universiteit Leuven: https://www.kuleuven.be // Submitted by Abuse KU Leuven kuleuven.cloud ezproxy.kuleuven.be // .KRD : http://nic.krd/data/krd/Registration%20Policy.pdf co.krd edu.krd // Krellian Ltd. : https://krellian.com // Submitted by Ben Francis krellian.net webthings.io // LCube - Professional hosting e.K. : https://www.lcube-webhosting.de // Submitted by Lars Laehn git-repos.de lcube-server.de svn-repos.de // Leadpages : https://www.leadpages.net // Submitted by Greg Dallavalle leadpages.co lpages.co lpusercontent.com // Lelux.fi : https://lelux.fi/ // Submitted by Lelux Admin lelux.site // Lifetime Hosting : https://Lifetime.Hosting/ // Submitted by Mike Fillator co.business co.education co.events co.financial co.network co.place co.technology // Lightmaker Property Manager, Inc. : https://app.lmpm.com/ // Submitted by Greg Holland app.lmpm.com // linkyard ldt: https://www.linkyard.ch/ // Submitted by Mario Siegenthaler linkyard.cloud linkyard-cloud.ch // Linode : https://linode.com // Submitted by members.linode.com *.nodebalancer.linode.com *.linodeobjects.com ip.linodeusercontent.com // LiquidNet Ltd : http://www.liquidnetlimited.com/ // Submitted by Victor Velchev we.bs // Localcert : https://localcert.dev // Submitted by Lann Martin *.user.localcert.dev // localzone.xyz // Submitted by Kenny Niehage localzone.xyz // Log'in Line : https://www.loginline.com/ // Submitted by Rémi Mach loginline.app loginline.dev loginline.io loginline.services loginline.site // Lokalized : https://lokalized.nl // Submitted by Noah Taheij servers.run // Lõhmus Family, The // Submitted by Heiki Lõhmus lohmus.me // LubMAN UMCS Sp. z o.o : https://lubman.pl/ // Submitted by Ireneusz Maliszewski krasnik.pl leczna.pl lubartow.pl lublin.pl poniatowa.pl swidnik.pl // Lug.org.uk : https://lug.org.uk // Submitted by Jon Spriggs glug.org.uk lug.org.uk lugs.org.uk // Lukanet Ltd : https://lukanet.com // Submitted by Anton Avramov barsy.bg barsy.co.uk barsyonline.co.uk barsycenter.com barsyonline.com barsy.club barsy.de barsy.eu barsy.in barsy.info barsy.io barsy.me barsy.menu barsy.mobi barsy.net barsy.online barsy.org barsy.pro barsy.pub barsy.ro barsy.shop barsy.site barsy.support barsy.uk // Magento Commerce // Submitted by Damien Tournoud *.magentosite.cloud // May First - People Link : https://mayfirst.org/ // Submitted by Jamie McClelland mayfirst.info mayfirst.org // Mail.Ru Group : https://hb.cldmail.ru // Submitted by Ilya Zaretskiy hb.cldmail.ru // Mail Transfer Platform : https://www.neupeer.com // Submitted by Li Hui cn.vu // Maze Play: https://www.mazeplay.com // Submitted by Adam Humpherys mazeplay.com // mcpe.me : https://mcpe.me // Submitted by Noa Heyl mcpe.me // McHost : https://mchost.ru // Submitted by Evgeniy Subbotin mcdir.me mcdir.ru mcpre.ru vps.mcdir.ru // Mediatech : https://mediatech.by // Submitted by Evgeniy Kozhuhovskiy mediatech.by mediatech.dev // Medicom Health : https://medicomhealth.com // Submitted by Michael Olson hra.health // Memset hosting : https://www.memset.com // Submitted by Tom Whitwell miniserver.com memset.net // Messerli Informatik AG : https://www.messerli.ch/ // Submitted by Ruben Schmidmeister messerli.app // MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/ // Submitted by Zdeněk Šustr *.cloud.metacentrum.cz custom.metacentrum.cz // MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/ // Submitted by Radim Janča flt.cloud.muni.cz usr.cloud.muni.cz // Meteor Development Group : https://www.meteor.com/hosting // Submitted by Pierre Carrier meteorapp.com eu.meteorapp.com // Michau Enterprises Limited : http://www.co.pl/ co.pl // Microsoft Corporation : http://microsoft.com // Submitted by Public Suffix List Admin *.azurecontainer.io azurewebsites.net azure-mobile.net cloudapp.net azurestaticapps.net 1.azurestaticapps.net 2.azurestaticapps.net 3.azurestaticapps.net centralus.azurestaticapps.net eastasia.azurestaticapps.net eastus2.azurestaticapps.net westeurope.azurestaticapps.net westus2.azurestaticapps.net // minion.systems : http://minion.systems // Submitted by Robert Böttinger csx.cc // Mintere : https://mintere.com/ // Submitted by Ben Aubin mintere.site // MobileEducation, LLC : https://joinforte.com // Submitted by Grayson Martin forte.id // Mozilla Corporation : https://mozilla.com // Submitted by Ben Francis mozilla-iot.org // Mozilla Foundation : https://mozilla.org/ // Submitted by glob bmoattachments.org // MSK-IX : https://www.msk-ix.ru/ // Submitted by Khannanov Roman net.ru org.ru pp.ru // Mythic Beasts : https://www.mythic-beasts.com // Submitted by Paul Cammish hostedpi.com customer.mythic-beasts.com caracal.mythic-beasts.com fentiger.mythic-beasts.com lynx.mythic-beasts.com ocelot.mythic-beasts.com oncilla.mythic-beasts.com onza.mythic-beasts.com sphinx.mythic-beasts.com vs.mythic-beasts.com x.mythic-beasts.com yali.mythic-beasts.com cust.retrosnub.co.uk // Nabu Casa : https://www.nabucasa.com // Submitted by Paulus Schoutsen ui.nabu.casa // Net at Work Gmbh : https://www.netatwork.de // Submitted by Jan Jaeschke cloud.nospamproxy.com // Netlify : https://www.netlify.com // Submitted by Jessica Parsons netlify.app // Neustar Inc. // Submitted by Trung Tran 4u.com // ngrok : https://ngrok.com/ // Submitted by Alan Shreve ngrok.app ngrok-free.app ngrok.dev ngrok-free.dev ngrok.io ap.ngrok.io au.ngrok.io eu.ngrok.io in.ngrok.io jp.ngrok.io sa.ngrok.io us.ngrok.io ngrok.pizza // Nimbus Hosting Ltd. : https://www.nimbushosting.co.uk/ // Submitted by Nicholas Ford nh-serv.co.uk // NFSN, Inc. : https://www.NearlyFreeSpeech.NET/ // Submitted by Jeff Wheelhouse nfshost.com // Noop : https://noop.app // Submitted by Nathaniel Schweinberg *.developer.app noop.app // Northflank Ltd. : https://northflank.com/ // Submitted by Marco Suter *.northflank.app *.build.run *.code.run *.database.run *.migration.run // Noticeable : https://noticeable.io // Submitted by Laurent Pellegrino noticeable.news // Now-DNS : https://now-dns.com // Submitted by Steve Russell dnsking.ch mypi.co n4t.co 001www.com ddnslive.com myiphost.com forumz.info 16-b.it 32-b.it 64-b.it soundcast.me tcp4.me dnsup.net hicam.net now-dns.net ownip.net vpndns.net dynserv.org now-dns.org x443.pw now-dns.top ntdll.top freeddns.us crafting.xyz zapto.xyz // nsupdate.info : https://www.nsupdate.info/ // Submitted by Thomas Waldmann nsupdate.info nerdpol.ovh // No-IP.com : https://noip.com/ // Submitted by Deven Reza blogsyte.com brasilia.me cable-modem.org ciscofreak.com collegefan.org couchpotatofries.org damnserver.com ddns.me ditchyourip.com dnsfor.me dnsiskinky.com dvrcam.info dynns.com eating-organic.net fantasyleague.cc geekgalaxy.com golffan.us health-carereform.com homesecuritymac.com homesecuritypc.com hopto.me ilovecollege.info loginto.me mlbfan.org mmafan.biz myactivedirectory.com mydissent.net myeffect.net mymediapc.net mypsx.net mysecuritycamera.com mysecuritycamera.net mysecuritycamera.org net-freaks.com nflfan.org nhlfan.net no-ip.ca no-ip.co.uk no-ip.net noip.us onthewifi.com pgafan.net point2this.com pointto.us privatizehealthinsurance.net quicksytes.com read-books.org securitytactics.com serveexchange.com servehumour.com servep2p.com servesarcasm.com stufftoread.com ufcfan.org unusualperson.com workisboring.com 3utilities.com bounceme.net ddns.net ddnsking.com gotdns.ch hopto.org myftp.biz myftp.org myvnc.com no-ip.biz no-ip.info no-ip.org noip.me redirectme.net servebeer.com serveblog.net servecounterstrike.com serveftp.com servegame.com servehalflife.com servehttp.com serveirc.com serveminecraft.net servemp3.com servepics.com servequake.com sytes.net webhop.me zapto.org // NodeArt : https://nodeart.io // Submitted by Konstantin Nosov stage.nodeart.io // Nucleos Inc. : https://nucleos.com // Submitted by Piotr Zduniak pcloud.host // NYC.mn : http://www.information.nyc.mn // Submitted by Matthew Brown nyc.mn // Observable, Inc. : https://observablehq.com // Submitted by Mike Bostock static.observableusercontent.com // Octopodal Solutions, LLC. : https://ulterius.io/ // Submitted by Andrew Sampson cya.gg // OMG.LOL : // Submitted by Adam Newbold omg.lol // Omnibond Systems, LLC. : https://www.omnibond.com // Submitted by Cole Estep cloudycluster.net // OmniWe Limited: https://omniwe.com // Submitted by Vicary Archangel omniwe.site // One.com: https://www.one.com/ // Submitted by Jacob Bunk Nielsen 123hjemmeside.dk 123hjemmeside.no 123homepage.it 123kotisivu.fi 123minsida.se 123miweb.es 123paginaweb.pt 123sait.ru 123siteweb.fr 123webseite.at 123webseite.de 123website.be 123website.ch 123website.lu 123website.nl service.one simplesite.com simplesite.com.br simplesite.gr simplesite.pl // One Fold Media : http://www.onefoldmedia.com/ // Submitted by Eddie Jones nid.io // Open Social : https://www.getopensocial.com/ // Submitted by Alexander Varwijk opensocial.site // OpenCraft GmbH : http://opencraft.com/ // Submitted by Sven Marnach opencraft.hosting // OpenResearch GmbH: https://openresearch.com/ // Submitted by Philipp Schmid orsites.com // Opera Software, A.S.A. // Submitted by Yngve Pettersen operaunite.com // Orange : https://www.orange.com // Submitted by Alexandre Linte tech.orange // Oursky Limited : https://authgear.com/, https://skygear.io/ // Submitted by Authgear Team , Skygear Developer authgear-staging.com authgearapps.com skygearapp.com // OutSystems // Submitted by Duarte Santos outsystemscloud.com // OVHcloud: https://ovhcloud.com // Submitted by Vincent Cassé *.webpaas.ovh.net *.hosting.ovh.net // OwnProvider GmbH: http://www.ownprovider.com // Submitted by Jan Moennich ownprovider.com own.pm // OwO : https://whats-th.is/ // Submitted by Dean Sheather *.owo.codes // OX : http://www.ox.rs // Submitted by Adam Grand ox.rs // oy.lc // Submitted by Charly Coste oy.lc // Pagefog : https://pagefog.com/ // Submitted by Derek Myers pgfog.com // Pagefront : https://www.pagefronthq.com/ // Submitted by Jason Kriss pagefrontapp.com // PageXL : https://pagexl.com // Submitted by Yann Guichard pagexl.com // Paywhirl, Inc : https://paywhirl.com/ // Submitted by Daniel Netzer *.paywhirl.com // pcarrier.ca Software Inc: https://pcarrier.ca/ // Submitted by Pierre Carrier bar0.net bar1.net bar2.net rdv.to // .pl domains (grandfathered) art.pl gliwice.pl krakow.pl poznan.pl wroc.pl zakopane.pl // Pantheon Systems, Inc. : https://pantheon.io/ // Submitted by Gary Dylina pantheonsite.io gotpantheon.com // Peplink | Pepwave : http://peplink.com/ // Submitted by Steve Leung mypep.link // Perspecta : https://perspecta.com/ // Submitted by Kenneth Van Alstyne perspecta.cloud // PE Ulyanov Kirill Sergeevich : https://airy.host // Submitted by Kirill Ulyanov lk3.ru // Planet-Work : https://www.planet-work.com/ // Submitted by Frédéric VANNIÈRE on-web.fr // Platform.sh : https://platform.sh // Submitted by Nikola Kotur bc.platform.sh ent.platform.sh eu.platform.sh us.platform.sh *.platformsh.site *.tst.site // Platter: https://platter.dev // Submitted by Patrick Flor platter-app.com platter-app.dev platterp.us // Plesk : https://www.plesk.com/ // Submitted by Anton Akhtyamov pdns.page plesk.page pleskns.com // Port53 : https://port53.io/ // Submitted by Maximilian Schieder dyn53.io // Porter : https://porter.run/ // Submitted by Rudraksh MK onporter.run // Positive Codes Technology Company : http://co.bn/faq.html // Submitted by Zulfais co.bn // Postman, Inc : https://postman.com // Submitted by Rahul Dhawan postman-echo.com pstmn.io mock.pstmn.io httpbin.org //prequalifyme.today : https://prequalifyme.today //Submitted by DeepakTiwari deepak@ivylead.io prequalifyme.today // prgmr.com : https://prgmr.com/ // Submitted by Sarah Newman xen.prgmr.com // priv.at : http://www.nic.priv.at/ // Submitted by registry priv.at // privacytools.io : https://www.privacytools.io/ // Submitted by Jonah Aragon prvcy.page // Protocol Labs : https://protocol.ai/ // Submitted by Michael Burns *.dweb.link // Protonet GmbH : http://protonet.io // Submitted by Martin Meier protonet.io // Publication Presse Communication SARL : https://ppcom.fr // Submitted by Yaacov Akiba Slama chirurgiens-dentistes-en-france.fr byen.site // pubtls.org: https://www.pubtls.org // Submitted by Kor Nielsen pubtls.org // PythonAnywhere LLP: https://www.pythonanywhere.com // Submitted by Giles Thomas pythonanywhere.com eu.pythonanywhere.com // QOTO, Org. // Submitted by Jeffrey Phillips Freeman qoto.io // Qualifio : https://qualifio.com/ // Submitted by Xavier De Cock qualifioapp.com // Quality Unit: https://qualityunit.com // Submitted by Vasyl Tsalko ladesk.com // QuickBackend: https://www.quickbackend.com // Submitted by Dani Biro qbuser.com // Rad Web Hosting: https://radwebhosting.com // Submitted by Scott Claeys cloudsite.builders // Redgate Software: https://red-gate.com // Submitted by Andrew Farries instances.spawn.cc // Redstar Consultants : https://www.redstarconsultants.com/ // Submitted by Jons Slemmer instantcloud.cn // Russian Academy of Sciences // Submitted by Tech Support ras.ru // QA2 // Submitted by Daniel Dent (https://www.danieldent.com/) qa2.com // QCX // Submitted by Cassandra Beelen qcx.io *.sys.qcx.io // QNAP System Inc : https://www.qnap.com // Submitted by Nick Chang dev-myqnapcloud.com alpha-myqnapcloud.com myqnapcloud.com // Quip : https://quip.com // Submitted by Patrick Linehan *.quipelements.com // Qutheory LLC : http://qutheory.io // Submitted by Jonas Schwartz vapor.cloud vaporcloud.io // Rackmaze LLC : https://www.rackmaze.com // Submitted by Kirill Pertsev rackmaze.com rackmaze.net // Rakuten Games, Inc : https://dev.viberplay.io // Submitted by Joshua Zhang g.vbrplsbx.io // Rancher Labs, Inc : https://rancher.com // Submitted by Vincent Fiduccia *.on-k3s.io *.on-rancher.cloud *.on-rio.io // Read The Docs, Inc : https://www.readthedocs.org // Submitted by David Fischer readthedocs.io // Red Hat, Inc. OpenShift : https://openshift.redhat.com/ // Submitted by Tim Kramer rhcloud.com // Render : https://render.com // Submitted by Anurag Goel app.render.com onrender.com // Repl.it : https://repl.it // Submitted by Lincoln Bergeson firewalledreplit.co id.firewalledreplit.co repl.co id.repl.co repl.run // Resin.io : https://resin.io // Submitted by Tim Perry resindevice.io devices.resinstaging.io // RethinkDB : https://www.rethinkdb.com/ // Submitted by Chris Kastorff hzc.io // Revitalised Limited : http://www.revitalised.co.uk // Submitted by Jack Price wellbeingzone.eu wellbeingzone.co.uk // Rico Developments Limited : https://adimo.co // Submitted by Colin Brown adimo.co.uk // Riseup Networks : https://riseup.net // Submitted by Micah Anderson itcouldbewor.se // Rochester Institute of Technology : http://www.rit.edu/ // Submitted by Jennifer Herting git-pages.rit.edu // Rocky Enterprise Software Foundation : https://resf.org // Submitted by Neil Hanlon rocky.page // Rusnames Limited: http://rusnames.ru/ // Submitted by Sergey Zotov биз.рус ком.рус крым.рус мир.рус мск.рус орг.рус самара.рус сочи.рус спб.рус я.рус // SAKURA Internet Inc. : https://www.sakura.ad.jp/ // Submitted by Internet Service Department 180r.com dojin.com sakuratan.com sakuraweb.com x0.com 2-d.jp bona.jp crap.jp daynight.jp eek.jp flop.jp halfmoon.jp jeez.jp matrix.jp mimoza.jp ivory.ne.jp mail-box.ne.jp mints.ne.jp mokuren.ne.jp opal.ne.jp sakura.ne.jp sumomo.ne.jp topaz.ne.jp netgamers.jp nyanta.jp o0o0.jp rdy.jp rgr.jp rulez.jp s3.isk01.sakurastorage.jp s3.isk02.sakurastorage.jp saloon.jp sblo.jp skr.jp tank.jp uh-oh.jp undo.jp rs.webaccel.jp user.webaccel.jp websozai.jp xii.jp squares.net jpn.org kirara.st x0.to from.tv sakura.tv // Salesforce.com, Inc. https://salesforce.com/ // Submitted by Michael Biven *.builder.code.com *.dev-builder.code.com *.stg-builder.code.com // Sandstorm Development Group, Inc. : https://sandcats.io/ // Submitted by Asheesh Laroia sandcats.io // SBE network solutions GmbH : https://www.sbe.de/ // Submitted by Norman Meilick logoip.de logoip.com // Scaleway : https://www.scaleway.com/ // Submitted by Rémy Léone fr-par-1.baremetal.scw.cloud fr-par-2.baremetal.scw.cloud nl-ams-1.baremetal.scw.cloud fnc.fr-par.scw.cloud functions.fnc.fr-par.scw.cloud k8s.fr-par.scw.cloud nodes.k8s.fr-par.scw.cloud s3.fr-par.scw.cloud s3-website.fr-par.scw.cloud whm.fr-par.scw.cloud priv.instances.scw.cloud pub.instances.scw.cloud k8s.scw.cloud k8s.nl-ams.scw.cloud nodes.k8s.nl-ams.scw.cloud s3.nl-ams.scw.cloud s3-website.nl-ams.scw.cloud whm.nl-ams.scw.cloud k8s.pl-waw.scw.cloud nodes.k8s.pl-waw.scw.cloud s3.pl-waw.scw.cloud s3-website.pl-waw.scw.cloud scalebook.scw.cloud smartlabeling.scw.cloud dedibox.fr // schokokeks.org GbR : https://schokokeks.org/ // Submitted by Hanno Böck schokokeks.net // Scottish Government: https://www.gov.scot // Submitted by Martin Ellis gov.scot service.gov.scot // Scry Security : http://www.scrysec.com // Submitted by Shante Adam scrysec.com // Securepoint GmbH : https://www.securepoint.de // Submitted by Erik Anders firewall-gateway.com firewall-gateway.de my-gateway.de my-router.de spdns.de spdns.eu firewall-gateway.net my-firewall.org myfirewall.org spdns.org // Seidat : https://www.seidat.com // Submitted by Artem Kondratev seidat.net // Sellfy : https://sellfy.com // Submitted by Yuriy Romadin sellfy.store // Senseering GmbH : https://www.senseering.de // Submitted by Felix Mönckemeyer senseering.net // Sendmsg: https://www.sendmsg.co.il // Submitted by Assaf Stern minisite.ms // Service Magnet : https://myservicemagnet.com // Submitted by Dave Sanders magnet.page // Service Online LLC : http://drs.ua/ // Submitted by Serhii Bulakh biz.ua co.ua pp.ua // Shift Crypto AG : https://shiftcrypto.ch // Submitted by alex shiftcrypto.dev shiftcrypto.io // ShiftEdit : https://shiftedit.net/ // Submitted by Adam Jimenez shiftedit.io // Shopblocks : http://www.shopblocks.com/ // Submitted by Alex Bowers myshopblocks.com // Shopify : https://www.shopify.com // Submitted by Alex Richter myshopify.com // Shopit : https://www.shopitcommerce.com/ // Submitted by Craig McMahon shopitsite.com // shopware AG : https://shopware.com // Submitted by Jens Küper shopware.store // Siemens Mobility GmbH // Submitted by Oliver Graebner mo-siemens.io // SinaAppEngine : http://sae.sina.com.cn/ // Submitted by SinaAppEngine 1kapp.com appchizi.com applinzi.com sinaapp.com vipsinaapp.com // Siteleaf : https://www.siteleaf.com/ // Submitted by Skylar Challand siteleaf.net // Skyhat : http://www.skyhat.io // Submitted by Shante Adam bounty-full.com alpha.bounty-full.com beta.bounty-full.com // Small Technology Foundation : https://small-tech.org // Submitted by Aral Balkan small-web.org // Smoove.io : https://www.smoove.io/ // Submitted by Dan Kozak vp4.me // Snowflake Inc : https://www.snowflake.com/ // Submitted by Faith Olapade snowflake.app privatelink.snowflake.app streamlit.app streamlitapp.com // Snowplow Analytics : https://snowplowanalytics.com/ // Submitted by Ian Streeter try-snowplow.com // SourceHut : https://sourcehut.org // Submitted by Drew DeVault srht.site // Stackhero : https://www.stackhero.io // Submitted by Adrien Gillon stackhero-network.com // Staclar : https://staclar.com // Submitted by Q Misell musician.io // Submitted by Matthias Merkel novecore.site // staticland : https://static.land // Submitted by Seth Vincent static.land dev.static.land sites.static.land // Storebase : https://www.storebase.io // Submitted by Tony Schirmer storebase.store // Strategic System Consulting (eApps Hosting): https://www.eapps.com/ // Submitted by Alex Oancea vps-host.net atl.jelastic.vps-host.net njs.jelastic.vps-host.net ric.jelastic.vps-host.net // Sony Interactive Entertainment LLC : https://sie.com/ // Submitted by David Coles playstation-cloud.com // SourceLair PC : https://www.sourcelair.com // Submitted by Antonis Kalipetis apps.lair.io *.stolos.io // SpaceKit : https://www.spacekit.io/ // Submitted by Reza Akhavan spacekit.io // SpeedPartner GmbH: https://www.speedpartner.de/ // Submitted by Stefan Neufeind customer.speedpartner.de // Spreadshop (sprd.net AG) : https://www.spreadshop.com/ // Submitted by Martin Breest myspreadshop.at myspreadshop.com.au myspreadshop.be myspreadshop.ca myspreadshop.ch myspreadshop.com myspreadshop.de myspreadshop.dk myspreadshop.es myspreadshop.fi myspreadshop.fr myspreadshop.ie myspreadshop.it myspreadshop.net myspreadshop.nl myspreadshop.no myspreadshop.pl myspreadshop.se myspreadshop.co.uk // Standard Library : https://stdlib.com // Submitted by Jacob Lee api.stdlib.com // Storj Labs Inc. : https://storj.io/ // Submitted by Philip Hutchins storj.farm // Studenten Net Twente : http://www.snt.utwente.nl/ // Submitted by Silke Hofstra utwente.io // Student-Run Computing Facility : https://www.srcf.net/ // Submitted by Edwin Balani soc.srcf.net user.srcf.net // Sub 6 Limited: http://www.sub6.com // Submitted by Dan Miller temp-dns.com // Supabase : https://supabase.io // Submitted by Inian Parameshwaran supabase.co supabase.in supabase.net su.paba.se // Symfony, SAS : https://symfony.com/ // Submitted by Fabien Potencier *.s5y.io *.sensiosite.cloud // Syncloud : https://syncloud.org // Submitted by Boris Rybalkin syncloud.it // Synology, Inc. : https://www.synology.com/ // Submitted by Rony Weng dscloud.biz direct.quickconnect.cn dsmynas.com familyds.com diskstation.me dscloud.me i234.me myds.me synology.me dscloud.mobi dsmynas.net familyds.net dsmynas.org familyds.org vpnplus.to direct.quickconnect.to // Tabit Technologies Ltd. : https://tabit.cloud/ // Submitted by Oren Agiv tabitorder.co.il mytabit.co.il mytabit.com // TAIFUN Software AG : http://taifun-software.de // Submitted by Bjoern Henke taifun-dns.de // Tailscale Inc. : https://www.tailscale.com // Submitted by David Anderson beta.tailscale.net ts.net // TASK geographical domains (www.task.gda.pl/uslugi/dns) gda.pl gdansk.pl gdynia.pl med.pl sopot.pl // team.blue https://team.blue // Submitted by Cedric Dubois site.tb-hosting.com // Teckids e.V. : https://www.teckids.org // Submitted by Dominik George edugit.io s3.teckids.org // Telebit : https://telebit.cloud // Submitted by AJ ONeal telebit.app telebit.io *.telebit.xyz // Thingdust AG : https://thingdust.com/ // Submitted by Adrian Imboden *.firenet.ch *.svc.firenet.ch reservd.com thingdustdata.com cust.dev.thingdust.io cust.disrec.thingdust.io cust.prod.thingdust.io cust.testing.thingdust.io reservd.dev.thingdust.io reservd.disrec.thingdust.io reservd.testing.thingdust.io // ticket i/O GmbH : https://ticket.io // Submitted by Christian Franke tickets.io // Tlon.io : https://tlon.io // Submitted by Mark Staarink arvo.network azimuth.network tlon.network // Tor Project, Inc. : https://torproject.org // Submitted by Antoine Beaupré bloxcms.com townnews-staging.com // TrafficPlex GmbH : https://www.trafficplex.de/ // Submitted by Phillipp Röll 12hp.at 2ix.at 4lima.at lima-city.at 12hp.ch 2ix.ch 4lima.ch lima-city.ch trafficplex.cloud de.cool 12hp.de 2ix.de 4lima.de lima-city.de 1337.pictures clan.rip lima-city.rocks webspace.rocks lima.zone // TransIP : https://www.transip.nl // Submitted by Rory Breuk *.transurl.be *.transurl.eu *.transurl.nl // TransIP: https://www.transip.nl // Submitted by Cedric Dubois site.transip.me // TuxFamily : http://tuxfamily.org // Submitted by TuxFamily administrators tuxfamily.org // TwoDNS : https://www.twodns.de/ // Submitted by TwoDNS-Support dd-dns.de diskstation.eu diskstation.org dray-dns.de draydns.de dyn-vpn.de dynvpn.de mein-vigor.de my-vigor.de my-wan.de syno-ds.de synology-diskstation.de synology-ds.de // Typedream : https://typedream.com // Submitted by Putri Karunia typedream.app // Typeform : https://www.typeform.com // Submitted by Sergi Ferriz pro.typeform.com // Uberspace : https://uberspace.de // Submitted by Moritz Werner uber.space *.uberspace.de // UDR Limited : http://www.udr.hk.com // Submitted by registry hk.com hk.org ltd.hk inc.hk // UK Intis Telecom LTD : https://it.com // Submitted by ITComdomains it.com // UNIVERSAL DOMAIN REGISTRY : https://www.udr.org.yt/ // see also: whois -h whois.udr.org.yt help // Submitted by Atanunu Igbunuroghene name.pm sch.tf biz.wf sch.wf org.yt // United Gameserver GmbH : https://united-gameserver.de // Submitted by Stefan Schwarz virtualuser.de virtual-user.de // Upli : https://upli.io // Submitted by Lenny Bakkalian upli.io // urown.net : https://urown.net // Submitted by Hostmaster urown.cloud dnsupdate.info // .US // Submitted by Ed Moore lib.de.us // VeryPositive SIA : http://very.lv // Submitted by Danko Aleksejevs 2038.io // Vercel, Inc : https://vercel.com/ // Submitted by Connor Davis vercel.app vercel.dev now.sh // Viprinet Europe GmbH : http://www.viprinet.com // Submitted by Simon Kissel router.management // Virtual-Info : https://www.virtual-info.info/ // Submitted by Adnan RIHAN v-info.info // Voorloper.com: https://voorloper.com // Submitted by Nathan van Bakel voorloper.cloud // Voxel.sh DNS : https://voxel.sh/dns/ // Submitted by Mia Rehlinger neko.am nyaa.am be.ax cat.ax es.ax eu.ax gg.ax mc.ax us.ax xy.ax nl.ci xx.gl app.gp blog.gt de.gt to.gt be.gy cc.hn blog.kg io.kg jp.kg tv.kg uk.kg us.kg de.ls at.md de.md jp.md to.md indie.porn vxl.sh ch.tc me.tc we.tc nyan.to at.vg blog.vu dev.vu me.vu // V.UA Domain Administrator : https://domain.v.ua/ // Submitted by Serhii Rostilo v.ua // Vultr Objects : https://www.vultr.com/products/object-storage/ // Submitted by Niels Maumenee *.vultrobjects.com // Waffle Computer Inc., Ltd. : https://docs.waffleinfo.com // Submitted by Masayuki Note wafflecell.com // WebHare bv: https://www.webhare.com/ // Submitted by Arnold Hendriks *.webhare.dev // WebHotelier Technologies Ltd: https://www.webhotelier.net/ // Submitted by Apostolos Tsakpinis reserve-online.net reserve-online.com bookonline.app hotelwithflight.com // WeDeploy by Liferay, Inc. : https://www.wedeploy.com // Submitted by Henrique Vicente wedeploy.io wedeploy.me wedeploy.sh // Western Digital Technologies, Inc : https://www.wdc.com // Submitted by Jung Jin remotewd.com // WIARD Enterprises : https://wiardweb.com // Submitted by Kidd Hustle pages.wiardweb.com // Wikimedia Labs : https://wikitech.wikimedia.org // Submitted by Arturo Borrero Gonzalez wmflabs.org toolforge.org wmcloud.org // WISP : https://wisp.gg // Submitted by Stepan Fedotov panel.gg daemon.panel.gg // Wizard Zines : https://wizardzines.com // Submitted by Julia Evans messwithdns.com // WoltLab GmbH : https://www.woltlab.com // Submitted by Tim Düsterhus woltlab-demo.com myforum.community community-pro.de diskussionsbereich.de community-pro.net meinforum.net // Woods Valldata : https://www.woodsvalldata.co.uk/ // Submitted by Chris Whittle affinitylottery.org.uk raffleentry.org.uk weeklylottery.org.uk // WP Engine : https://wpengine.com/ // Submitted by Michael Smith // Submitted by Brandon DuRette wpenginepowered.com js.wpenginepowered.com // Wix.com, Inc. : https://www.wix.com // Submitted by Shahar Talmi wixsite.com editorx.io // XenonCloud GbR: https://xenoncloud.net // Submitted by Julian Uphoff half.host // XnBay Technology : http://www.xnbay.com/ // Submitted by XnBay Developer xnbay.com u2.xnbay.com u2-local.xnbay.com // XS4ALL Internet bv : https://www.xs4all.nl/ // Submitted by Daniel Mostertman cistron.nl demon.nl xs4all.space // Yandex.Cloud LLC: https://cloud.yandex.com // Submitted by Alexander Lodin yandexcloud.net storage.yandexcloud.net website.yandexcloud.net // YesCourse Pty Ltd : https://yescourse.com // Submitted by Atul Bhouraskar official.academy // Yola : https://www.yola.com/ // Submitted by Stefano Rivera yolasite.com // Yombo : https://yombo.net // Submitted by Mitch Schwenk ybo.faith yombo.me homelink.one ybo.party ybo.review ybo.science ybo.trade // Yunohost : https://yunohost.org // Submitted by Valentin Grimaud ynh.fr nohost.me noho.st // ZaNiC : http://www.za.net/ // Submitted by registry za.net za.org // Zine EOOD : https://zine.bg/ // Submitted by Martin Angelov bss.design // Zitcom A/S : https://www.zitcom.dk // Submitted by Emil Stahl basicserver.io virtualserver.io enterprisecloud.nu // ===END PRIVATE DOMAINS=== ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/fido2/py.typed0000644000000000000000000000000014413232070012130 0ustar00././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/fido2/rpid.py0000644000000000000000000000520214413232070011752 0ustar00# 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. """ These functions validate RP_ID and APP_ID according to simplified TLD+1 rules, using a bundled copy of the public suffix list fetched from: https://publicsuffix.org/list/public_suffix_list.dat Advanced APP_ID values pointing to JSON files containing valid facets are not supported by this implementation. """ from __future__ import annotations import os from urllib.parse import urlparse tld_fname = os.path.join(os.path.dirname(__file__), "public_suffix_list.dat") with open(tld_fname, "rb") as f: suffixes = [ entry for entry in (line.decode("utf8").strip() for line in f.readlines()) if entry and not entry.startswith("//") ] def verify_rp_id(rp_id: str, origin: str) -> bool: """Checks if a Webauthn RP ID is usable for a given origin. :param rp_id: The RP ID to validate. :param origin: The origin of the request. :return: True if the RP ID is usable by the origin, False if not. """ if not rp_id: return False url = urlparse(origin) if url.scheme != "https": return False host = url.hostname if host == rp_id: return True if host and host.endswith("." + rp_id) and rp_id not in suffixes: return True return False ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1688637503.572309 fido2-1.1.2/fido2/server.py0000644000000000000000000004637714451510100012337 0ustar00# 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 annotations from .rpid import verify_rp_id from .cose import CoseKey from .utils import websafe_encode, websafe_decode from .webauthn import ( CollectedClientData, AuthenticatorData, AttestationObject, AttestedCredentialData, AttestationConveyancePreference, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, AuthenticatorSelectionCriteria, PublicKeyCredentialDescriptor, PublicKeyCredentialType, PublicKeyCredentialParameters, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, UserVerificationRequirement, ResidentKeyRequirement, AuthenticatorAttachment, RegistrationResponse, AuthenticationResponse, CredentialCreationOptions, CredentialRequestOptions, ) from cryptography.hazmat.primitives import constant_time from cryptography.exceptions import InvalidSignature as _InvalidSignature from dataclasses import replace from urllib.parse import urlparse from typing import Sequence, Mapping, Optional, Callable, Union, Tuple, Any, overload import os import logging logger = logging.getLogger(__name__) VerifyAttestation = Callable[[AttestationObject, bytes], None] VerifyOrigin = Callable[[str], bool] def _verify_origin_for_rp(rp_id: str) -> VerifyOrigin: return lambda o: verify_rp_id(rp_id, o) def _validata_challenge(challenge: Optional[bytes]) -> bytes: if challenge is None: challenge = os.urandom(32) else: if not isinstance(challenge, bytes): raise TypeError("Custom challenge must be of type 'bytes'.") if len(challenge) < 16: raise ValueError("Custom challenge length must be >= 16.") return challenge def to_descriptor( credential: AttestedCredentialData, transports=None ) -> PublicKeyCredentialDescriptor: """Converts an AttestedCredentialData to a PublicKeyCredentialDescriptor. :param credential: AttestedCredentialData containing the credential ID to use. :param transports: Optional list of AuthenticatorTransport strings to add to the descriptor. :return: A descriptor of the credential, for use with register_begin or authenticate_begin. :rtype: PublicKeyCredentialDescriptor """ return PublicKeyCredentialDescriptor( PublicKeyCredentialType.PUBLIC_KEY, credential.credential_id, transports ) def _wrap_credentials( creds: Optional[ Sequence[Union[AttestedCredentialData, PublicKeyCredentialDescriptor]] ], ) -> Optional[Sequence[PublicKeyCredentialDescriptor]]: if creds is None: return None return [ to_descriptor(c) if isinstance(c, AttestedCredentialData) else PublicKeyCredentialDescriptor.from_dict(c) for c in creds ] def _ignore_attestation( attestation_object: AttestationObject, client_data_hash: bytes ) -> None: """Ignore attestation.""" class Fido2Server: """FIDO2 server. :param rp: Relying party data as `PublicKeyCredentialRpEntity` instance. :param attestation: (optional) Requirement on authenticator attestation. :param verify_origin: (optional) Alternative function to validate an origin. :param verify_attestation: (optional) function to validate attestation, which is invoked with attestation_object and client_data_hash. It should return nothing and raise an exception on failure. By default, attestation is ignored. Attestation is also ignored if `attestation` is set to `none`. """ def __init__( self, rp: PublicKeyCredentialRpEntity, attestation: Optional[AttestationConveyancePreference] = None, verify_origin: Optional[VerifyOrigin] = None, verify_attestation: Optional[VerifyAttestation] = None, ): self.rp = PublicKeyCredentialRpEntity.from_dict(rp) self._verify = verify_origin or _verify_origin_for_rp(self.rp.id) self.timeout = None self.attestation = AttestationConveyancePreference(attestation) self.allowed_algorithms = [ PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, alg) for alg in CoseKey.supported_algorithms() ] self._verify_attestation = verify_attestation or _ignore_attestation logger.debug(f"Fido2Server initialized for RP: {self.rp}") def register_begin( self, user: PublicKeyCredentialUserEntity, credentials: Optional[ Sequence[Union[AttestedCredentialData, PublicKeyCredentialDescriptor]] ] = None, resident_key_requirement: Optional[ResidentKeyRequirement] = None, user_verification: Optional[UserVerificationRequirement] = None, authenticator_attachment: Optional[AuthenticatorAttachment] = None, challenge: Optional[bytes] = None, extensions=None, ) -> Tuple[CredentialCreationOptions, Any]: """Return a PublicKeyCredentialCreationOptions registration object and the internal state dictionary that needs to be passed as is to the corresponding `register_complete` call. :param user: The dict containing the user data. :param credentials: The list of previously registered credentials, these can be of type AttestedCredentialData, or PublicKeyCredentialDescriptor. :param resident_key_requirement: The desired RESIDENT_KEY_REQUIREMENT level. :param user_verification: The desired USER_VERIFICATION level. :param authenticator_attachment: The desired AUTHENTICATOR_ATTACHMENT or None to not provide a preference (and get both types). :param challenge: A custom challenge to sign and verify or None to use OS-specific random bytes. :return: Registration data, internal state.""" if not self.allowed_algorithms: raise ValueError("Server has no allowed algorithms.") challenge = _validata_challenge(challenge) descriptors = _wrap_credentials(credentials) state = self._make_internal_state(challenge, user_verification) logger.debug( "Starting new registration, existing credentials: " + ", ".join(d.id.hex() for d in descriptors or []) ) return ( CredentialCreationOptions( PublicKeyCredentialCreationOptions( self.rp, user, challenge, self.allowed_algorithms, self.timeout, descriptors, AuthenticatorSelectionCriteria( authenticator_attachment, resident_key_requirement, user_verification, ) if any( ( authenticator_attachment, resident_key_requirement, user_verification, ) ) else None, self.attestation, extensions, ) ), state, ) @overload def register_complete( self, state, response: Union[RegistrationResponse, Mapping[str, Any]], ) -> AuthenticatorData: pass @overload def register_complete( self, state, client_data: CollectedClientData, attestation_object: AttestationObject, ) -> AuthenticatorData: pass def register_complete(self, state, *args, **kwargs): """Verify the correctness of the registration data received from the client. :param state: The state data returned by the corresponding `register_begin`. :param client_data: The client data. :param attestation_object: The attestation object. :return: The authenticator data """ response = None if len(args) == 1 and not kwargs: response = args[0] elif set(kwargs) == {"response"} and not args: response = kwargs["response"] if response: registration = RegistrationResponse.from_dict(response) client_data = registration.response.client_data attestation_object = registration.response.attestation_object else: names = ["client_data", "attestation_object"] pos = dict(zip(names, args)) data = {**kwargs, **pos} if set(kwargs) & set(pos) or set(data) != set(names): raise TypeError("incorrect arguments passed to register_complete()") client_data = data[names[0]] attestation_object = data[names[1]] if client_data.type != CollectedClientData.TYPE.CREATE: raise ValueError("Incorrect type in CollectedClientData.") if not self._verify(client_data.origin): raise ValueError("Invalid origin in CollectedClientData.") if not constant_time.bytes_eq( websafe_decode(state["challenge"]), client_data.challenge ): raise ValueError("Wrong challenge in response.") if not constant_time.bytes_eq( self.rp.id_hash, attestation_object.auth_data.rp_id_hash ): raise ValueError("Wrong RP ID hash in response.") if not attestation_object.auth_data.is_user_present(): raise ValueError("User Present flag not set.") if ( state["user_verification"] == UserVerificationRequirement.REQUIRED and not attestation_object.auth_data.is_user_verified() ): raise ValueError( "User verification required, but User Verified flag not set." ) if self.attestation not in (None, AttestationConveyancePreference.NONE): logger.debug(f"Verifying attestation of type {attestation_object.fmt}") self._verify_attestation(attestation_object, client_data.hash) # We simply ignore attestation if self.attestation == 'none', as not all # clients strip the attestation. auth_data = attestation_object.auth_data assert auth_data.credential_data is not None # nosec logger.info( "New credential registered: " + auth_data.credential_data.credential_id.hex() ) return auth_data def authenticate_begin( self, credentials: Optional[ Sequence[Union[AttestedCredentialData, PublicKeyCredentialDescriptor]] ] = None, user_verification: Optional[UserVerificationRequirement] = None, challenge: Optional[bytes] = None, extensions=None, ) -> Tuple[CredentialRequestOptions, Any]: """Return a PublicKeyCredentialRequestOptions assertion object and the internal state dictionary that needs to be passed as is to the corresponding `authenticate_complete` call. :param credentials: The list of previously registered credentials, these can be of type AttestedCredentialData, or PublicKeyCredentialDescriptor. :param user_verification: The desired USER_VERIFICATION level. :param challenge: A custom challenge to sign and verify or None to use OS-specific random bytes. :return: Assertion data, internal state.""" challenge = _validata_challenge(challenge) descriptors = _wrap_credentials(credentials) state = self._make_internal_state(challenge, user_verification) if descriptors is None: logger.debug("Starting new authentication without credentials") else: logger.debug( "Starting new authentication, for credentials: " + ", ".join(d.id.hex() for d in descriptors) ) return ( CredentialRequestOptions( PublicKeyCredentialRequestOptions( challenge, self.timeout, self.rp.id, descriptors, user_verification, extensions, ) ), state, ) @overload def authenticate_complete( self, state, credentials: Sequence[AttestedCredentialData], response: Union[AuthenticationResponse, Mapping[str, Any]], ) -> AttestedCredentialData: pass @overload def authenticate_complete( self, state, credentials: Sequence[AttestedCredentialData], credential_id: bytes, client_data: CollectedClientData, auth_data: AuthenticatorData, signature: bytes, ) -> AttestedCredentialData: pass def authenticate_complete(self, state, credentials, *args, **kwargs): """Verify the correctness of the assertion data received from the client. :param state: The state data returned by the corresponding `register_begin`. :param credentials: The list of previously registered credentials. :param credential_id: The credential id from the client response. :param client_data: The client data. :param auth_data: The authenticator data. :param signature: The signature provided by the client.""" response = None if len(args) == 1 and not kwargs: response = args[0] elif set(kwargs) == {"response"} and not args: response = kwargs["response"] if response: authentication = AuthenticationResponse.from_dict(response) credential_id = authentication.id client_data = authentication.response.client_data auth_data = authentication.response.authenticator_data signature = authentication.response.signature else: names = ["credential_id", "client_data", "auth_data", "signature"] pos = dict(zip(names, args)) data = {**kwargs, **pos} if set(kwargs) & set(pos) or set(data) != set(names): raise TypeError("incorrect arguments passed to authenticate_complete()") credential_id = data[names[0]] client_data = data[names[1]] auth_data = data[names[2]] signature = data[names[3]] if client_data.type != CollectedClientData.TYPE.GET: raise ValueError("Incorrect type in CollectedClientData.") if not self._verify(client_data.origin): raise ValueError("Invalid origin in CollectedClientData.") if websafe_decode(state["challenge"]) != client_data.challenge: raise ValueError("Wrong challenge in response.") if not constant_time.bytes_eq(self.rp.id_hash, auth_data.rp_id_hash): raise ValueError("Wrong RP ID hash in response.") if not auth_data.is_user_present(): raise ValueError("User Present flag not set.") if ( state["user_verification"] == UserVerificationRequirement.REQUIRED and not auth_data.is_user_verified() ): raise ValueError( "User verification required, but user verified flag not set." ) for cred in credentials: if cred.credential_id == credential_id: try: cred.public_key.verify(auth_data + client_data.hash, signature) except _InvalidSignature: raise ValueError("Invalid signature.") logger.info(f"Credential authenticated: {credential_id.hex()}") return cred raise ValueError("Unknown credential ID.") @staticmethod def _make_internal_state( challenge: bytes, user_verification: Optional[UserVerificationRequirement] ): return { "challenge": websafe_encode(challenge), "user_verification": user_verification, } def verify_app_id(app_id: str, origin: str) -> bool: """Checks if a FIDO U2F App ID is usable for a given origin. :param app_id: The App ID to validate. :param origin: The origin of the request. :return: True if the App ID is usable by the origin, False if not. """ url = urlparse(app_id) if url.scheme != "https": return False hostname = url.hostname if not hostname: return False return verify_rp_id(hostname, origin) class U2FFido2Server(Fido2Server): """Fido2Server which can be used with existing U2F credentials. This Fido2Server can be used with existing U2F credentials by using the WebAuthn appid extension, as well as with new WebAuthn credentials. See https://www.w3.org/TR/webauthn/#sctn-appid-extension for details. :param app_id: The appId which was used for U2F registration. :param verify_u2f_origin: (optional) Alternative function to validate an origin for U2F credentials. For other parameters, see Fido2Server. """ def __init__( self, app_id: str, rp: PublicKeyCredentialRpEntity, verify_u2f_origin: Optional[VerifyOrigin] = None, *args, **kwargs, ): super().__init__(rp, *args, **kwargs) if verify_u2f_origin: kwargs["verify_origin"] = verify_u2f_origin else: kwargs["verify_origin"] = lambda o: verify_app_id(app_id, o) self._app_id = app_id self._app_id_server = Fido2Server( replace(PublicKeyCredentialRpEntity.from_dict(rp), id=app_id), *args, **kwargs, ) def register_begin(self, *args, **kwargs): kwargs.setdefault("extensions", {})["appidExclude"] = self._app_id req, state = super().register_begin(*args, **kwargs) return req, state def authenticate_begin(self, *args, **kwargs): kwargs.setdefault("extensions", {})["appid"] = self._app_id req, state = super().authenticate_begin(*args, **kwargs) return req, state def authenticate_complete(self, *args, **kwargs): try: return super().authenticate_complete(*args, **kwargs) except ValueError: return self._app_id_server.authenticate_complete(*args, **kwargs) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1688650182.4458866 fido2-1.1.2/fido2/utils.py0000644000000000000000000002111714451540706012171 0ustar00# Copyright (c) 2013 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. """Various utility functions. This module contains various functions used throughout the rest of the project. """ from __future__ import annotations from base64 import urlsafe_b64decode, urlsafe_b64encode from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hmac, hashes from io import BytesIO from dataclasses import fields, Field from abc import abstractmethod from typing import ( Union, Optional, Sequence, Mapping, Any, TypeVar, Hashable, get_type_hints, ) import struct __all__ = [ "websafe_encode", "websafe_decode", "sha256", "hmac_sha256", "bytes2int", "int2bytes", ] LOG_LEVEL_TRAFFIC = 5 def sha256(data: bytes) -> bytes: """Produces a SHA256 hash of the input. :param data: The input data to hash. :return: The resulting hash. """ h = hashes.Hash(hashes.SHA256(), default_backend()) h.update(data) return h.finalize() def hmac_sha256(key: bytes, data: bytes) -> bytes: """Performs an HMAC-SHA256 operation on the given data, using the given key. :param key: The key to use. :param data: The input data to hash. :return: The resulting hash. """ h = hmac.HMAC(key, hashes.SHA256(), default_backend()) h.update(data) return h.finalize() def bytes2int(value: bytes) -> int: """Parses an arbitrarily sized integer from a byte string. :param value: A byte string encoding a big endian unsigned integer. :return: The parsed int. """ return int.from_bytes(value, "big") def int2bytes(value: int, minlen: int = -1) -> bytes: """Encodes an int as a byte string. :param value: The integer value to encode. :param minlen: An optional minimum length for the resulting byte string. :return: The value encoded as a big endian byte string. """ ba = [] while value > 0xFF: ba.append(0xFF & value) value >>= 8 ba.append(value) ba.extend([0] * (minlen - len(ba))) return bytes(reversed(ba)) def websafe_decode(data: Union[str, bytes]) -> bytes: """Decodes a websafe-base64 encoded string. See: "Base 64 Encoding with URL and Filename Safe Alphabet" from Section 5 in RFC4648 without padding. :param data: The input to decode. :return: The decoded bytes. """ if isinstance(data, str): data = data.encode("ascii") data += b"=" * (-len(data) % 4) return urlsafe_b64decode(data) def websafe_encode(data: bytes) -> str: """Encodes a byte string into websafe-base64 encoding. :param data: The input to encode. :return: The encoded string. """ return urlsafe_b64encode(data).replace(b"=", b"").decode("ascii") class ByteBuffer(BytesIO): """BytesIO-like object with the ability to unpack values.""" def unpack(self, fmt: str): """Reads and unpacks a value from the buffer. :param fmt: A struct format string yielding a single value. :return: The unpacked value. """ s = struct.Struct(fmt) return s.unpack(self.read(s.size))[0] def read(self, size: Optional[int] = -1) -> bytes: """Like BytesIO.read(), but checks the number of bytes read and raises an error if fewer bytes were read than expected. """ data = super().read(size) if size is not None and size > 0 and len(data) != size: raise ValueError( "Not enough data to read (need: %d, had: %d)." % (size, len(data)) ) return data def _snake2camel(name: str) -> str: parts = name.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) def _parse_value(t, value): if value is None: return None if Optional[t] == t: # Optional, get the type t = t.__args__[0] # Handle list of values if issubclass(getattr(t, "__origin__", object), Sequence): t = t.__args__[0] return [_parse_value(t, v) for v in value] # Handle Mappings if issubclass(getattr(t, "__origin__", object), Mapping) and isinstance( value, Mapping ): return value # Check if type is already correct try: if isinstance(value, t): return value except TypeError: pass # Check for subclass of _DataClassMapping try: is_dataclass = issubclass(t, _DataClassMapping) except TypeError: is_dataclass = False if is_dataclass: # Recursively call from_dict for nested _DataClassMappings return t.from_dict(value) # Convert to enum values, other wrappers return t(value) _T = TypeVar("_T", bound=Hashable) class _DataClassMapping(Mapping[_T, Any]): # TODO: This requires Python 3.9, and fixes the tpye errors we now ignore # __dataclass_fields__: ClassVar[Dict[str, Field[Any]]] def __post_init__(self): hints = get_type_hints(type(self)) for f in fields(self): # type: ignore value = getattr(self, f.name) if value is None: continue try: value = _parse_value(hints[f.name], value) except (TypeError, KeyError, ValueError): raise ValueError( f"Error parsing field {f.name} for {self.__class__.__name__}" ) object.__setattr__(self, f.name, value) @classmethod @abstractmethod def _get_field_key(cls, field: Field) -> _T: raise NotImplementedError() def __getitem__(self, key): for f in fields(self): # type: ignore if key == self._get_field_key(f): value = getattr(self, f.name) serialize = f.metadata.get("serialize") if serialize: return serialize(value) if isinstance(value, _DataClassMapping): return dict(value) if isinstance(value, Sequence) and all( isinstance(x, _DataClassMapping) for x in value ): return [dict(x) for x in value] return value raise KeyError(key) def __iter__(self): return ( self._get_field_key(f) for f in fields(self) # type: ignore if getattr(self, f.name) is not None ) def __len__(self): return len(list(iter(self))) @classmethod def from_dict(cls, data: Optional[Mapping[_T, Any]]): if data is None: return None if isinstance(data, cls): return data if not isinstance(data, Mapping): raise TypeError( f"{cls.__name__}.from_dict called with non-Mapping data of type" f"{type(data)}" ) kwargs = {} for f in fields(cls): # type: ignore key = cls._get_field_key(f) if key in data: value = data[key] if value is not None: deserialize = f.metadata.get("deserialize") if deserialize: value = deserialize(value) kwargs[f.name] = value return cls(**kwargs) class _CamelCaseDataObject(_DataClassMapping[str]): @classmethod def _get_field_key(cls, field: Field) -> str: return field.metadata.get("name", _snake2camel(field.name)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/fido2/webauthn.py0000644000000000000000000005257214413232070012645 0ustar00# 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 annotations from . import cbor from .cose import CoseKey, ES256 from .utils import ( sha256, websafe_decode, websafe_encode, ByteBuffer, _CamelCaseDataObject, ) from .features import webauthn_json_mapping from enum import Enum, EnumMeta, unique, IntFlag from dataclasses import dataclass, field from typing import Any, Mapping, Optional, Sequence, Tuple, Union, cast import struct import json """ Data classes based on the W3C WebAuthn specification (https://www.w3.org/TR/webauthn/). See the specification for a description and details on their usage. """ # Binary types class Aaguid(bytes): def __init__(self, data): if len(data) != 16: raise ValueError("AAGUID must be 16 bytes") def __bool__(self): return self != Aaguid.NONE def __str__(self): h = self.hex() return f"{h[:8]}-{h[8:12]}-{h[12:16]}-{h[16:20]}-{h[20:]}" def __repr__(self): return f"AAGUID({str(self)})" @classmethod def parse(cls, value: str) -> Aaguid: return cls.fromhex(value.replace("-", "")) NONE: Aaguid # Special instance of AAGUID used when there is no AAGUID Aaguid.NONE = Aaguid(b"\0" * 16) @dataclass(init=False, frozen=True) class AttestedCredentialData(bytes): aaguid: Aaguid credential_id: bytes public_key: CoseKey def __init__(self, _): super().__init__() parsed = AttestedCredentialData._parse(self) object.__setattr__(self, "aaguid", parsed[0]) object.__setattr__(self, "credential_id", parsed[1]) object.__setattr__(self, "public_key", parsed[2]) if parsed[3]: raise ValueError("Wrong length") def __str__(self): # Override default implementation from bytes. return repr(self) @staticmethod def _parse(data: bytes) -> Tuple[bytes, bytes, CoseKey, bytes]: """Parse the components of an AttestedCredentialData from a binary string, and return them. :param data: A binary string containing an attested credential data. :return: AAGUID, credential ID, public key, and remaining data. """ reader = ByteBuffer(data) aaguid = Aaguid(reader.read(16)) cred_id = reader.read(reader.unpack(">H")) pub_key, rest = cbor.decode_from(reader.read()) return aaguid, cred_id, CoseKey.parse(pub_key), rest @classmethod def create( cls, aaguid: bytes, credential_id: bytes, public_key: CoseKey ) -> AttestedCredentialData: """Create an AttestedCredentialData by providing its components. :param aaguid: The AAGUID of the authenticator. :param credential_id: The binary ID of the credential. :param public_key: A COSE formatted public key. :return: The attested credential data. """ return cls( aaguid + struct.pack(">H", len(credential_id)) + credential_id + cbor.encode(public_key) ) @classmethod def unpack_from(cls, data: bytes) -> Tuple[AttestedCredentialData, bytes]: """Unpack an AttestedCredentialData from a byte string, returning it and any remaining data. :param data: A binary string containing an attested credential data. :return: The parsed AttestedCredentialData, and any remaining data from the input. """ aaguid, cred_id, pub_key, rest = cls._parse(data) return cls.create(aaguid, cred_id, pub_key), rest @classmethod def from_ctap1(cls, key_handle: bytes, public_key: bytes) -> AttestedCredentialData: """Create an AttestatedCredentialData from a CTAP1 RegistrationData instance. :param key_handle: The CTAP1 credential key_handle. :type key_handle: bytes :param public_key: The CTAP1 65 byte public key. :type public_key: bytes :return: The credential data, using an all-zero AAGUID. :rtype: AttestedCredentialData """ return cls.create(Aaguid.NONE, key_handle, ES256.from_ctap1(public_key)) @dataclass(init=False, frozen=True) class AuthenticatorData(bytes): """Binary encoding of the authenticator data. :param _: The binary representation of the authenticator data. :ivar rp_id_hash: SHA256 hash of the RP ID. :ivar flags: The flags of the authenticator data, see AuthenticatorData.FLAG. :ivar counter: The signature counter of the authenticator. :ivar credential_data: Attested credential data, if available. :ivar extensions: Authenticator extensions, if available. """ class FLAG(IntFlag): """Authenticator data flags See https://www.w3.org/TR/webauthn/#sec-authenticator-data for details """ # Names used in WebAuthn UP = 0x01 UV = 0x04 BE = 0x08 BS = 0x10 AT = 0x40 ED = 0x80 # Aliases (for historical purposes) USER_PRESENT = 0x01 USER_VERIFIED = 0x04 BACKUP_ELIGIBILITY = 0x08 BACKUP_STATE = 0x10 ATTESTED = 0x40 EXTENSION_DATA = 0x80 rp_id_hash: bytes flags: AuthenticatorData.FLAG counter: int credential_data: Optional[AttestedCredentialData] extensions: Optional[Mapping] def __init__(self, _): super().__init__() reader = ByteBuffer(self) object.__setattr__(self, "rp_id_hash", reader.read(32)) object.__setattr__(self, "flags", reader.unpack("B")) object.__setattr__(self, "counter", reader.unpack(">I")) rest = reader.read() if self.flags & AuthenticatorData.FLAG.AT: credential_data, rest = AttestedCredentialData.unpack_from(rest) else: credential_data = None object.__setattr__(self, "credential_data", credential_data) if self.flags & AuthenticatorData.FLAG.ED: extensions, rest = cbor.decode_from(rest) else: extensions = None object.__setattr__(self, "extensions", extensions) if rest: raise ValueError("Wrong length") def __str__(self): # Override default implementation from bytes. return repr(self) @classmethod def create( cls, rp_id_hash: bytes, flags: AuthenticatorData.FLAG, counter: int, credential_data: bytes = b"", extensions: Optional[Mapping] = None, ): """Create an AuthenticatorData instance. :param rp_id_hash: SHA256 hash of the RP ID. :param flags: Flags of the AuthenticatorData. :param counter: Signature counter of the authenticator data. :param credential_data: Authenticated credential data (only if attested credential data flag is set). :param extensions: Authenticator extensions (only if ED flag is set). :return: The authenticator data. """ return cls( rp_id_hash + struct.pack(">BI", flags, counter) + credential_data + (cbor.encode(extensions) if extensions is not None else b"") ) def is_user_present(self) -> bool: """Return true if the User Present flag is set.""" return bool(self.flags & AuthenticatorData.FLAG.UP) def is_user_verified(self) -> bool: """Return true if the User Verified flag is set.""" return bool(self.flags & AuthenticatorData.FLAG.UV) def is_backup_eligible(self) -> bool: """Return true if the Backup Eligibility flag is set.""" return bool(self.flags & AuthenticatorData.FLAG.BE) def is_backed_up(self) -> bool: """Return true if the Backup State flag is set.""" return bool(self.flags & AuthenticatorData.FLAG.BS) def is_attested(self) -> bool: """Return true if the Attested credential data flag is set.""" return bool(self.flags & AuthenticatorData.FLAG.AT) def has_extension_data(self) -> bool: """Return true if the Extenstion data flag is set.""" return bool(self.flags & AuthenticatorData.FLAG.ED) @dataclass(init=False, frozen=True) class AttestationObject(bytes): # , Mapping[str, Any]): """Binary CBOR encoded attestation object. :param _: The binary representation of the attestation object. :ivar fmt: The type of attestation used. :ivar auth_data: The attested authenticator data. :ivar att_statement: The attestation statement. """ fmt: str auth_data: AuthenticatorData att_stmt: Mapping[str, Any] def __init__(self, _): super().__init__() data = cast(Mapping[str, Any], cbor.decode(bytes(self))) object.__setattr__(self, "fmt", data["fmt"]) object.__setattr__(self, "auth_data", AuthenticatorData(data["authData"])) object.__setattr__(self, "att_stmt", data["attStmt"]) def __str__(self): # Override default implementation from bytes. return repr(self) @classmethod def create( cls, fmt: str, auth_data: AuthenticatorData, att_stmt: Mapping[str, Any] ) -> AttestationObject: return cls( cbor.encode({"fmt": fmt, "authData": auth_data, "attStmt": att_stmt}) ) @classmethod def from_ctap1(cls, app_param: bytes, registration) -> AttestationObject: """Create an AttestationObject from a CTAP1 RegistrationData instance. :param app_param: SHA256 hash of the RP ID used for the CTAP1 request. :type app_param: bytes :param registration: The CTAP1 registration data. :type registration: RegistrationData :return: The attestation object, using the "fido-u2f" format. :rtype: AttestationObject """ return cls.create( "fido-u2f", AuthenticatorData.create( app_param, AuthenticatorData.FLAG.AT | AuthenticatorData.FLAG.UP, 0, AttestedCredentialData.from_ctap1( registration.key_handle, registration.public_key ), ), {"x5c": [registration.certificate], "sig": registration.signature}, ) @dataclass(init=False, frozen=True) class CollectedClientData(bytes): @unique class TYPE(str, Enum): CREATE = "webauthn.create" GET = "webauthn.get" type: str challenge: bytes origin: str cross_origin: bool = False def __init__(self, *args): super().__init__() data = json.loads(self.decode()) object.__setattr__(self, "type", data["type"]) object.__setattr__(self, "challenge", websafe_decode(data["challenge"])) object.__setattr__(self, "origin", data["origin"]) object.__setattr__(self, "cross_origin", data.get("crossOrigin", False)) @classmethod def create( cls, type: str, challenge: Union[bytes, str], origin: str, cross_origin: bool = False, **kwargs, ) -> CollectedClientData: if isinstance(challenge, bytes): encoded_challenge = websafe_encode(challenge) else: encoded_challenge = challenge return cls( json.dumps( { "type": type, "challenge": encoded_challenge, "origin": origin, "crossOrigin": cross_origin, **kwargs, }, separators=(",", ":"), ).encode() ) def __str__(self): # Override default implementation from bytes. return repr(self) @property def b64(self) -> str: return websafe_encode(self) @property def hash(self) -> bytes: return sha256(self) class _StringEnumMeta(EnumMeta): def _get_value(cls, value): return None def __call__(cls, value, *args, **kwargs): try: return super().__call__(value, *args, **kwargs) except ValueError: return cls._get_value(value) class _StringEnum(str, Enum, metaclass=_StringEnumMeta): """Enum of strings for WebAuthn types. Unrecognized values are treated as missing. """ _b64_metadata = dict( serialize=lambda x: websafe_encode(x) if webauthn_json_mapping.enabled else x, deserialize=lambda x: websafe_decode(x) if webauthn_json_mapping.enabled else x, ) @unique class AttestationConveyancePreference(_StringEnum): NONE = "none" INDIRECT = "indirect" DIRECT = "direct" ENTERPRISE = "enterprise" @unique class UserVerificationRequirement(_StringEnum): REQUIRED = "required" PREFERRED = "preferred" DISCOURAGED = "discouraged" @unique class ResidentKeyRequirement(_StringEnum): REQUIRED = "required" PREFERRED = "preferred" DISCOURAGED = "discouraged" @unique class AuthenticatorAttachment(_StringEnum): PLATFORM = "platform" CROSS_PLATFORM = "cross-platform" @unique class AuthenticatorTransport(_StringEnum): USB = "usb" NFC = "nfc" BLE = "ble" HYBRID = "hybrid" INTERNAL = "internal" @unique class PublicKeyCredentialType(_StringEnum): PUBLIC_KEY = "public-key" @dataclass(eq=False, frozen=True) class PublicKeyCredentialRpEntity(_CamelCaseDataObject): name: str id: Optional[str] = None @property def id_hash(self) -> Optional[bytes]: """Return SHA256 hash of the identifier.""" return sha256(self.id.encode("utf8")) if self.id else None @dataclass(eq=False, frozen=True) class PublicKeyCredentialUserEntity(_CamelCaseDataObject): name: str id: bytes = field(metadata=_b64_metadata) display_name: Optional[str] = None @dataclass(eq=False, frozen=True) class PublicKeyCredentialParameters(_CamelCaseDataObject): type: PublicKeyCredentialType alg: int @classmethod def _deserialize_list(cls, value): if value is None: return None items = [cls.from_dict(e) for e in value] return [e for e in items if e.type is not None] @dataclass(eq=False, frozen=True) class PublicKeyCredentialDescriptor(_CamelCaseDataObject): type: PublicKeyCredentialType id: bytes = field(metadata=_b64_metadata) transports: Optional[Sequence[AuthenticatorTransport]] = None @classmethod def _deserialize_list(cls, value): if value is None: return None items = [cls.from_dict(e) for e in value] return [e for e in items if e.type is not None] @dataclass(eq=False, frozen=True) class AuthenticatorSelectionCriteria(_CamelCaseDataObject): authenticator_attachment: Optional[AuthenticatorAttachment] = None resident_key: Optional[ResidentKeyRequirement] = None user_verification: Optional[UserVerificationRequirement] = None require_resident_key: Optional[bool] = False def __post_init__(self): super().__post_init__() if self.resident_key is None: object.__setattr__( self, "resident_key", ResidentKeyRequirement.REQUIRED if self.require_resident_key else ResidentKeyRequirement.DISCOURAGED, ) object.__setattr__( self, "require_resident_key", self.resident_key == ResidentKeyRequirement.REQUIRED, ) @dataclass(eq=False, frozen=True) class PublicKeyCredentialCreationOptions(_CamelCaseDataObject): rp: PublicKeyCredentialRpEntity user: PublicKeyCredentialUserEntity challenge: bytes = field(metadata=_b64_metadata) pub_key_cred_params: Sequence[PublicKeyCredentialParameters] = field( metadata=dict(deserialize=PublicKeyCredentialParameters._deserialize_list), ) timeout: Optional[int] = None exclude_credentials: Optional[Sequence[PublicKeyCredentialDescriptor]] = field( default=None, metadata=dict(deserialize=PublicKeyCredentialDescriptor._deserialize_list), ) authenticator_selection: Optional[AuthenticatorSelectionCriteria] = None attestation: Optional[AttestationConveyancePreference] = None extensions: Optional[Mapping[str, Any]] = None @dataclass(eq=False, frozen=True) class PublicKeyCredentialRequestOptions(_CamelCaseDataObject): challenge: bytes = field(metadata=_b64_metadata) timeout: Optional[int] = None rp_id: Optional[str] = None allow_credentials: Optional[Sequence[PublicKeyCredentialDescriptor]] = field( default=None, metadata=dict(deserialize=PublicKeyCredentialDescriptor._deserialize_list), ) user_verification: Optional[UserVerificationRequirement] = None extensions: Optional[Mapping[str, Any]] = None @dataclass(eq=False, frozen=True) class AuthenticatorAttestationResponse(_CamelCaseDataObject): client_data: CollectedClientData = field( metadata=dict( _b64_metadata, name="clientDataJSON", ) ) attestation_object: AttestationObject = field(metadata=_b64_metadata) extension_results: Optional[Mapping[str, Any]] = None def __getitem__(self, key): if key == "clientData" and not webauthn_json_mapping.enabled: return self.client_data return super().__getitem__(key) @classmethod def from_dict(cls, data: Optional[Mapping[str, Any]]): if data is not None and not webauthn_json_mapping.enabled: value = dict(data) value["clientDataJSON"] = value.pop("clientData", None) data = value return super().from_dict(data) @dataclass(eq=False, frozen=True) class AuthenticatorAssertionResponse(_CamelCaseDataObject): client_data: CollectedClientData = field( metadata=dict( _b64_metadata, name="clientDataJSON", ) ) authenticator_data: AuthenticatorData = field(metadata=_b64_metadata) signature: bytes = field(metadata=_b64_metadata) user_handle: Optional[bytes] = field(metadata=_b64_metadata, default=None) credential_id: Optional[bytes] = field(metadata=_b64_metadata, default=None) extension_results: Optional[Mapping[str, Any]] = None def __getitem__(self, key): if key == "clientData" and not webauthn_json_mapping.enabled: return self.client_data return super().__getitem__(key) @classmethod def from_dict(cls, data: Optional[Mapping[str, Any]]): if data is not None and not webauthn_json_mapping.enabled: value = dict(data) value["clientDataJSON"] = value.pop("clientData", None) data = value return super().from_dict(data) @dataclass(eq=False, frozen=True) class RegistrationResponse(_CamelCaseDataObject): id: bytes = field(metadata=_b64_metadata) response: AuthenticatorAttestationResponse authenticator_attachment: Optional[AuthenticatorAttachment] = None client_extension_results: Optional[Mapping] = None type: Optional[PublicKeyCredentialType] = None def __post_init__(self): webauthn_json_mapping.require() super().__post_init__() @dataclass(eq=False, frozen=True) class AuthenticationResponse(_CamelCaseDataObject): id: bytes = field(metadata=_b64_metadata) response: AuthenticatorAssertionResponse authenticator_attachment: Optional[AuthenticatorAttachment] = None client_extension_results: Optional[Mapping] = None type: Optional[PublicKeyCredentialType] = None def __post_init__(self): webauthn_json_mapping.require() super().__post_init__() @dataclass(eq=False, frozen=True) class CredentialCreationOptions(_CamelCaseDataObject): public_key: PublicKeyCredentialCreationOptions @dataclass(eq=False, frozen=True) class CredentialRequestOptions(_CamelCaseDataObject): public_key: PublicKeyCredentialRequestOptions ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/fido2/win_api.py0000644000000000000000000007035514413232070012455 0ustar00# Copyright (c) 2019 Onica Group LLC. # Modified work Copyright 2019 Yubico. # 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. """ Structs based on Microsoft's WebAuthN API. https://github.com/microsoft/webauthn """ # With the ctypes.Structure a lot of the property names # will be invalid, and when creating the __init__ methods # we do not need to call super() for the Structure class # # pylint: disable=invalid-name, super-init-not-called, too-few-public-methods from __future__ import annotations from enum import IntEnum, unique from ctypes.wintypes import BOOL, DWORD, LONG, LPCWSTR, HWND from threading import Thread from typing import Mapping import ctypes from ctypes import WinDLL # type: ignore from ctypes import LibraryLoader windll = LibraryLoader(WinDLL) PBYTE = ctypes.POINTER(ctypes.c_ubyte) # Different from wintypes.PBYTE, which is signed PCWSTR = ctypes.c_wchar_p class BytesProperty: """Property for structs storing byte arrays as DWORD + PBYTE. Allows for easy reading/writing to struct fields using Python bytes objects. """ def __init__(self, name): self.cbName = "cb" + name self.pbName = "pb" + name def __get__(self, instance, owner): return bytes( bytearray(getattr(instance, self.pbName)[: getattr(instance, self.cbName)]) ) def __set__(self, instance, value): setattr(instance, self.cbName, len(value)) setattr(instance, self.pbName, ctypes.cast(value, PBYTE)) class GUID(ctypes.Structure): """GUID Type in C++.""" _fields_ = [ ("Data1", ctypes.c_ulong), ("Data2", ctypes.c_ushort), ("Data3", ctypes.c_ushort), ("Data4", ctypes.c_ubyte * 8), ] def __str__(self): return "{%08X-%04X-%04X-%04X-%012X}" % ( self.Data1, self.Data2, self.Data3, self.Data4[0] * 256 + self.Data4[1], self.Data4[2] * (256**5) + self.Data4[3] * (256**4) + self.Data4[4] * (256**3) + self.Data4[5] * (256**2) + self.Data4[6] * 256 + self.Data4[7], ) class WebAuthNCoseCredentialParameter(ctypes.Structure): """Maps to WEBAUTHN_COSE_CREDENTIAL_PARAMETER Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L185 :param Dict[str, Any] cred_params: Dict of Credential parameters. """ _fields_ = [("dwVersion", DWORD), ("pwszCredentialType", LPCWSTR), ("lAlg", LONG)] def __init__(self, cred_params): self.dwVersion = get_version(self.__class__.__name__) self.pwszCredentialType = cred_params["type"] self.lAlg = cred_params["alg"] class WebAuthNCoseCredentialParameters(ctypes.Structure): """Maps to WEBAUTHN_COSE_CREDENTIAL_PARAMETERS Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L191 :param List[Dict[str, Any]] params: List of Credential parameter dicts. """ _fields_ = [ ("cCredentialParameters", DWORD), ("pCredentialParameters", ctypes.POINTER(WebAuthNCoseCredentialParameter)), ] def __init__(self, params): self.cCredentialParameters = len(params) self.pCredentialParameters = (WebAuthNCoseCredentialParameter * len(params))( *(WebAuthNCoseCredentialParameter(param) for param in params) ) class WebAuthNClientData(ctypes.Structure): """Maps to WEBAUTHN_CLIENT_DATA Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L153 :param bytes client_data: ClientData serialized as JSON bytes. """ _fields_ = [ ("dwVersion", DWORD), ("cbClientDataJSON", DWORD), ("pbClientDataJSON", PBYTE), ("pwszHashAlgId", LPCWSTR), ] json = BytesProperty("ClientDataJSON") def __init__(self, client_data): self.dwVersion = get_version(self.__class__.__name__) self.json = client_data self.pwszHashAlgId = "SHA-256" class WebAuthNRpEntityInformation(ctypes.Structure): """Maps to WEBAUTHN_RP_ENTITY_INFORMATION Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L98 :param Dict[str, Any] rp: Dict of RP information. """ _fields_ = [ ("dwVersion", DWORD), ("pwszId", PCWSTR), ("pwszName", PCWSTR), ("pwszIcon", PCWSTR), ] def __init__(self, rp): self.dwVersion = get_version(self.__class__.__name__) self.pwszId = rp["id"] self.pwszName = rp["name"] self.pwszIcon = rp.get("icon") class WebAuthNUserEntityInformation(ctypes.Structure): """Maps to WEBAUTHN_USER_ENTITY_INFORMATION Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L127 :param Dict[str, Any] user: Dict of User information. """ _fields_ = [ ("dwVersion", DWORD), ("cbId", DWORD), ("pbId", PBYTE), ("pwszName", PCWSTR), ("pwszIcon", PCWSTR), ("pwszDisplayName", PCWSTR), ] id = BytesProperty("Id") def __init__(self, user): self.dwVersion = get_version(self.__class__.__name__) self.id = user["id"] self.pwszName = user["name"] self.pwszIcon = user.get("icon") self.pwszDisplayName = user.get("displayName") class WebAuthNCredentialEx(ctypes.Structure): """Maps to WEBAUTHN_CREDENTIAL_EX Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L250 :param Dict[str, Any] cred: Dict of Credential Descriptor data. """ _fields_ = [ ("dwVersion", DWORD), ("cbId", DWORD), ("pbId", PBYTE), ("pwszCredentialType", LPCWSTR), ("dwTransports", DWORD), ] id = BytesProperty("Id") def __init__(self, cred): self.dwVersion = get_version(self.__class__.__name__) self.id = cred["id"] self.pwszCredentialType = cred["type"] self.dwTransports = WebAuthNCTAPTransport[cred.get("transport", "ANY")] class WebAuthNCredentialList(ctypes.Structure): """Maps to WEBAUTHN_CREDENTIAL_LIST Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L261 :param List[Dict[str, Any]] credentials: List of dict of Credential Descriptor data. """ _fields_ = [ ("cCredentials", DWORD), ("ppCredentials", ctypes.POINTER(ctypes.POINTER(WebAuthNCredentialEx))), ] def __init__(self, credentials): self.cCredentials = len(credentials) self.ppCredentials = (ctypes.POINTER(WebAuthNCredentialEx) * len(credentials))( *(ctypes.pointer(WebAuthNCredentialEx(cred)) for cred in credentials) ) class WebAuthNExtension(ctypes.Structure): """Maps to WEBAUTHN_EXTENSION Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L317 """ _fields_ = [ ("pwszExtensionIdentifier", LPCWSTR), ("cbExtension", DWORD), ("pvExtension", PBYTE), ] class WebAuthNExtensions(ctypes.Structure): """Maps to WEBAUTHN_EXTENSIONS Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L324 """ _fields_ = [ ("cExtensions", DWORD), ("pExtensions", ctypes.POINTER(WebAuthNExtension)), ] class WebAuthNCredential(ctypes.Structure): """Maps to WEBAUTHN_CREDENTIAL Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L212 :param Dict[str, Any] cred: Dict of Credential Descriptor data. """ _fields_ = [ ("dwVersion", DWORD), ("cbId", DWORD), ("pbId", PBYTE), ("pwszCredentialType", LPCWSTR), ] id = BytesProperty("Id") def __init__(self, cred): self.id = cred["id"] self.pwszCredentialType = cred["type"] @property def descriptor(self): return {"type": self.pwszCredentialType, "id": self.id} class WebAuthNCredentials(ctypes.Structure): """Maps to WEBAUTHN_CREDENTIALS Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L219 :param List[Dict[str, Any]] credentials: List of dict of Credential Descriptor data. """ _fields_ = [ ("cCredentials", DWORD), ("pCredentials", ctypes.POINTER(WebAuthNCredential)), ] def __init__(self, credentials): self.cCredentials = len(credentials) self.pCredentials = (WebAuthNCredential * len(credentials))( *(WebAuthNCredential(cred) for cred in credentials) ) class WebAuthNGetAssertionOptions(ctypes.Structure): """Maps to WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L452 :param int timeout: Time that the operation is expected to complete within. This is used as guidance, and can be overridden by the platform. :param WebAuthNAuthenticatorAttachment attachment: Platform vs Cross-Platform Authenticators. :param WebAuthNUserVerificationRequirement user_verification_requirement: User Verification Requirement. :param List[Dict[str,Any]] credentials: Allowed Credentials List. """ _fields_ = [ ("dwVersion", DWORD), ("dwTimeoutMilliseconds", DWORD), ("CredentialList", WebAuthNCredentials), ("Extensions", WebAuthNExtensions), ("dwAuthenticatorAttachment", DWORD), ("dwUserVerificationRequirement", DWORD), ("dwFlags", DWORD), ("pwszU2fAppId", PCWSTR), ("pbU2fAppId", BOOL), ("pCancellationId", ctypes.POINTER(GUID)), ("pAllowCredentialList", ctypes.POINTER(WebAuthNCredentialList)), ("dwCredLargeBlobOperation", DWORD), ("cbCredLargeBlob", DWORD), ("pbCredLargeBlob", PBYTE), ] cred_large_blob = BytesProperty("CredLargeBlob") def __init__( self, timeout, attachment, user_verification_requirement, credentials, cancellationId, cred_large_blob_operation, cred_large_blob, ): self.dwVersion = get_version(self.__class__.__name__) self.dwTimeoutMilliseconds = timeout self.dwAuthenticatorAttachment = attachment self.dwUserVerificationRequirement = user_verification_requirement if self.dwVersion >= 3: self.pCancellationId = cancellationId if self.dwVersion >= 4: clist = WebAuthNCredentialList(credentials) self.pAllowCredentialList = ctypes.pointer(clist) else: self.CredentialList = WebAuthNCredentials(credentials) if self.dwVersion >= 5: self.cred_large_blob_operation = cred_large_blob_operation self.cred_large_blob = cred_large_blob class WebAuthNAssertion(ctypes.Structure): """Maps to WEBAUTHN_ASSERTION Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L616 """ _fields_ = [ ("dwVersion", DWORD), ("cbAuthenticatorData", DWORD), ("pbAuthenticatorData", PBYTE), ("cbSignature", DWORD), ("pbSignature", PBYTE), ("Credential", WebAuthNCredential), ("cbUserId", DWORD), ("pbUserId", PBYTE), ("Extensions", WebAuthNExtensions), ("cbCredLargeBlob", DWORD), ("pbCredLargeBlob", PBYTE), ("dwCredLargeBlobStatus", DWORD), ] auth_data = BytesProperty("AuthenticatorData") signature = BytesProperty("Signature") user_id = BytesProperty("UserId") cred_large_blob = BytesProperty("CredLargeBlob") def __del__(self): WEBAUTHN.WebAuthNFreeAssertion(ctypes.byref(self)) class WebAuthNMakeCredentialOptions(ctypes.Structure): """maps to WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L394 :param int timeout: Time that the operation is expected to complete within.This is used as guidance, and can be overridden by the platform. :param bool require_resident_key: Require key to be resident or not. :param WebAuthNAuthenticatorAttachment attachment: Platform vs Cross-Platform Authenticators. :param WebAuthNUserVerificationRequirement user_verification_requirement: User Verification Requirement. :param WebAuthNAttestationConvoyancePreference attestation_convoyence: Attestation Conveyance Preference. :param List[Dict[str,Any]] credentials: Credentials used for exclusion. """ _fields_ = [ ("dwVersion", DWORD), ("dwTimeoutMilliseconds", DWORD), ("CredentialList", WebAuthNCredentials), ("Extensions", WebAuthNExtensions), ("dwAuthenticatorAttachment", DWORD), ("bRequireResidentKey", BOOL), ("dwUserVerificationRequirement", DWORD), ("dwAttestationConveyancePreference", DWORD), ("dwFlags", DWORD), ("pCancellationId", ctypes.POINTER(GUID)), ("pExcludeCredentialList", ctypes.POINTER(WebAuthNCredentialList)), ("dwEnterpriseAttestation", DWORD), ("dwLargeBlobSupport", DWORD), ("bPreferResidentKey", BOOL), ] def __init__( self, timeout, require_resident_key, attachment, user_verification_requirement, attestation_convoyence, credentials, cancellationId, enterprise_attestation, large_blob_support, prefer_resident_key, ): self.dwVersion = get_version(self.__class__.__name__) self.dwTimeoutMilliseconds = timeout self.bRequireResidentKey = require_resident_key self.dwAuthenticatorAttachment = attachment self.dwUserVerificationRequirement = user_verification_requirement self.dwAttestationConveyancePreference = attestation_convoyence if self.dwVersion >= 2: self.pCancellationId = cancellationId if self.dwVersion >= 3: self.pExcludeCredentialList = ctypes.pointer( WebAuthNCredentialList(credentials) ) else: self.CredentialList = WebAuthNCredentials(credentials) if self.dwVersion >= 4: self.dwEnterpriseAttestation = enterprise_attestation self.dwLargeBlobSupport = large_blob_support self.bPreferResidentKey = prefer_resident_key class WebAuthNCredentialAttestation(ctypes.Structure): """Maps to WEBAUTHN_CREDENTIAL_ATTESTATION Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L582 """ _fields_ = [ ("dwVersion", DWORD), ("pwszFormatType", LPCWSTR), ("cbAuthenticatorData", DWORD), ("pbAuthenticatorData", PBYTE), ("cbAttestation", DWORD), ("pbAttestation", PBYTE), ("dwAttestationDecodeType", DWORD), ("pvAttestationDecode", PBYTE), ("cbAttestationObject", DWORD), ("pbAttestationObject", PBYTE), ("cbCredentialId", DWORD), ("pbCredentialId", PBYTE), ("Extensions", WebAuthNExtensions), ("dwUsedTransport", DWORD), ("bEpAtt", BOOL), ("bLargeBlobSupported", BOOL), ("bResidentKey", BOOL), ] auth_data = BytesProperty("AuthenticatorData") attestation = BytesProperty("Attestation") attestation_object = BytesProperty("AttestationObject") credential_id = BytesProperty("CredentialId") def __del__(self): WEBAUTHN.WebAuthNFreeCredentialAttestation(ctypes.byref(self)) class _FromString(object): @classmethod def from_string(cls, value): return getattr(cls, value.upper().replace("-", "_")) @unique class WebAuthNUserVerificationRequirement(_FromString, IntEnum): """Maps to WEBAUTHN_USER_VERIFICATION_REQUIREMENT_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L335 """ ANY = 0 REQUIRED = 1 PREFERRED = 2 DISCOURAGED = 3 @unique class WebAuthNAttestationConvoyancePreference(_FromString, IntEnum): """Maps to WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L340 """ ANY = 0 NONE = 1 INDIRECT = 2 DIRECT = 3 @unique class WebAuthNAuthenticatorAttachment(_FromString, IntEnum): """Maps to WEBAUTHN_AUTHENTICATOR_ATTACHMENT_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L330 """ ANY = 0 PLATFORM = 1 CROSS_PLATFORM = 2 CROSS_PLATFORM_U2F_V2 = 3 @unique class WebAuthNCTAPTransport(_FromString, IntEnum): """Maps to WEBAUTHN_CTAP_TRANSPORT_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L225 """ ANY = 0x00000000 USB = 0x00000001 NFC = 0x00000002 BLE = 0x00000004 TEST = 0x00000008 INTERNAL = 0x00000010 FLAGS_MASK = 0x0000001F @unique class WebAuthNEnterpriseAttestation(_FromString, IntEnum): """Maps to WEBAUTHN_ENTERPRISE_ATTESTATION_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L401 """ NONE = 0 VENDOR_FACILITATED = 1 PLATFORM_MANAGED = 2 @unique class WebAuthNLargeBlobSupport(_FromString, IntEnum): """Maps to WEBAUTHN_LARGE_BLOB_SUPPORT_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L405 """ NONE = 0 REQUIRED = 1 PREFERRED = 2 @unique class WebAuthNLargeBlobOperation(_FromString, IntEnum): """Maps to WEBAUTHN_LARGE_BLOB_OPERATION_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L478 """ NONE = 0 GET = 1 SET = 2 DELETE = 3 HRESULT = ctypes.HRESULT # type: ignore WEBAUTHN = windll.webauthn # type: ignore WEBAUTHN_API_VERSION = WEBAUTHN.WebAuthNGetApiVersionNumber() # The following is derived from # https://github.com/microsoft/webauthn/blob/master/webauthn.h#L37 WEBAUTHN.WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable.argtypes = [ ctypes.POINTER(ctypes.c_bool) ] WEBAUTHN.WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable.restype = HRESULT WEBAUTHN.WebAuthNAuthenticatorMakeCredential.argtypes = [ HWND, ctypes.POINTER(WebAuthNRpEntityInformation), ctypes.POINTER(WebAuthNUserEntityInformation), ctypes.POINTER(WebAuthNCoseCredentialParameters), ctypes.POINTER(WebAuthNClientData), ctypes.POINTER(WebAuthNMakeCredentialOptions), ctypes.POINTER(ctypes.POINTER(WebAuthNCredentialAttestation)), ] WEBAUTHN.WebAuthNAuthenticatorMakeCredential.restype = HRESULT WEBAUTHN.WebAuthNAuthenticatorGetAssertion.argtypes = [ HWND, LPCWSTR, ctypes.POINTER(WebAuthNClientData), ctypes.POINTER(WebAuthNGetAssertionOptions), ctypes.POINTER(ctypes.POINTER(WebAuthNAssertion)), ] WEBAUTHN.WebAuthNAuthenticatorGetAssertion.restype = HRESULT WEBAUTHN.WebAuthNFreeCredentialAttestation.argtypes = [ ctypes.POINTER(WebAuthNCredentialAttestation) ] WEBAUTHN.WebAuthNFreeAssertion.argtypes = [ctypes.POINTER(WebAuthNAssertion)] WEBAUTHN.WebAuthNGetCancellationId.argtypes = [ctypes.POINTER(GUID)] WEBAUTHN.WebAuthNGetCancellationId.restype = HRESULT WEBAUTHN.WebAuthNCancelCurrentOperation.argtypes = [ctypes.POINTER(GUID)] WEBAUTHN.WebAuthNCancelCurrentOperation.restype = HRESULT WEBAUTHN.WebAuthNGetErrorName.argtypes = [HRESULT] WEBAUTHN.WebAuthNGetErrorName.restype = PCWSTR WEBAUTHN_STRUCT_VERSIONS: Mapping[int, Mapping[str, int]] = { 1: { "WebAuthNRpEntityInformation": 1, "WebAuthNUserEntityInformation": 1, "WebAuthNClientData": 1, "WebAuthNCoseCredentialParameter": 1, "WebAuthNCredential": 1, "WebAuthNCredentialEx": 1, "WebAuthNMakeCredentialOptions": 3, "WebAuthNGetAssertionOptions": 4, "WebAuthNCommonAttestation": 1, "WebAuthNCredentialAttestation": 3, "WebAuthNAssertion": 1, }, 2: {}, 3: { "WebAuthNMakeCredentialOptions": 4, "WebAuthNGetAssertionOptions": 5, "WebAuthNCredentialAttestation": 4, "WebAuthNAssertion": 2, }, } def get_version(class_name: str) -> int: """Get version of struct. :param str class_name: Struct class name. :returns: Version of Struct to use. :rtype: int """ for api_version in range(WEBAUTHN_API_VERSION, 0, -1): if ( api_version in WEBAUTHN_STRUCT_VERSIONS and class_name in WEBAUTHN_STRUCT_VERSIONS[api_version] ): return WEBAUTHN_STRUCT_VERSIONS[api_version][class_name] raise ValueError("Unknown class name") class CancelThread(Thread): def __init__(self, event): super().__init__() self.daemon = True self._completed = False self.event = event self.guid = GUID() WEBAUTHN.WebAuthNGetCancellationId(ctypes.byref(self.guid)) def run(self): self.event.wait() if not self._completed: WEBAUTHN.WebAuthNCancelCurrentOperation(ctypes.byref(self.guid)) def complete(self): self._completed = True self.event.set() self.join() class WinAPI: """Implementation of Microsoft's WebAuthN APIs. :param ctypes.HWND handle: Window handle to use for API calls. """ version = WEBAUTHN_API_VERSION def __init__(self, handle=None): self.handle = handle or windll.user32.GetForegroundWindow() def get_error_name(self, winerror): """Returns an error name given an error HRESULT value. :param int winerror: Windows error code from an OSError. :return: An error name. :rtype: str Example: try: api.make_credential(*args, **kwargs) except OSError as e: print(api.get_error_name(e.winerror)) """ return WEBAUTHN.WebAuthNGetErrorName(winerror) def make_credential( self, rp, user, pub_key_cred_params, client_data, timeout=0, resident_key=False, platform_attachment=WebAuthNAuthenticatorAttachment.ANY, user_verification=WebAuthNUserVerificationRequirement.ANY, attestation=WebAuthNAttestationConvoyancePreference.DIRECT, exclude_credentials=None, extensions=None, event=None, ): """Make credential using Windows WebAuthN API. :param Dict[str,Any] rp: Relying Party Entity data. :param Dict[str,Any] user: User Entity data. :param List[Dict[str,Any]] pub_key_cred_params: List of PubKeyCredentialParams data. :param bytes client_data: ClientData JSON. :param int timeout: (optional) Timeout value, in ms. :param bool resident_key: (optional) Require resident key, default: False. :param WebAuthNAuthenticatorAttachment platform_attachment: (optional) Authenticator Attachment, default: any. :param WebAuthNUserVerificationRequirement user_verification: (optional) User Verification Requirement, default: any. :param WebAuthNAttestationConvoyancePreference attestation: (optional) Attestation Conveyance Preference, default: direct. :param List[Dict[str,Any]] exclude_credentials: (optional) List of PublicKeyCredentialDescriptor of previously registered credentials. :param Any extensions: Currently not supported. :param threading.Event event: (optional) Signal to abort the operation. """ if event: t = CancelThread(event) t.start() # TODO: add support for extensions attestation_pointer = ctypes.POINTER(WebAuthNCredentialAttestation)() WEBAUTHN.WebAuthNAuthenticatorMakeCredential( self.handle, ctypes.byref(WebAuthNRpEntityInformation(rp)), ctypes.byref(WebAuthNUserEntityInformation(user)), ctypes.byref(WebAuthNCoseCredentialParameters(pub_key_cred_params)), ctypes.byref(WebAuthNClientData(client_data)), ctypes.byref( WebAuthNMakeCredentialOptions( timeout, resident_key, platform_attachment, user_verification, attestation, exclude_credentials or [], ctypes.pointer(t.guid) if event else None, # Not supported: WebAuthNEnterpriseAttestation.NONE, WebAuthNLargeBlobSupport.NONE, False, ) ), ctypes.byref(attestation_pointer), ) if event: t.complete() return attestation_pointer.contents.attestation_object def get_assertion( self, rp_id, client_data, timeout=0, platform_attachment=WebAuthNAuthenticatorAttachment.ANY, user_verification=WebAuthNUserVerificationRequirement.ANY, allow_credentials=None, extensions=None, event=None, ): """Get assertion using Windows WebAuthN API. :param str rp_id: Relying Party ID string. :param bytes client_data: ClientData JSON. :param int timeout: (optional) Timeout value, in ms. :param WebAuthNAuthenticatorAttachment platform_attachment: (optional) Authenticator Attachment, default: any. :param WebAuthNUserVerificationRequirement user_verification: (optional) User Verification Requirement, default: any. :param List[Dict[str,Any]] allow_credentials: (optional) List of PublicKeyCredentialDescriptor of previously registered credentials. :param Any extensions: Currently not supported. :param threading.Event event: (optional) Signal to abort the operation. """ if event: t = CancelThread(event) t.start() # TODO: add support for extensions assertion_pointer = ctypes.POINTER(WebAuthNAssertion)() WEBAUTHN.WebAuthNAuthenticatorGetAssertion( self.handle, rp_id, ctypes.byref(WebAuthNClientData(client_data)), ctypes.byref( WebAuthNGetAssertionOptions( timeout, platform_attachment, user_verification, allow_credentials or [], ctypes.pointer(t.guid) if event else None, # Not supported: WebAuthNLargeBlobOperation.NONE, b"", ) ), ctypes.byref(assertion_pointer), ) if event: t.complete() obj = assertion_pointer.contents return obj.Credential.descriptor, obj.auth_data, obj.signature, obj.user_id ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1688652058.0625174 fido2-1.1.2/NEWS0000644000000000000000000001663214451544432010161 0ustar00* Version 1.1.2 (released 2023-07-06) ** Fix ClientPin usage for Authenticators that do not support passing a PIN. ** Fix: Handle correct CTAP response codes in authenticatorSelection. * Version 1.1.1 (released 2023-04-05) ** Add community provided support for NetBSD. ** Bugfix: Don't set length for largeBlob when offset is 0. ** Bugfix: Remove print statement in webauthn parsing. * Version 1.1.0 (released 2022-10-17) ** Bugfix: Fix name of "crossOrigin" in CollectedClientData.create(). ** Bugfix: Some incorrect type hints in the MDS3 classes were fixed. ** Stricter checking of dataclass field types. ** Add support for JSON-serialization of WebAuthn data classes. This changes the objects dict representation to align with new additions in the WebAuthn specification. As this may break compatibility, the new behavior requires explicit opt-in until python-fido2 2.0 is released. ** Update server example to use JSON serialization. ** Server: Add support for passing RegistrationResponse/AuthenticationResponse (or their deserialized JSON data) to register_complete/authenticate_complete. ** Add new "hybrid" AuthenticatorTransport. ** Add new AuthenticatorData flags, and use 2-letter names as in the WebAuthn spec (long names are still available as aliases). * Version 1.0.0 (released 2022-06-08) ** First stable release. * Version 1.0.0rc1 (released 2022-05-02) ** Release Candidate 1 of first stable release. ** Require Python 3.7 or later. ** APIs have updated to align with WebAuthn level 2. ** Several CTAP 2.1 features have been implemented. * Version 0.9.3 (released 2021-11-09) ** Bugfix: Linux - Don't fail device discovery when hidraw doesn't support HIDIOCGRAWUNIQ (Linux kernels before 5.6). * Version 0.9.2 (released 2021-10-14) ** Support the latest Windows webauthn.h API (included in Windows 11). ** Add product name and serial number to HidDescriptors. ** Remove the need for the uhid-freebsd dependency on FreeBSD. * Version 0.9.1 (released 2021-02-03) ** Add new CTAP error codes and improve handling of unknown codes. * Version 0.9.0 (released 2021-01-20) ** Server: Attestation is now done in two parts (to align better with the spec): First, type-specific validation is done to provide a trust chain. Second, validation of the trust chain is done. ** Client: API changes to better support extensions. *** Fido2Client can be configured with Ctap2Extensions to support. *** Client.make_credential now returns a AuthenticatorAttestationResponse, which holds the AttestationObject and ClientData, as well as any client extension results for the credential. *** Client.get_assertion now returns an AssertionSelection object, which is used to select between multiple assertions, resulting in an AuthenticatorAssertionResponse, which holds the ClientData, assertion values, as well as any client extension results for the assertion. ** Renames: The CTAP1 and CTAP2 classes have been renamed to Ctap1 and Ctap2, respectively. The old names currently work, but will be removed in the future. ** ClientPin: The ClientPin API has been restructured to support multiple PIN protocols, UV tokens, and token permissions. ** CTAP 2.1 PRE: Several new features have been added for CTAP 2.1, including Credential Management, Bio Enrollment, Large Blobs, and Authenticator Config. ** HID: The platform specific HID code has been revamped and cleaned up. * Version 0.8.1 (released 2019-11-25) ** Bugfix: WindowsClient.make_credential error when resident key requirement is unspecified. * Version 0.8.0 (released 2019-11-25) ** New fido2.webauthn classes modeled after the W3C WebAuthn spec introduced. ** CTAP2 send_cbor/make_credential/get_assertion and U2fClient request/authenticate `timeout` arguments replaced with `event` used to cancel a request. ** Fido2Client: *** make_credential/get_assertion now take WebAuthn options objects. *** timeout is now provided in ms in WebAuthn options objects. Event based cancelation also available by passing an Event. ** Fido2Server: *** ATTESTATION, USER_VERIFICATION, and AUTHENTICATOR_ATTACHMENT enums have been replaced with fido2.webauthn classes. *** RelyingParty has been replaced with PublicKeyCredentialRpEntity, and name is no longer optional. *** Options returned by register_begin/authenticate_begin now omit unspecified values if they are optional, instead of filling in default values. *** Fido2Server.allowed_algorithms now contains a list of PublicKeyCredentialParameters instead of algorithm identifiers. *** Fido2Server.timeout is now in ms and of type int. ** Support native WebAuthn API on Windows through WindowsClient. * Version 0.7.3 (released 2019-10-24) ** Bugfix: Workaround for size of int on Python 2 on Windows. * Version 0.7.2 (released 2019-10-24) ** Support for the TPM attestation format. ** Allow passing custom challenges to register/authenticate in Fido2Server. ** Bugfix: CTAP2 CANCEL command response handling fixed. ** Bugfix: Fido2Client fix handling of empty allow_list. ** Bugfix: Fix typo in CTAP2.get_assertions() causing it to fail. * Version 0.7.1 (released 2019-09-20) ** Support for FreeBSD. ** Enforce canonical CBOR on Authenticator responses by default. ** PCSC: Support extended APDUs. ** Server: Verify that UP flag is set. ** U2FFido2Server: Implement AppID exclusion extension. ** U2FFido2Server: Allow custom U2F facet verification. ** Bugfix: U2FFido2Server.authenticate_complete now returns the result. * Version 0.7.0 (released 2019-06-17) ** Add support for NFC devices using PCSC. ** Add support for the hmac-secret Authenticator extension. ** Honor max credential ID length and number of credentials to Authenticator. ** Add close() method to CTAP devices to explicitly release their resources. * Version 0.6.0 (released 2019-05-10) ** Don't fail if CTAP2 Info contains unknown fields. ** Replace cbor loads/dumps functions with encode/decode/decode_from. ** Server: Add support for AuthenticatorAttachment. ** Server: Add support for more key algorithms. ** Client: Expose CTAP2 Info object as Fido2Client.info. * Version 0.5.0 (released 2018-12-21) ** Changes to server classes, some backwards breaking. ** Add ability to authenticate U2F credentials by using the appid extension. ** Make verification of attestation more explicit. ** Add support for Android SafetyNet attestation. ** Make it easier to work with U2F/CTAP1 data formats. * Version 0.4.0 (released 2018-09-27) ** Add classes for implementing a server. ** Various small changes, some affecting backwards compatibility. * Version 0.3.0 (released 2018-04-13) ** Add conversion between string/int keys for AttestationObject. ** Replace internal Exceptions with built-in types. ** Bugfix: Don't use TimeoutError which isn't available on Python 2. * Version 0.2.2 (released 2018-04-11) ** Bugfix: Better handling of unplugged devices on MacOS and avoid leaking threads. * Version 0.2.1 (released 2018-04-10) ** Add server example. ** Parse AttestationObjects that use string keys (Webauthn). ** Fix bug in handling packets with the wrong channel id. * Version 0.2.0 (released 2018-04-05) ** Changed name of project to python-fido2 to better reflect its scope. ** Added attestation and assertion verification methods. ** A lot of name changes, moved classes, etc. ** New example for multi-device use. * Version 0.1.0 (released 2018-03-16) ** First beta release. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1688652058.0642614 fido2-1.1.2/pyproject.toml0000644000000000000000000000306114451544432012366 0ustar00[tool.poetry] name = "fido2" version = "1.1.2" description = "FIDO2/WebAuthn library for implementing clients and servers." authors = ["Dain Nilsson "] homepage = "https://github.com/Yubico/python-fido2" repository = "https://github.com/Yubico/python-fido2" keywords = ["fido2", "webauthn", "ctap", "u2f"] classifiers = [ "License :: OSI Approved :: BSD License", "License :: OSI Approved :: Apache Software License", "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: System Administrators", "Topic :: Internet", "Topic :: Security :: Cryptography", "Topic :: Software Development :: Libraries :: Python Modules" ] include = [ { path = "COPYING", format = "sdist"}, { path = "COPYING.MPLv2", format = "sdist"}, { path = "COPYING.APLv2", format = "sdist"}, { path = "NEWS", format = "sdist"}, { path = "README.adoc", format = "sdist"}, { path= "tests/", format = "sdist"}, { path= "examples/", format = "sdist"}, ] [tool.poetry.dependencies] python = "^3.7" cryptography = ">=2.6, !=35, <44" pyscard = {version = "^1.9 || ^2", optional = true} [tool.poetry.extras] pcsc = ["pyscard"] [tool.poetry.dev-dependencies] pytest = "^7.0" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.pytest.ini_options] testpaths = ["tests"] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1680688177.912042 fido2-1.1.2/README.adoc0000644000000000000000000001001614413242062011225 0ustar00== python-fido2 image:https://github.com/Yubico/python-fido2/workflows/build/badge.svg["Github actions build", link="https://github.com/Yubico/python-fido2/actions"] Provides library functionality for communicating with a FIDO device over USB as well as verifying attestation and assertion signatures. This library aims to support the FIDO U2F and FIDO 2 protocols for communicating with a USB authenticator via the Client-to-Authenticator Protocol (CTAP 1 and 2). In addition to this low-level device access, classes defined in the `fido2.client` and `fido2.server` modules implement higher level operations which are useful when interfacing with an Authenticator, or when implementing WebAuthn support for a Relying Party. For usage, see the `examples/` directory. === References These links related to WebAuthn and FIDO2 can help you get started: * Yubico WebAuthn/FIDO2 guide: https://developers.yubico.com/FIDO2/ * W3C WebAuthn specification: https://www.w3.org/TR/webauthn/ * FIDO specifications: https://fidoalliance.org/specifications/download/ === License This project, with the exception of the files mentioned below, is licensed under the BSD 2-clause license. See the _COPYING_ file for the full license text. This project contains source code from pyu2f (https://github.com/google/pyu2f) which is licensed under the Apache License, version 2.0. These files are located in `fido2/hid/`. See http://www.apache.org/licenses/LICENSE-2.0, or the _COPYING.APLv2_ file for the full license text. This project also bundles the public suffix list (https://publicsuffix.org) which is licensed under the Mozilla Public License, version 2.0. This file is stored as `fido2/public_suffix_list.dat`. See https://mozilla.org/MPL/2.0/, or the _COPYING.MPLv2_ file for the full license text. === Requirements fido2 is compatible with Python 3.7 and later, and is tested on Windows, MacOS, and Linux. Support for OpenBSD, FreeBSD, and NetBSD is provided as-is and relies on community contributions. === Installation fido2 is installable by running the following command: pip install fido2 To install the dependencies required for communication with NFC authenticators, instead use: pip install fido2[pcsc] Under Windows 10 (1903 or later) access to FIDO devices is restricted and requires running as Administrator. This library can still be used when running as non-administrator, via the `fido.client.WindowsClient` class. An example of this is included in the file `examples/credential.py`. Under Linux you will need to add a Udev rule to be able to access the FIDO device, or run as root. For example, the Udev rule may contain the following: ---- #Udev rule for allowing HID access to Yubico devices for FIDO support. KERNEL=="hidraw*", SUBSYSTEM=="hidraw", \ MODE="0664", GROUP="plugdev", ATTRS{idVendor}=="1050" ---- There may be a package already available for your distribution that does this for you, see: https://support.yubico.com/hc/en-us/articles/360013708900-Using-Your-U2F-YubiKey-with-Linux Under FreeBSD you will either need to run as root or add rules for your device to /etc/devd.conf, which can be automated by installing security/u2f-devd: # pkg install u2f-devd ==== Dependencies This project depends on Cryptography. For instructions on installing this dependency, see https://cryptography.io/en/latest/installation/. NFC support is optionally available via PC/SC, using the pyscard library. For instructions on installing this dependency, see https://github.com/LudovicRousseau/pyscard/blob/master/INSTALL.md. === Development For development of the library we use https://python-poetry.org/[poetry]. To set up the dev environment, run this command in the root directory of the repository: poetry install We also use https://pre-commit.com/[pre-commit] to run some scans on the code prior to committing. ==== Running tests While many tests can run on their own, some require a connected U2F or FIDO2 device to run. poetry run pytest ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/__init__.py0000644000000000000000000000011614413232070012711 0ustar00import fido2.features fido2.features.webauthn_json_mapping.enabled = True ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/hid/__init__.py0000644000000000000000000000000014413232070013445 0ustar00././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/hid/test_base.py0000644000000000000000000000143714413232070013676 0ustar00import unittest from fido2.hid.base import parse_report_descriptor class TestBase(unittest.TestCase): def test_parse_report_descriptor_1(self): max_in_size, max_out_size = parse_report_descriptor( bytes.fromhex( "06d0f10901a1010920150026ff007508954081020921150026ff00750895409102c0" ) ) self.assertEqual(max_in_size, 64) self.assertEqual(max_out_size, 64) def test_parse_report_descriptor_2(self): with self.assertRaises(ValueError): parse_report_descriptor( bytes.fromhex( "05010902a1010901a10005091901290515002501950575018102950175038101" "05010930093109381581257f750895038106c0c0" ) ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1688635376.1595967 fido2-1.1.2/tests/test_attestation.py0000644000000000000000000007541514451503760014577 0ustar00# Copyright (c) 2013 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 fido2.webauthn import AuthenticatorData from fido2.attestation import ( Attestation, AttestationType, UnsupportedAttestation, FidoU2FAttestation, PackedAttestation, TpmAttestation, NoneAttestation, AndroidSafetynetAttestation, AppleAttestation, InvalidData, InvalidSignature, UnsupportedType, verify_x509_chain, ) from cryptography.exceptions import UnsupportedAlgorithm, _Reasons import unittest # GS Root R2 (https://pki.goog/) _GSR2_DER = bytes.fromhex( "308203ba308202a2a003020102020b0400000000010f8626e60d300d06092a864886f70d0101050500304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d20523231133011060355040a130a476c6f62616c5369676e311330110603550403130a476c6f62616c5369676e301e170d3036313231353038303030305a170d3231313231353038303030305a304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d20523231133011060355040a130a476c6f62616c5369676e311330110603550403130a476c6f62616c5369676e30820122300d06092a864886f70d01010105000382010f003082010a0282010100a6cf240ebe2e6f28994542c4ab3e21549b0bd37f8470fa12b3cbbf875fc67f86d3b2305cd6fdadf17bdce5f86096099210f5d053defb7b7e7388ac52887b4aa6ca49a65ea8a78c5a11bc7a82ebbe8ce9b3ac962507974a992a072fb41e77bf8a0fb5027c1b96b8c5b93a2cbcd612b9eb597de2d006865f5e496ab5395e8834ecbc780c0898846ca8cd4bb4a07d0c794df0b82dcb21cad56c5b7de1a02984a1f9d39449cb24629120bcdd0bd5d9ccf9ea270a2b7391c69d1bacc8cbe8e0a0f42f908b4dfbb0361bf6197a85e06df26113885c9fe0930a51978a5aceafabd5f7aa09aa60bddcd95fdf72a960135e0001c94afa3fa4ea070321028e82ca03c29b8f0203010001a3819c308199300e0603551d0f0101ff040403020106300f0603551d130101ff040530030101ff301d0603551d0e041604149be20757671c1ec06a06de59b49a2ddfdc19862e30360603551d1f042f302d302ba029a0278625687474703a2f2f63726c2e676c6f62616c7369676e2e6e65742f726f6f742d72322e63726c301f0603551d230418301680149be20757671c1ec06a06de59b49a2ddfdc19862e300d06092a864886f70d01010505000382010100998153871c68978691ece04ab8440bab81ac274fd6c1b81c4378b30c9afcea2c3c6e611b4d4b29f59f051d26c1b8e983006245b6a90893b9a9334b189ac2f887884edbdd71341ac154da463fe0d32aab6d5422f53a62cd206fba2989d7dd91eed35ca23ea15b41f5dfe564432de9d539abd2a2dfb78bd0c080191c45c02d8ce8f82da4745649c505b54f15de6e44783987a87ebbf3791891bbf46f9dc1f08c358c5d01fbc36db9ef446d7946317e0afea982c1ffefab6e20c450c95f9d4d9b178c0ce501c9a0416a7353faa550b46e250ffb4c18f4fd52d98e69b1e8110fde88d8fb1d49f7aade95cf2078c26012db25408c6afc7e4238406412f79e81e1932e" # noqa E501 ) class TestAttestationObject(unittest.TestCase): def test_unsupported_attestation(self): attestation = Attestation.for_type("__unsupported__")() self.assertIsInstance(attestation, UnsupportedAttestation) with self.assertRaises(UnsupportedType) as ctx: attestation.verify({}, 0, b"") self.assertEqual(ctx.exception.fmt, "__unsupported__") def test_none_attestation(self): attestation = Attestation.for_type("none")() self.assertIsInstance(attestation, NoneAttestation) auth_data = AuthenticatorData( bytes.fromhex( "0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12410000002BF8A011F38C0A4D15800617111F9EDC7D0040A17370D9C1759005700C8DE77E7DFD3A0A5300E0A26E5213AA40D6DF10EE4028B58B5F34167035D840BEBAE0C5CE8FD05AD9BD33E3BE7D1C558D81AB4803570BA5010203262001215820A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1225820FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C" # noqa E501 ) ) res = attestation.verify({}, auth_data, b"deadbeef" * 8) self.assertEqual(res.attestation_type, AttestationType.NONE) self.assertEqual(res.trust_path, []) with self.assertRaises(InvalidData): attestation.verify({"not": "empty"}, auth_data, b"deadbeef" * 8) def test_none_windows_hello_attestation(self): attestation = Attestation.for_type("none")() self.assertIsInstance(attestation, NoneAttestation) auth_data = AuthenticatorData( bytes.fromhex( "54ce651ed715b4aaa755eecebd4ea0950815b334bd07d109893e963018cddbd945000000006028b017b1d44c02b4b3afcdafc96bb200201decfcd6d6a05c2826d52348afdc70a9800df007845047b1a23706aa6e2f315ca401030339010020590100af59f4ad4f71da800bb91045b267e240e06317f7b2b1d76f78e239a433811faeca58a1869fb00225eb2727f81b6b20cbc18c0ad8d38fa450e8df11b4ad3bc3ee5d13c77ed172fa3af0195ec6ac0c4bac8c950115dfce6d38737cbafefbe117d8401cd56c638043a0d585131bc48a153b17a8dcb96671e15a90ba1b4ff810b138b77ac0a050b039b87b6089dd8dfa45611b992109d554aad3e6b72ac82d801496e4d2d230aa466090bbbf4f5632fe4b588e4f571462378fa6f514a536a5945b223c8d98f730b7cf85de86b98c217090f9e9ebf9643cf3feceeacb837d7a18542e03271cd8c70cf81186cdb63e4cbf4efc0cbbd3c93231b06f19580d0a980264d12143010001" # noqa ) ) # noqa res = attestation.verify({}, auth_data, b"deadbeef" * 8) self.assertEqual(res.attestation_type, AttestationType.NONE) self.assertEqual(res.trust_path, []) with self.assertRaises(InvalidData): attestation.verify({"not": "empty"}, auth_data, b"deadbeef" * 8) def test_tpm_windows_hello_attestation(self): attestation = Attestation.for_type("tpm")() self.assertIsInstance(attestation, TpmAttestation) statement = { "alg": -65535, "sig": bytes.fromhex( """80e564d8cbb236577de68d2e68ecae200a8eaf6992889b5 fdc24624a4cb69caaab18df965058fbac39df9714b9c80b9a12d715cfc4dd15ed3a6e191a6d26e 7206fd402b0733c2c8b91f62ad44e4d41c940e2e914253b1d1a1c8889b1cdaf668b5449245dc33 1fab12e0b0dcdfc530cbe1f370e1f2b06c163fbd6177925a1a8998edd2e726989246a1980fa34e 6d65d3ca284944cb10254d85db0d8948294fb8174a41206c6b5e36406bae447343f8c9f97420e3 9f361815dfb268b33ccde5f29e4348a70f95abc30754c839fa7126e5bd882377d6abe3c0c95ba5 c21190a5e4fff5380b2c23cc1655e593244019e172ba8284618471d95b92c231c1ffe98ff23 """.replace( "\n", "" ) ), "x5c": [ bytes.fromhex( """308204b23082039aa0030201020210789e1a3657344c52bad2 2ed1ceb1bfaf300d06092a864886f70d01010b05003041313f303d060355040313364e43552d4e 54432d4b455949442d394642423739414130463532363237384245443135303932394137313731 45393641333542454637301e170d3139303430313038353934305a170d32393034303130383539 34305a300030820122300d06092a864886f70d01010105000382010f003082010a0282010100a6 60d1fe41564c26f943c70ff89fbd9ed6d957191d5ecaf727393d73cfff85e3ccfb9830027fe84b 171cc4b0b13811df4d9deff2bce4d8a8f9797169f7b8fc25016d9ba687c003083693716180c8f1 eccaa4410a9a7fe07b198ad7ba94ecf744e9bef0273a5e0723a4ac197994ca1ac0e5f595433970 0cf14ead419ae7cde8c3e81389771d5fa3d339f8d0856e918fd3746fa9a944d3c1f1c6a4e0ce3f 99b5ac5ba05166b3b8695405ae7d3777f9cc8e3ab8570f2191ba4f2cfc4c544337596f48d3d5f5 f9ae80575bf9eb81d5c477e99c58854645d587dd0ccdea2b0e3d482e69b326b289e65741e6b214 3fc2bca35ca7dd60e554affdcb85000762ff09b0410203010001a38201e5308201e1300e060355 1d0f0101ff040403020780300c0603551d130101ff04023000306d0603551d200101ff04633061 305f06092b060104018237151f3052305006082b0601050507020230441e420054004300500041 002000200054007200750073007400650064002000200050006c006100740066006f0072006d00 200020004900640065006e007400690074007930100603551d250409300706056781050803304a 0603551d110101ff0440303ea43c303a3138300e060567810502030c0569643a31333010060567 810502020c074e5043543678783014060567810502010c0b69643a3445353434333030301f0603 551d23041830168014c799ef2371327cb2e9e03838d0a9009fe9ed29e7301d0603551d0e041604 1429fb5f05c6187d8463b8b250b8f0ff128fd3a0713081b306082b060105050701010481a63081 a33081a006082b0601050507300286819368747470733a2f2f617a637370726f646e637561696b 7075626c6973682e626c6f622e636f72652e77696e646f77732e6e65742f6e63752d6e74632d6b 657969642d39666262373961613066353236323738626564313530393239613731373165393661 3335626566372f66383530353438392d303235612d343235322d383239302d3934646532633633 643039362e636572300d06092a864886f70d01010b0500038201010084bc4b9ac3ab6c2438bdec dd3d99e6179bfc465995481d856683602bdcf0c26327b8ab77f7b695c8c6aab5f283b079c29369 29727b839e5bf08c687a33fc59bf281ebf28e9d04e78fd626573028014028badca038e68361017 a4501b18d56a6a73e35f00e043d8febb7a4c719c837bc5cb801efe23570d6c8b40699ba411fe66 f6fe5558f7d1c56a7646ba483cd601690a9323caba9257ae561781b13c658083ad1281047d94d4 c1ab9759d90a16fbe167cec388e7b67027a20dbc1b88986dbb636107ef91ffec22c413ac5fbfec 3de9ee4aa1c6e4c173e43246193890c8b024587fcc8028eb379f515de3c678b11dfb81aef3547c 3c6e790577d52f775f9148""".replace( "\n", "" ) ), bytes.fromhex( """308205e8308203d0a003020102021333000000a5304bb34bf0 bee43e0000000000a5300d06092a864886f70d01010b050030818c310b30090603550406130255 53311330110603550408130a57617368696e67746f6e3110300e060355040713075265646d6f6e 64311e301c060355040a13154d6963726f736f667420436f72706f726174696f6e313630340603 550403132d4d6963726f736f66742054504d20526f6f7420436572746966696361746520417574 686f726974792032303134301e170d3136303831383230323032305a170d323931323331323032 3032305a3041313f303d060355040313364e43552d4e54432d4b455949442d3946424237394141 304635323632373842454431353039323941373137314539364133354245463730820122300d06 092a864886f70d01010105000382010f003082010a0282010100e0b963203494ff3b8b93855f4d 0086aabf9f5038fe2a2c04311609074565097dd16de61ae1e6086f5d16997dc7ee5342bf9988f6 bb73ca614f3f5d8ea084fd047112892ae22db792e2efbe24bcb07fd01af124666db7ad53677e45 6a95e972a659c04fe3569e882afbf019c3c5890c52d2e81d175f97234fbe341406cbf834cafa76 184c077c9bd058fbe14b4032039142128fe985ee6041819eee86a62a43491d11af9d78f08e722a 28c0e9b522fed12f172dddfd032a634a6eba2fc90c332997d3ba5f297230cd7d666b6925c0e6ea 79b2459f68fc283cd7a09e09973a610fb88eb63bb1cc29e0dc5e033ace6b966c78038c1adc049e f5360ae28696825ed10203010001a382018b30820187300b0603551d0f040403020186301b0603 551d250414301206092b06010401823715240605678105080330160603551d20040f300d300b06 092b060104018237151f30120603551d130101ff040830060101ff020100301d0603551d0e0416 0414c799ef2371327cb2e9e03838d0a9009fe9ed29e7301f0603551d230418301680147a8c0ace 2f486217e294d1ae55c152ec7174a45630700603551d1f046930673065a063a061865f68747470 3a2f2f7777772e6d6963726f736f66742e636f6d2f706b696f70732f63726c2f4d6963726f736f 667425323054504d253230526f6f742532304365727469666963617465253230417574686f7269 7479253230323031342e63726c307d06082b060105050701010471306f306d06082b0601050507 30028661687474703a2f2f7777772e6d6963726f736f66742e636f6d2f706b696f70732f636572 74732f4d6963726f736f667425323054504d253230526f6f742532304365727469666963617465 253230417574686f72697479253230323031342e637274300d06092a864886f70d01010b050003 820201003e91d074e6d6b9719bf13ffd7cd16b733938c092edaa580136ac7d8bb5295242e432f6 7c3ca5b1c8968b994665e99796a39d579a85cbe6eab02dfce1d08a4ce802b41bf6b00a01533c7c f3b96c7d0b9c0f3a5d2e04350037aea5140a5cc781ca73f370998110bd1031cfa427760920574a 5d7709a1765921d61cb36d91d2ce9d3301f0798ae4b23592b080e70bb535cdf57403f96fe6f0ff 4c0f0363f785a918a1fd3debfaaaebe6b08724a216b491e95e6e300e3d43e4e156fe3c036afba1 7ad2b442f904568af1cc3fd9ad1888cbbd9ec98d42e55af5b26fa8790b6b7da677a585fff6ae90 18e492742d4e9c5ca1a06990a3abff76c6bc4b1e22d8c226d09a96fdcc12801345b647e15850d1 0d0cdb609160b1a7a7c2c6f0eb3dbc2fcd42b765fd22a5672b26009b9a83b44388b62cb89e9169 a455ff5be5ce8f7bde0420b5d7d24ec254affdc2e7e946c961ec159b6dfc703e3934f9445b0072 8e137e11a7c66f76709ca2177b39159fc08593aaa83724b159abb93e535aef53d7d6066a317f92 d42d17888534fee9daf844260de901c3b18b49ccb2a5f81f0f4639f2e2cfa1ce1d7c791cef6f48 5d10df989aac02b1e9afd1094603f5307133f5f59ce105a5910700f98fea5a5fcf8f5cf4c797bd 79d440cc4f9161f5cc61e0e8f06592050cd1f0f0fd066bd1d6335710fdf8159b75281ee1082bff 1da2fc0b631bd346ac""".replace( "\n", "" ) ), ], "certInfo": bytes.fromhex( """ff54434780170022000b68cec627cc6411099a1f80 9fde4379f649aa170c7072d1adf230de439efc80810014f7c8b0cdeb31328648130a19733d6fff 16e76e1300000003ef605603446ed8c56aa7608d01a6ea5651ee67a8a20022000bdf681917e185 29c61e1b85a1e7952f3201eb59c609ed5d8e217e5de76b228bbd0022000b0a10d216b0c3ab82bf dc1f0a016ab9493384c7aee1937ee8800f76b30c9b71a7""".replace( "\n", "" ) ), "pubArea": bytes.fromhex( """0001000b0006047200209dffcbf36c383ae699fb986 8dc6dcb89d7153884be2803922c124158bfad22ae001000100800000000000100c706586c7f46c dffede0ee0c5ebc8b7a08b36555c8091669e9ef2cb4fd858134a01e9522d3ef924069aeeec2271 823fe9879b5079eb3123be2eb39a7e954f8b83b5ebefefda25aed01bd19eab6db1962a3713985b 7a2dd1aa7770b5c1567fb0d18521e14abebbccc16832ef10bb05dcc818bbb70c91c224475928ad a6f6181ed64f1cfb40db5e01687454cfacafa8318bdc6a677550baa6e24f8af864fa5324e9d930 a97cdeb1995b476f21a017b33ab7fe4139f2524c784fcb04cf5241c89f0c145eb23da914ad1722 d47a843692a0b2a567d94dd808c13678a51c5a0583dc042dcbba1b9ceff12b159d0539248b0994 ee18128ed50dd7a855e54d2459db005""".replace( "\n", "" ) ), } auth_data = AuthenticatorData( bytes.fromhex( "54ce651ed715b4aaa755eecebd4ea0950815b334bd07d109893e963018cddbd9450000000008987058cadc4b81b6e130de50dcbe9600206053b7b599d16fb3fb11ea17a344850ebd0d18183a5b7ca6dfbd20c63cdb462aa401030339010020590100c706586c7f46cdffede0ee0c5ebc8b7a08b36555c8091669e9ef2cb4fd858134a01e9522d3ef924069aeeec2271823fe9879b5079eb3123be2eb39a7e954f8b83b5ebefefda25aed01bd19eab6db1962a3713985b7a2dd1aa7770b5c1567fb0d18521e14abebbccc16832ef10bb05dcc818bbb70c91c224475928ada6f6181ed64f1cfb40db5e01687454cfacafa8318bdc6a677550baa6e24f8af864fa5324e9d930a97cdeb1995b476f21a017b33ab7fe4139f2524c784fcb04cf5241c89f0c145eb23da914ad1722d47a843692a0b2a567d94dd808c13678a51c5a0583dc042dcbba1b9ceff12b159d0539248b0994ee18128ed50dd7a855e54d2459db0052143010001" # noqa ) ) client_param = bytes.fromhex( "057a0ecbe7e3e99e8926941614f6af078c802b110be89eb221d69be2e17a1ba4" ) try: res = attestation.verify(statement, auth_data, client_param) except UnsupportedAlgorithm as e: if e._reason is _Reasons.UNSUPPORTED_HASH: self.skipTest( "SHA1 signature verification not supported on this machine" ) res = attestation.verify(statement, auth_data, client_param) self.assertEqual(res.attestation_type, AttestationType.ATT_CA) verify_x509_chain(res.trust_path) def test_fido_u2f_attestation(self): attestation = Attestation.for_type("fido-u2f")() self.assertIsInstance(attestation, FidoU2FAttestation) statement = { "sig": bytes.fromhex( "30450220324779C68F3380288A1197B6095F7A6EB9B1B1C127F66AE12A99FE8532EC23B9022100E39516AC4D61EE64044D50B415A6A4D4D84BA6D895CB5AB7A1AA7D081DE341FA" # noqa E501 ), "x5c": [ bytes.fromhex( "3082024A30820132A0030201020204046C8822300D06092A864886F70D01010B0500302E312C302A0603550403132359756269636F2055324620526F6F742043412053657269616C203435373230303633313020170D3134303830313030303030305A180F32303530303930343030303030305A302C312A302806035504030C2159756269636F205532462045452053657269616C203234393138323332343737303059301306072A8648CE3D020106082A8648CE3D030107034200043CCAB92CCB97287EE8E639437E21FCD6B6F165B2D5A3F3DB131D31C16B742BB476D8D1E99080EB546C9BBDF556E6210FD42785899E78CC589EBE310F6CDB9FF4A33B3039302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C020101040403020430300D06092A864886F70D01010B050003820101009F9B052248BC4CF42CC5991FCAABAC9B651BBE5BDCDC8EF0AD2C1C1FFB36D18715D42E78B249224F92C7E6E7A05C49F0E7E4C881BF2E94F45E4A21833D7456851D0F6C145A29540C874F3092C934B43D222B8962C0F410CEF1DB75892AF116B44A96F5D35ADEA3822FC7146F6004385BCB69B65C99E7EB6919786703C0D8CD41E8F75CCA44AA8AB725AD8E799FF3A8696A6F1B2656E631B1E40183C08FDA53FA4A8F85A05693944AE179A1339D002D15CABD810090EC722EF5DEF9965A371D415D624B68A2707CAD97BCDD1785AF97E258F33DF56A031AA0356D8E8D5EBCADC74E071636C6B110ACE5CC9B90DFEACAE640FF1BB0F1FE5DB4EFF7A95F060733F5" # noqa E501 ) ], } auth_data = AuthenticatorData( bytes.fromhex( "1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE41000000000000000000000000000000000000000000403EBD89BF77EC509755EE9C2635EFAAAC7B2B9C5CEF1736C3717DA48534C8C6B654D7FF945F50B5CC4E78055BDD396B64F78DA2C5F96200CCD415CD08FE420038A5010203262001215820E87625896EE4E46DC032766E8087962F36DF9DFE8B567F3763015B1990A60E1422582027DE612D66418BDA1950581EBC5C8C1DAD710CB14C22F8C97045F4612FB20C91" # noqa E501 ) ) client_param = bytes.fromhex( "687134968222EC17202E42505F8ED2B16AE22F16BB05B88C25DB9E602645F141" ) res = attestation.verify(statement, auth_data, client_param) self.assertEqual(res.attestation_type, AttestationType.BASIC) self.assertEqual(len(res.trust_path), 1) statement["sig"] = b"a" * len(statement["sig"]) with self.assertRaises(InvalidSignature): attestation.verify(statement, auth_data, client_param) def test_packed_attestation(self): attestation = Attestation.for_type("packed")() self.assertIsInstance(attestation, PackedAttestation) statement = { "alg": -7, "sig": bytes.fromhex( "304502200D15DAF337D727AB4719B4027114A2AC43CD565D394CED62C3D9D1D90825F0B3022100989615E7394C87F4AD91F8FDAE86F7A3326DF332B3633DB088AAC76BFFB9A46B" # noqa E501 ), "x5c": [ bytes.fromhex( "308202B73082019FA00302010202041D31330D300D06092A864886F70D01010B0500302A3128302606035504030C1F59756269636F2050726576696577204649444F204174746573746174696F6E301E170D3138303332383036333932345A170D3139303332383036333932345A306E310B300906035504061302534531123010060355040A0C0959756269636F20414231223020060355040B0C1941757468656E74696361746F72204174746573746174696F6E3127302506035504030C1E59756269636F205532462045452053657269616C203438393736333539373059301306072A8648CE3D020106082A8648CE3D030107034200047D71E8367CAFD0EA6CF0D61E4C6A416BA5BB6D8FAD52DB2389AD07969F0F463BFDDDDDC29D39D3199163EE49575A3336C04B3309D607F6160C81E023373E0197A36C306A302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C0201010404030204303021060B2B0601040182E51C01010404120410F8A011F38C0A4D15800617111F9EDC7D300C0603551D130101FF04023000300D06092A864886F70D01010B050003820101009B904CEADBE1F1985486FEAD02BAEAA77E5AB4E6E52B7E6A2666A4DC06E241578169193B63DADEC5B2B78605A128B2E03F7FE2A98EAEB4219F52220995F400CE15D630CF0598BA662D7162459F1AD1FC623067376D4E4091BE65AC1A33D8561B9996C0529EC1816D1710786384D5E8783AA1F7474CB99FE8F5A63A79FF454380361C299D67CB5CC7C79F0D8C09F8849B0500F6D625408C77CBBC26DDEE11CB581BEB7947137AD4F05AAF38BD98DA10042DDCAC277604A395A5B3EAA88A5C8BB27AB59C8127D59D6BBBA5F11506BF7B75FDA7561A0837C46F025FD54DCF1014FC8D17C859507AC57D4B1DEA99485DF0BA8F34D00103C3EEF2EF3BBFEC7A6613DE" # noqa E501 ) ], } auth_data = AuthenticatorData( bytes.fromhex( "0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE124100000003F8A011F38C0A4D15800617111F9EDC7D004060A386206A3AACECBDBB22D601853D955FDC5D11ADFBD1AA6A950D966B348C7663D40173714A9F987DF6461BEADFB9CD6419FFDFE4D4CF2EEC1AA605A4F59BDAA50102032620012158200EDB27580389494D74D2373B8F8C2E8B76FA135946D4F30D0E187E120B423349225820E03400D189E85A55DE9AB0F538ED60736EB750F5F0306A80060FE1B13010560D" # noqa E501 ) ) client_param = bytes.fromhex( "985B6187D042FB1258892ED637CEC88617DDF5F6632351A545617AA2B75261BF" ) res = attestation.verify(statement, auth_data, client_param) self.assertEqual(res.attestation_type, AttestationType.BASIC) self.assertEqual(len(res.trust_path), 1) statement["sig"] = b"a" * len(statement["sig"]) with self.assertRaises(InvalidSignature): attestation.verify(statement, auth_data, client_param) def test_android_safetynet_attestation(self): attestation = Attestation.for_type("android-safetynet")() self.assertIsInstance(attestation, AndroidSafetynetAttestation) statement = { "ver": "14574037", "response": b"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlGa2pDQ0JIcWdBd0lCQWdJUVJYcm9OMFpPZFJrQkFBQUFBQVB1bnpBTkJna3Foa2lHOXcwQkFRc0ZBREJDTVFzd0NRWURWUVFHRXdKVlV6RWVNQndHQTFVRUNoTVZSMjl2WjJ4bElGUnlkWE4wSUZObGNuWnBZMlZ6TVJNd0VRWURWUVFERXdwSFZGTWdRMEVnTVU4eE1CNFhEVEU0TVRBeE1EQTNNVGswTlZvWERURTVNVEF3T1RBM01UazBOVm93YkRFTE1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFUxdmRXNTBZV2x1SUZacFpYY3hFekFSQmdOVkJBb1RDa2R2YjJkc1pTQk1URU14R3pBWkJnTlZCQU1URW1GMGRHVnpkQzVoYm1SeWIybGtMbU52YlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTmpYa3owZUsxU0U0bSsvRzV3T28rWEdTRUNycWRuODhzQ3BSN2ZzMTRmSzBSaDNaQ1laTEZIcUJrNkFtWlZ3Mks5RkcwTzlyUlBlUURJVlJ5RTMwUXVuUzl1Z0hDNGVnOW92dk9tK1FkWjJwOTNYaHp1blFFaFVXWEN4QURJRUdKSzNTMmFBZnplOTlQTFMyOWhMY1F1WVhIRGFDN09acU5ub3NpT0dpZnM4djFqaTZIL3hobHRDWmUybEorN0d1dHpleEtweHZwRS90WlNmYlk5MDVxU2xCaDlmcGowMTVjam5RRmtVc0FVd21LVkFVdWVVejR0S2NGSzRwZXZOTGF4RUFsK09raWxNdElZRGFjRDVuZWw0eEppeXM0MTNoYWdxVzBXaGg1RlAzOWhHazlFL0J3UVRqYXpTeEdkdlgwbTZ4RlloaC8yVk15WmpUNEt6UEpFQ0F3RUFBYU9DQWxnd2dnSlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBVEFNQmdOVkhSTUJBZjhFQWpBQU1CMEdBMVVkRGdRV0JCUXFCUXdHV29KQmExb1RLcXVwbzRXNnhUNmoyREFmQmdOVkhTTUVHREFXZ0JTWTBmaHVFT3ZQbSt4Z254aVFHNkRyZlFuOUt6QmtCZ2dyQmdFRkJRY0JBUVJZTUZZd0p3WUlLd1lCQlFVSE1BR0dHMmgwZEhBNkx5OXZZM053TG5CcmFTNW5iMjluTDJkMGN6RnZNVEFyQmdnckJnRUZCUWN3QW9ZZmFIUjBjRG92TDNCcmFTNW5iMjluTDJkemNqSXZSMVJUTVU4eExtTnlkREFkQmdOVkhSRUVGakFVZ2hKaGRIUmxjM1F1WVc1a2NtOXBaQzVqYjIwd0lRWURWUjBnQkJvd0dEQUlCZ1puZ1F3QkFnSXdEQVlLS3dZQkJBSFdlUUlGQXpBdkJnTlZIUjhFS0RBbU1DU2dJcUFnaGg1b2RIUndPaTh2WTNKc0xuQnJhUzVuYjI5bkwwZFVVekZQTVM1amNtd3dnZ0VFQmdvckJnRUVBZFo1QWdRQ0JJSDFCSUh5QVBBQWR3Q2t1UW1RdEJoWUZJZTdFNkxNWjNBS1BEV1lCUGtiMzdqamQ4ME95QTNjRUFBQUFXWmREM1BMQUFBRUF3QklNRVlDSVFDU1pDV2VMSnZzaVZXNkNnK2dqLzl3WVRKUnp1NEhpcWU0ZVk0Yy9teXpqZ0loQUxTYmkvVGh6Y3pxdGlqM2RrM3ZiTGNJVzNMbDJCMG83NUdRZGhNaWdiQmdBSFVBVmhRR21pL1h3dXpUOWVHOVJMSSt4MFoydWJ5WkVWekE3NVNZVmRhSjBOMEFBQUZtWFE5ejVBQUFCQU1BUmpCRUFpQmNDd0E5ajdOVEdYUDI3OHo0aHIvdUNIaUFGTHlvQ3EySzAreUxSd0pVYmdJZ2Y4Z0hqdnB3Mm1CMUVTanEyT2YzQTBBRUF3Q2tuQ2FFS0ZVeVo3Zi9RdEl3RFFZSktvWklodmNOQVFFTEJRQURnZ0VCQUk5blRmUktJV2d0bFdsM3dCTDU1RVRWNmthenNwaFcxeUFjNUR1bTZYTzQxa1p6d0o2MXdKbWRSUlQvVXNDSXkxS0V0MmMwRWpnbG5KQ0YyZWF3Y0VXbExRWTJYUEx5RmprV1FOYlNoQjFpNFcyTlJHelBodDNtMWI0OWhic3R1WE02dFg1Q3lFSG5UaDhCb200L1dsRmloemhnbjgxRGxkb2d6L0syVXdNNlM2Q0IvU0V4a2lWZnYremJKMHJqdmc5NEFsZGpVZlV3a0k5Vk5NakVQNWU4eWRCM29MbDZnbHBDZUY1ZGdmU1g0VTl4MzVvai9JSWQzVUUvZFBwYi9xZ0d2c2tmZGV6dG1VdGUvS1Ntcml3Y2dVV1dlWGZUYkkzenNpa3daYmtwbVJZS21qUG1odjRybGl6R0NHdDhQbjhwcThNMktEZi9QM2tWb3QzZTE4UT0iLCJNSUlFU2pDQ0F6S2dBd0lCQWdJTkFlTzBtcUdOaXFtQkpXbFF1REFOQmdrcWhraUc5dzBCQVFzRkFEQk1NU0F3SGdZRFZRUUxFeGRIYkc5aVlXeFRhV2R1SUZKdmIzUWdRMEVnTFNCU01qRVRNQkVHQTFVRUNoTUtSMnh2WW1Gc1UybG5iakVUTUJFR0ExVUVBeE1LUjJ4dlltRnNVMmxuYmpBZUZ3MHhOekEyTVRVd01EQXdOREphRncweU1URXlNVFV3TURBd05ESmFNRUl4Q3pBSkJnTlZCQVlUQWxWVE1SNHdIQVlEVlFRS0V4VkhiMjluYkdVZ1ZISjFjM1FnVTJWeWRtbGpaWE14RXpBUkJnTlZCQU1UQ2tkVVV5QkRRU0F4VHpFd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURRR005RjFJdk4wNXprUU85K3ROMXBJUnZKenp5T1RIVzVEekVaaEQyZVBDbnZVQTBRazI4RmdJQ2ZLcUM5RWtzQzRUMmZXQllrL2pDZkMzUjNWWk1kUy9kTjRaS0NFUFpSckF6RHNpS1VEelJybUJCSjV3dWRnem5kSU1ZY0xlL1JHR0ZsNXlPRElLZ2pFdi9TSkgvVUwrZEVhbHROMTFCbXNLK2VRbU1GKytBY3hHTmhyNTlxTS85aWw3MUkyZE44RkdmY2Rkd3VhZWo0YlhocDBMY1FCYmp4TWNJN0pQMGFNM1Q0SStEc2F4bUtGc2JqemFUTkM5dXpwRmxnT0lnN3JSMjV4b3luVXh2OHZObWtxN3pkUEdIWGt4V1k3b0c5aitKa1J5QkFCazdYckpmb3VjQlpFcUZKSlNQazdYQTBMS1cwWTN6NW96MkQwYzF0Skt3SEFnTUJBQUdqZ2dFek1JSUJMekFPQmdOVkhROEJBZjhFQkFNQ0FZWXdIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFBd0hRWURWUjBPQkJZRUZKalIrRzRRNjgrYjdHQ2ZHSkFib090OUNmMHJNQjhHQTFVZEl3UVlNQmFBRkp2aUIxZG5IQjdBYWdiZVdiU2FMZC9jR1lZdU1EVUdDQ3NHQVFVRkJ3RUJCQ2t3SnpBbEJnZ3JCZ0VGQlFjd0FZWVphSFIwY0RvdkwyOWpjM0F1Y0d0cExtZHZiMmN2WjNOeU1qQXlCZ05WSFI4RUt6QXBNQ2VnSmFBamhpRm9kSFJ3T2k4dlkzSnNMbkJyYVM1bmIyOW5MMmR6Y2pJdlozTnlNaTVqY213d1B3WURWUjBnQkRnd05qQTBCZ1puZ1F3QkFnSXdLakFvQmdnckJnRUZCUWNDQVJZY2FIUjBjSE02THk5d2Eya3VaMjl2Wnk5eVpYQnZjMmwwYjNKNUx6QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFHb0ErTm5uNzh5NnBSamQ5WGxRV05hN0hUZ2laL3IzUk5Ha21VbVlIUFFxNlNjdGk5UEVhanZ3UlQyaVdUSFFyMDJmZXNxT3FCWTJFVFV3Z1pRK2xsdG9ORnZoc085dHZCQ09JYXpwc3dXQzlhSjl4anU0dFdEUUg4TlZVNllaWi9YdGVEU0dVOVl6SnFQalk4cTNNRHhyem1xZXBCQ2Y1bzhtdy93SjRhMkc2eHpVcjZGYjZUOE1jRE8yMlBMUkw2dTNNNFR6czNBMk0xajZieWtKWWk4d1dJUmRBdktMV1p1L2F4QlZielltcW13a201ekxTRFc1bklBSmJFTENRQ1p3TUg1NnQyRHZxb2Z4czZCQmNDRklaVVNweHU2eDZ0ZDBWN1N2SkNDb3NpclNtSWF0ai85ZFNTVkRRaWJldDhxLzdVSzR2NFpVTjgwYXRuWnoxeWc9PSJdfQ.eyJub25jZSI6InpiNVE5NFVPaHFOWnRVUWEraWY0NnF1UDRwZWZQN2JnQWRpQ3hraDFZRGs9IiwidGltZXN0YW1wTXMiOjE1NDM0ODI1Njg4NTgsImFwa1BhY2thZ2VOYW1lIjoiY29tLmdvb2dsZS5hbmRyb2lkLmdtcyIsImFwa0RpZ2VzdFNoYTI1NiI6InIxYzZiTkJmQ0hjZHcvZWpKSE1NWjhoakIrU0xXa1BSM0lreTZjV1dhNE09IiwiY3RzUHJvZmlsZU1hdGNoIjp0cnVlLCJhcGtDZXJ0aWZpY2F0ZURpZ2VzdFNoYTI1NiI6WyI4UDFzVzBFUEpjc2x3N1V6UnNpWEw2NHcrTzUwRWQrUkJJQ3RheTFnMjRNPSJdLCJiYXNpY0ludGVncml0eSI6dHJ1ZX0.Lq9WpOJ_GilocvPCTbIN2K5FtppXW2fTQzCW2pvb1Bo5qOZnJ0oOYBUqMgxx-zghlluSkkIIfPTvYl2zZUQsY-SNlBx7JASqDbksMyRsdU9r1Jn8D2zEtipFgjmZUkozi7AngnHoA5d0Yp-NF6slmr_FLMpAOnLZY9lREw8Cxnmso3Ph7zYUu7O5SxaRGwj8eMKydYJYHa23h2C8acuQKgSWL2YlG9T-oKT0CJ8jOSrKnHr39eMo7PFX0464diUvXUsv_M9kRIIQqCP0LzilGMdJVUrvFU7kg8csnFP6KMDfY70RGZ5ey3eNqs_D5-pjPfC4XPsPsksmy_wf-3UOmw", # noqa E501 } auth_data = AuthenticatorData( bytes.fromhex( "720c20fde835785e0f5ebcad8ef6a7bd88804a91612a2e820e0059b8d5358797450000000000000000000000000000000000000000004101c8fd9b533d6adacf6710ebcfb39f6361c4d7e8787db47dc0a75ae0e7c862198c9c83b81ef2547bb5669314095fc846af4ecac6875f7b230cac7359c76b0c20f7a5010203262001215820a28851e2d411b5b2c289da50d41cc41be88498941fc256dab500b21c8dafe8d1225820d289dd467715be06a622771a7b21e1bbe2372f8713d20dd7888a6e7ae1845ca8" # noqa E501 ) ) client_param = bytes.fromhex( "8422c80f3428e4e6465f76ebc8a4a93759a0a2e1fb845ee5eea7a02027408520" ) res = attestation.verify(statement, auth_data, client_param) self.assertEqual(res.attestation_type, AttestationType.BASIC) verify_x509_chain(res.trust_path + [_GSR2_DER]) def test_apple_attestation(self): attestation = Attestation.for_type("apple")() self.assertIsInstance(attestation, AppleAttestation) statement = { "alg": -7, "x5c": [ bytes.fromhex( "30820242308201c9a00302010202060176af5359ff300a06082a8648ce3d0403023048311c301a06035504030c134170706c6520576562417574686e204341203131133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e6961301e170d3230313232383136323732345a170d3230313233313136323732345a3081913149304706035504030c4038303966626331313065613835663233613862323435616563363136333530663337646665393632313232373336653431663862646365663334366138306439311a3018060355040b0c114141412043657274696669636174696f6e31133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e69613059301306072a8648ce3d020106082a8648ce3d030107034200041f46a2f159fde354598cdd47e005f1b6e7c9f00ed2a941ec7a88d222f5bcf55d6b078bc5b0be9552d85a974921f5bb848e2bbc3aecd6f71a386d2c87d6eafd37a3553053300c0603551d130101ff04023000300e0603551d0f0101ff0404030204f0303306092a864886f76364080204263024a1220420e56fb6212b3aae885294464fb10184b7fea62c48a6d78e61194e07ae6dacc132300a06082a8648ce3d040302036700306402301de8f0f238eee4f5ae80c59290b51e8c3f79397bf198e444ba162d4fccaab8558b072cf00a7c662f9058ff2a98af61ae0230149403b9643066e73a98d3659563dc4da49bf84e82b2b5bbeaf57755646fa243f36344d44b80a5798203bca023e030c7" # noqa E501 ), bytes.fromhex( "30820234308201baa003020102021056255395c7a7fb40ebe228d8260853b6300a06082a8648ce3d040303304b311f301d06035504030c164170706c6520576562417574686e20526f6f7420434131133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e6961301e170d3230303331383138333830315a170d3330303331333030303030305a3048311c301a06035504030c134170706c6520576562417574686e204341203131133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e69613076301006072a8648ce3d020106052b8104002203620004832e872f261491810225b9f5fcd6bb6378b5f55f3fcb045bc735993475fd549044df9bfe19211765c69a1dda050b38d45083401a434fb24d112d56c3e1cfbfcb9891fec0696081bef96cbc77c88dddaf46a5aee1dd515b5afaab93be9c0b2691a366306430120603551d130101ff040830060101ff020100301f0603551d2304183016801426d764d9c578c25a67d1a7de6b12d01b63f1c6d7301d0603551d0e04160414ebae82c4ffa1ac5b51d4cf24610500be63bd7788300e0603551d0f0101ff040403020106300a06082a8648ce3d0403030368003065023100dd8b1a3481a5fad9dbb4e7657b841e144c27b75b876a4186c2b1475750337227efe554457ef648950c632e5c483e70c102302c8a6044dc201fcfe59bc34d2930c1487851d960ed6a75f1eb4acabe38cd25b897d0c805bef0c7f78b07a571c6e80e07" # noqa E501 ), ], } auth_data = AuthenticatorData( bytes.fromhex( "c46cef82ad1b546477591d008b08759ec3e6d2ecb4f39474bfea6969925d03b7450000000000000000000000000000000000000000001473d9429f4052d84debd035eb5bb7e716e3b81863a50102032620012158201f46a2f159fde354598cdd47e005f1b6e7c9f00ed2a941ec7a88d222f5bcf55d2258206b078bc5b0be9552d85a974921f5bb848e2bbc3aecd6f71a386d2c87d6eafd37" # noqa E501 ) ) client_param = bytes.fromhex( "0d3ce80fabbc3adb9dd891deabb8db84603ea1fe2da8b5d4b46d6591aab342f3" ) res = attestation.verify(statement, auth_data, client_param) self.assertEqual(res.attestation_type, AttestationType.ANON_CA) self.assertEqual(len(res.trust_path), 2) verify_x509_chain(res.trust_path) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1688637503.573279 fido2-1.1.2/tests/test_cbor.py0000644000000000000000000001545614451510100013146 0ustar00# coding=utf-8 # Copyright (c) 2013 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 fido2 import cbor import unittest _TEST_VECTORS = [ ("00", 0), ("01", 1), ("0a", 10), ("17", 23), ("1818", 24), ("1819", 25), ("1864", 100), ("1903e8", 1000), ("1a000f4240", 1000000), ("1b000000e8d4a51000", 1000000000000), ("1bffffffffffffffff", 18446744073709551615), # ('c249010000000000000000', 18446744073709551616), ("3bffffffffffffffff", -18446744073709551616), # ('c349010000000000000000', -18446744073709551617), ("20", -1), ("29", -10), ("3863", -100), ("3903e7", -1000), # ('f90000', 0.0), # ('f98000', -0.0), # ('f93c00', 1.0), # ('fb3ff199999999999a', 1.1), # ('f93e00', 1.5), # ('f97bff', 65504.0), # ('fa47c35000', 100000.0), # ('fa7f7fffff', 3.4028234663852886e+38), # ('fb7e37e43c8800759c', 1e+300), # ('f90001', 5.960464477539063e-08), # ('f90400', 6.103515625e-05), # ('f9c400', -4.0), # ('fbc010666666666666', -4.1), # ('f97c00', None), # ('f97e00', None), # ('f9fc00', None), # ('fa7f800000', None), # ('fa7fc00000', None), # ('faff800000', None), # ('fb7ff0000000000000', None), # ('fb7ff8000000000000', None), # ('fbfff0000000000000', None), ("f4", False), ("f5", True), # ('f6', None), # ('f7', None), # ('f0', None), # ('f818', None), # ('f8ff', None), # ('c074323031332d30332d32315432303a30343a30305a', None), # ('c11a514b67b0', None), # ('c1fb41d452d9ec200000', None), # ('d74401020304', None), # ('d818456449455446', None), # ('d82076687474703a2f2f7777772e6578616d706c652e636f6d', None), ("40", b""), ("4401020304", b"\1\2\3\4"), ("60", ""), ("6161", "a"), ("6449455446", "IETF"), ("62225c", '"\\'), ("62c3bc", "ü"), ("63e6b0b4", "水"), ("64f0908591", "𐅑"), ("80", []), ("83010203", [1, 2, 3]), ("8301820203820405", [1, [2, 3], [4, 5]]), ( "98190102030405060708090a0b0c0d0e0f101112131415161718181819", [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, ], ), ("a0", {}), ("a201020304", {1: 2, 3: 4}), ("a26161016162820203", {"a": 1, "b": [2, 3]}), ("826161a161626163", ["a", {"b": "c"}]), ( "a56161614161626142616361436164614461656145", {"c": "C", "d": "D", "a": "A", "b": "B", "e": "E"}, ), # ('5f42010243030405ff', None), # ('7f657374726561646d696e67ff', 'streaming'), # ('9fff', []), # ('9f018202039f0405ffff', [1, [2, 3], [4, 5]]), # ('9f01820203820405ff', [1, [2, 3], [4, 5]]), # ('83018202039f0405ff', [1, [2, 3], [4, 5]]), # ('83019f0203ff820405', [1, [2, 3], [4, 5]]), # ('9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]), # noqa E501 # ('bf61610161629f0203ffff', {'a': 1, 'b': [2, 3]}), # ('826161bf61626163ff', ['a', {'b': 'c'}]), # ('bf6346756ef563416d7421ff', {'Amt': -2, 'Fun': True}), ] def cbor2hex(data): return cbor.encode(data).hex() class TestCborTestVectors(unittest.TestCase): """ From https://github.com/cbor/test-vectors Unsupported values are commented out. """ def test_vectors(self): for data, value in _TEST_VECTORS: try: self.assertEqual(cbor.decode_from(bytes.fromhex(data)), (value, b"")) self.assertEqual(cbor.decode(bytes.fromhex(data)), value) self.assertEqual(cbor2hex(value), data) except Exception: print("\nERROR in test vector, %s" % data) raise class TestFidoCanonical(unittest.TestCase): """ As defined in section 6 of: https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html """ def test_integers(self): self.assertEqual(cbor2hex(0), "00") self.assertEqual(cbor2hex(0), "00") self.assertEqual(cbor2hex(23), "17") self.assertEqual(cbor2hex(24), "1818") self.assertEqual(cbor2hex(255), "18ff") self.assertEqual(cbor2hex(256), "190100") self.assertEqual(cbor2hex(65535), "19ffff") self.assertEqual(cbor2hex(65536), "1a00010000") self.assertEqual(cbor2hex(4294967295), "1affffffff") self.assertEqual(cbor2hex(4294967296), "1b0000000100000000") self.assertEqual(cbor2hex(-1), "20") self.assertEqual(cbor2hex(-24), "37") self.assertEqual(cbor2hex(-25), "3818") def test_key_order(self): self.assertEqual(cbor2hex({"3": 0, b"2": 0, 1: 0}), "a30100413200613300") self.assertEqual(cbor2hex({"3": 0, b"": 0, 256: 0}), "a3190100004000613300") self.assertEqual( cbor2hex({4294967296: 0, 255: 0, 256: 0, 0: 0}), "a4000018ff00190100001b000000010000000000", ) self.assertEqual( cbor2hex({b"22": 0, b"3": 0, b"111": 0}), "a3413300423232004331313100" ) self.assertEqual( cbor2hex({b"001": 0, b"003": 0, b"002": 0}), "a3433030310043303032004330303300", ) self.assertEqual(cbor2hex({True: 0, False: 0}), "a2f400f500") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/test_client.py0000644000000000000000000002322214413232070013472 0ustar00# coding=utf-8 # Copyright (c) 2013 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. import unittest from unittest import mock from fido2 import cbor from fido2.utils import sha256, websafe_encode from fido2.hid import CAPABILITY from fido2.ctap import CtapError from fido2.ctap1 import RegistrationData from fido2.ctap2 import Info, AttestationResponse from fido2.client import ClientError, Fido2Client from fido2.webauthn import ( PublicKeyCredentialCreationOptions, AttestationObject, CollectedClientData, ) APP_ID = "https://foo.example.com" REG_DATA = RegistrationData( bytes.fromhex( "0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c253082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" # noqa E501 ) ) rp = {"id": "example.com", "name": "Example RP"} user = {"id": websafe_encode(b"user_id"), "name": "A. User"} challenge = b"Y2hhbGxlbmdl" _INFO_NO_PIN = bytes.fromhex( "a60182665532465f5632684649444f5f325f3002826375766d6b686d61632d7365637265740350f8a011f38c0a4d15800617111f9edc7d04a462726bf5627570f564706c6174f469636c69656e7450696ef4051904b0068101" # noqa E501 ) _MC_RESP = bytes.fromhex( "a301667061636b6564025900c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae12410000001cf8a011f38c0a4d15800617111f9edc7d0040fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a529003a363616c67266373696758483046022100cc1ef43edf07de8f208c21619c78a565ddcf4150766ad58781193be8e0a742ed022100f1ed7c7243e45b7d8e5bda6b1abf10af7391789d1ef21b70bd69fed48dba4cb163783563815901973082019330820138a003020102020900859b726cb24b4c29300a06082a8648ce3d0403023047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e301e170d3136313230343131353530305a170d3236313230323131353530305a3047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3059301306072a8648ce3d020106082a8648ce3d03010703420004ad11eb0e8852e53ad5dfed86b41e6134a18ec4e1af8f221a3c7d6e636c80ea13c3d504ff2e76211bb44525b196c44cb4849979cf6f896ecd2bb860de1bf4376ba30d300b30090603551d1304023000300a06082a8648ce3d0403020349003046022100e9a39f1b03197525f7373e10ce77e78021731b94d0c03f3fda1fd22db3d030e7022100c4faec3445a820cf43129cdb00aabefd9ae2d874f9c5d343cb2f113da23723f3" # noqa E501 ) class TestFido2Client(unittest.TestCase): def test_ctap1_info(self): dev = mock.Mock() dev.capabilities = 0 client = Fido2Client(dev, APP_ID) self.assertEqual(client.info.versions, ["U2F_V2"]) self.assertEqual(client.info.pin_uv_protocols, []) @mock.patch("fido2.client.Ctap2") def test_make_credential_wrong_app_id(self, PatchedCtap2): dev = mock.Mock() dev.capabilities = CAPABILITY.CBOR ctap2 = mock.MagicMock() ctap2.get_info.return_value = Info.from_dict(cbor.decode(_INFO_NO_PIN)) PatchedCtap2.return_value = ctap2 client = Fido2Client(dev, APP_ID) try: client.make_credential( PublicKeyCredentialCreationOptions( {"id": "bar.example.com", "name": "Invalid RP"}, user, challenge, [{"type": "public-key", "alg": -7}], ) ) self.fail("make_credential did not raise error") except ClientError as e: self.assertEqual(e.code, ClientError.ERR.BAD_REQUEST) @mock.patch("fido2.client.Ctap2") def test_make_credential_existing_key(self, PatchedCtap2): dev = mock.Mock() dev.capabilities = CAPABILITY.CBOR ctap2 = mock.MagicMock() ctap2.get_info.return_value = Info.from_dict(cbor.decode(_INFO_NO_PIN)) ctap2.info = ctap2.get_info() ctap2.make_credential.side_effect = CtapError(CtapError.ERR.CREDENTIAL_EXCLUDED) PatchedCtap2.return_value = ctap2 client = Fido2Client(dev, APP_ID) try: client.make_credential( PublicKeyCredentialCreationOptions( rp, user, challenge, [{"type": "public-key", "alg": -7}], authenticator_selection={"userVerification": "discouraged"}, ) ) self.fail("make_credential did not raise error") except ClientError as e: self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE) ctap2.make_credential.assert_called_once() @mock.patch("fido2.client.Ctap2") def test_make_credential_ctap2(self, PatchedCtap2): dev = mock.Mock() dev.capabilities = CAPABILITY.CBOR ctap2 = mock.MagicMock() ctap2.get_info.return_value = Info.from_dict(cbor.decode(_INFO_NO_PIN)) ctap2.info = ctap2.get_info() ctap2.make_credential.return_value = AttestationResponse.from_dict( cbor.decode(_MC_RESP) ) PatchedCtap2.return_value = ctap2 client = Fido2Client(dev, APP_ID) response = client.make_credential( PublicKeyCredentialCreationOptions( rp, user, challenge, [{"type": "public-key", "alg": -7}], timeout=1000, authenticator_selection={"userVerification": "discouraged"}, ) ) self.assertIsInstance(response.attestation_object, AttestationObject) self.assertIsInstance(response.client_data, CollectedClientData) ctap2.make_credential.assert_called_with( response.client_data.hash, rp, user, [{"type": "public-key", "alg": -7}], None, None, None, None, None, None, event=mock.ANY, on_keepalive=mock.ANY, ) self.assertEqual(response.client_data.origin, APP_ID) self.assertEqual(response.client_data.type, "webauthn.create") self.assertEqual(response.client_data.challenge, challenge) def test_make_credential_ctap1(self): dev = mock.Mock() dev.capabilities = 0 # No CTAP2 client = Fido2Client(dev, APP_ID) ctap1_mock = mock.MagicMock() ctap1_mock.get_version.return_value = "U2F_V2" ctap1_mock.register.return_value = REG_DATA client._backend.ctap1 = ctap1_mock response = client.make_credential( PublicKeyCredentialCreationOptions( rp, user, challenge, [{"type": "public-key", "alg": -7}] ) ) self.assertIsInstance(response.attestation_object, AttestationObject) self.assertIsInstance(response.client_data, CollectedClientData) client_data = response.client_data ctap1_mock.register.assert_called_with( client_data.hash, sha256(rp["id"].encode()) ) self.assertEqual(client_data.origin, APP_ID) self.assertEqual(client_data.type, "webauthn.create") self.assertEqual(client_data.challenge, challenge) self.assertEqual(response.attestation_object.fmt, "fido-u2f") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/test_cose.py0000644000000000000000000001455314413232070013154 0ustar00# Copyright (c) 2013 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 fido2 import cbor from fido2.cose import CoseKey, ES256, RS256, EdDSA, UnsupportedKey from binascii import a2b_hex import unittest _ES256_KEY = a2b_hex( b"A5010203262001215820A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1225820FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C" # noqa E501 ) _RS256_KEY = a2b_hex( b"A401030339010020590100B610DCE84B65029FAE24F7BF8A1730D37BC91435642A628E691E9B030BF3F7CEC59FF91CBE82C54DE16C136FA4FA8A58939B5A950B32E03073592FEC8D8B33601C04F70E5E2D5CF7B4E805E1990EA5A86928A1B390EB9026527933ACC03E6E41DC0BE40AA5EB7B9B460743E4DD80895A758FB3F3F794E5E9B8310D3A60C28F2410D95CF6E732749A243A30475267628B456DE770BC2185BBED1D451ECB0062A3D132C0E4D842E0DDF93A444A3EE33A85C2E913156361713155F1F1DC64E8E68ED176466553BBDE669EB82810B104CB4407D32AE6316C3BD6F382EC3AE2C5FD49304986D64D92ED11C25B6C5CF1287233545A987E9A3E169F99790603DBA5C8AD2143010001" # noqa E501 ) _EdDSA_KEY = a2b_hex( b"a4010103272006215820ee9b21803405d3cf45601e58b6f4c06ea93862de87d3af903c5870a5016e86f5" # noqa E501 ) class TestCoseKey(unittest.TestCase): def test_ES256_parse_verify(self): key = CoseKey.parse(cbor.decode(_ES256_KEY)) self.assertIsInstance(key, ES256) self.assertEqual( key, { 1: 2, 3: -7, -1: 1, -2: a2b_hex( b"A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1" ), -3: a2b_hex( b"FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C" ), }, ) key.verify( a2b_hex( b"0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12010000002C" # noqa E501 + b"7B89F12A9088B0F5EE0EF8F6718BCCC374249C31AEEBAEB79BD0450132CD536C" ), a2b_hex( b"304402202B3933FE954A2D29DE691901EB732535393D4859AAA80D58B08741598109516D0220236FBE6B52326C0A6B1CFDC6BF0A35BDA92A6C2E41E40C3A1643428D820941E0" # noqa E501 ), ) def test_RS256_parse_verify(self): key = CoseKey.parse(cbor.decode(_RS256_KEY)) self.assertIsInstance(key, RS256) self.assertEqual( key, { 1: 3, 3: -257, -1: a2b_hex( b"B610DCE84B65029FAE24F7BF8A1730D37BC91435642A628E691E9B030BF3F7CEC59FF91CBE82C54DE16C136FA4FA8A58939B5A950B32E03073592FEC8D8B33601C04F70E5E2D5CF7B4E805E1990EA5A86928A1B390EB9026527933ACC03E6E41DC0BE40AA5EB7B9B460743E4DD80895A758FB3F3F794E5E9B8310D3A60C28F2410D95CF6E732749A243A30475267628B456DE770BC2185BBED1D451ECB0062A3D132C0E4D842E0DDF93A444A3EE33A85C2E913156361713155F1F1DC64E8E68ED176466553BBDE669EB82810B104CB4407D32AE6316C3BD6F382EC3AE2C5FD49304986D64D92ED11C25B6C5CF1287233545A987E9A3E169F99790603DBA5C8AD" # noqa E501 ), -2: a2b_hex(b"010001"), }, ) key.verify( a2b_hex( b"0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12010000002E" # noqa E501 + b"CC9340FD84950987BA667DBE9B2C97C7241E15E2B54869A0DD1CE2013C4064B8" ), a2b_hex( b"071B707D11F0E7F62861DFACA89C4E674321AD8A6E329FDD40C7D6971348FBB0514E7B2B0EFE215BAAC0365C4124A808F8180D6575B710E7C01DAE8F052D0C5A2CE82F487C656E7AD824F3D699BE389ADDDE2CBF39E87A8955E93202BAE8830AB4139A7688DFDAD849F1BB689F3852BA05BED70897553CC44704F6941FD1467AD6A46B4DAB503716D386FE7B398E78E0A5A8C4040539D2C9BFA37E4D94F96091FFD1D194DE2CA58E9124A39757F013801421E09BD261ADA31992A8B0386A80AF51A87BD0CEE8FDAB0D4651477670D4C7B245489BED30A57B83964DB79418D5A4F5F2E5ABCA274426C9F90B007A962AE15DFF7343AF9E110746E2DB9226D785C6" # noqa E501 ), ) def test_EdDSA_parse_verify(self): key = CoseKey.parse(cbor.decode(_EdDSA_KEY)) self.assertIsInstance(key, EdDSA) self.assertEqual( key, { 1: 1, 3: -8, -1: 6, -2: a2b_hex( "EE9B21803405D3CF45601E58B6F4C06EA93862DE87D3AF903C5870A5016E86F5" ), }, ) key.verify( a2b_hex( b"a379a6f6eeafb9a55e378c118034e2751e682fab9f2d30ab13d2125586ce1947010000000500a11a323057d1103784ddff99a354ddd42348c2f00e88d8977b916cabf92268" # noqa E501 ), a2b_hex( b"e8c927ef1a57c738ff4ba8d6f90e06d837a5219eee47991f96b126b0685d512520c9c2eedebe4b88ff2de2b19cb5f8686efc7c4261e9ed1cb3ac5de50869be0a" # noqa E501 ), ) def test_unsupported_key(self): key = CoseKey.parse({1: 4711, 3: 4712, -1: b"123", -2: b"456"}) self.assertIsInstance(key, UnsupportedKey) self.assertEqual(key, {1: 4711, 3: 4712, -1: b"123", -2: b"456"}) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/test_ctap1.py0000644000000000000000000002001514413232070013221 0ustar00# Copyright (c) 2013 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. import unittest from unittest import mock from fido2.ctap1 import Ctap1, ApduError class TestCtap1(unittest.TestCase): def test_send_apdu_ok(self): ctap = Ctap1(mock.MagicMock()) ctap.device.call.return_value = b"response\x90\x00" self.assertEqual(b"response", ctap.send_apdu(1, 2, 3, 4, b"foobar")) ctap.device.call.assert_called_with(0x03, b"\1\2\3\4\0\0\6foobar\0\0") def test_send_apdu_err(self): ctap = Ctap1(mock.MagicMock()) ctap.device.call.return_value = b"err\x6a\x80" try: ctap.send_apdu(1, 2, 3, 4, b"foobar") self.fail("send_apdu did not raise error") except ApduError as e: self.assertEqual(e.code, 0x6A80) self.assertEqual(e.data, b"err") ctap.device.call.assert_called_with(0x03, b"\1\2\3\4\0\0\6foobar\0\0") def test_get_version(self): ctap = Ctap1(mock.MagicMock()) ctap.device.call.return_value = b"U2F_V2\x90\x00" self.assertEqual("U2F_V2", ctap.get_version()) ctap.device.call.assert_called_with(0x03, b"\0\3\0\0\0\0\0\0\0") def test_register(self): ctap = Ctap1(mock.MagicMock()) ctap.device.call.return_value = ( bytes.fromhex( "0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c253082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" # noqa E501 ) + b"\x90\x00" ) client_param = bytes.fromhex( "4142d21c00d94ffb9d504ada8f99b721f4b191ae4e37ca0140f696b6983cfacb" ) app_param = bytes.fromhex( "f0e6a6a97042a4f1f1c87f5f7d44315b2d852c2df5c7991cc66241bf7072d1c4" ) resp = ctap.register(client_param, app_param) ctap.device.call.assert_called_with( 0x03, b"\0\1\0\0\0\0\x40" + client_param + app_param + b"\0\0" ) self.assertEqual( resp.public_key, bytes.fromhex( "04b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9" # noqa E501 ), ) self.assertEqual( resp.key_handle, bytes.fromhex( "2a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c25" # noqa E501 ), ) self.assertEqual( resp.certificate, bytes.fromhex( "3082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df" # noqa E501 ), ) self.assertEqual( resp.signature, bytes.fromhex( "304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" # noqa E501 ), ) resp.verify(app_param, client_param) def test_authenticate(self): ctap = Ctap1(mock.MagicMock()) ctap.device.call.return_value = ( bytes.fromhex( "0100000001304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f" # noqa E501 ) + b"\x90\x00" ) client_param = bytes.fromhex( "ccd6ee2e47baef244d49a222db496bad0ef5b6f93aa7cc4d30c4821b3b9dbc57" ) app_param = bytes.fromhex( "4b0be934baebb5d12d26011b69227fa5e86df94e7d94aa2949a89f2d493992ca" ) key_handle = b"\3" * 64 resp = ctap.authenticate(client_param, app_param, key_handle) ctap.device.call.assert_called_with( 0x03, b"\0\2\3\0\0\0\x81" + client_param + app_param + b"\x40" + key_handle + b"\0\0", ) self.assertEqual(resp.user_presence, 1) self.assertEqual(resp.counter, 1) self.assertEqual( resp.signature, bytes.fromhex( "304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f" # noqa E501 ), ) public_key = bytes.fromhex( "04d368f1b665bade3c33a20f1e429c7750d5033660c019119d29aa4ba7abc04aa7c80a46bbe11ca8cb5674d74f31f8a903f6bad105fb6ab74aefef4db8b0025e1d" # noqa E501 ) resp.verify(app_param, client_param, public_key) key_handle = b"\4" * 8 ctap.authenticate(client_param, app_param, key_handle) ctap.device.call.assert_called_with( 0x03, b"\0\2\3\0\0\0\x49" + client_param + app_param + b"\x08" + key_handle + b"\0\0", ) ctap.authenticate(client_param, app_param, key_handle, True) ctap.device.call.assert_called_with( 0x03, b"\0\2\7\0\0\0\x49" + client_param + app_param + b"\x08" + key_handle + b"\0\0", ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/test_ctap2.py0000644000000000000000000004572314413232070013237 0ustar00# Copyright (c) 2013 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. import unittest from unittest import mock from fido2.ctap1 import RegistrationData from fido2.ctap2 import ( Ctap2, ClientPin, PinProtocolV1, Info, AttestationResponse, AssertionResponse, ) from fido2.webauthn import AttestationObject, AuthenticatorData, AttestedCredentialData from fido2.attestation import Attestation from fido2 import cbor from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec _AAGUID = bytes.fromhex("F8A011F38C0A4D15800617111F9EDC7D") _INFO = bytes.fromhex( "a60182665532465f5632684649444f5f325f3002826375766d6b686d61632d7365637265740350f8a011f38c0a4d15800617111f9edc7d04a462726bf5627570f564706c6174f469636c69656e7450696ef4051904b0068101" # noqa E501 ) _INFO_EXTRA_KEY = bytes.fromhex( "A70182665532465F5632684649444F5F325F3002826375766D6B686D61632D7365637265740350F8A011F38C0A4D15800617111F9EDC7D04A462726BF5627570F564706C6174F469636C69656E7450696EF4051904B006810118631904D2" # noqa E501 ) class TestInfo(unittest.TestCase): def test_parse_bytes(self): info = Info.from_dict(cbor.decode(_INFO)) self.assertEqual(info.versions, ["U2F_V2", "FIDO_2_0"]) self.assertEqual(info.extensions, ["uvm", "hmac-secret"]) self.assertEqual(info.aaguid, _AAGUID) self.assertEqual( info.options, {"rk": True, "up": True, "plat": False, "clientPin": False} ) self.assertEqual(info.max_msg_size, 1200) self.assertEqual(info.pin_uv_protocols, [1]) assert info[0x01] == ["U2F_V2", "FIDO_2_0"] assert info[0x02] == ["uvm", "hmac-secret"] assert info[0x03] == _AAGUID assert info[0x04] == { "clientPin": False, "plat": False, "rk": True, "up": True, } assert info[0x05] == 1200 assert info[0x06] == [1] def test_info_with_extra_field(self): info = Info.from_dict(cbor.decode(_INFO_EXTRA_KEY)) self.assertEqual(info.versions, ["U2F_V2", "FIDO_2_0"]) _ATT_CRED_DATA = bytes.fromhex( "f8a011f38c0a4d15800617111f9edc7d0040fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a5290" # noqa E501 ) _CRED_ID = bytes.fromhex( "fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783" # noqa E501 ) _PUB_KEY = { 1: 2, 3: -7, -1: 1, -2: bytes.fromhex( "643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf" ), -3: bytes.fromhex( "171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a5290" ), } class TestAttestedCredentialData(unittest.TestCase): def test_parse_bytes(self): data = AttestedCredentialData(_ATT_CRED_DATA) self.assertEqual(data.aaguid, _AAGUID) self.assertEqual(data.credential_id, _CRED_ID) self.assertEqual(data.public_key, _PUB_KEY) def test_create_from_args(self): data = AttestedCredentialData.create(_AAGUID, _CRED_ID, _PUB_KEY) self.assertEqual(_ATT_CRED_DATA, data) _AUTH_DATA_MC = bytes.fromhex( "0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12410000001CF8A011F38C0A4D15800617111F9EDC7D0040FE3AAC036D14C1E1C65518B698DD1DA8F596BC33E11072813466C6BF3845691509B80FB76D59309B8D39E0A93452688F6CA3A39A76F3FC52744FB73948B15783A5010203262001215820643566C206DD00227005FA5DE69320616CA268043A38F08BDE2E9DC45A5CAFAF225820171353B2932434703726AAE579FA6542432861FE591E481EA22D63997E1A5290" # noqa E501 ) _AUTH_DATA_GA = bytes.fromhex( "0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12010000001D" ) _RP_ID_HASH = bytes.fromhex( "0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12" ) class TestAuthenticatorData(unittest.TestCase): def test_parse_bytes_make_credential(self): data = AuthenticatorData(_AUTH_DATA_MC) self.assertEqual(data.rp_id_hash, _RP_ID_HASH) self.assertEqual(data.flags, 0x41) self.assertEqual(data.counter, 28) self.assertEqual(data.credential_data, _ATT_CRED_DATA) self.assertIsNone(data.extensions) def test_parse_bytes_get_assertion(self): data = AuthenticatorData(_AUTH_DATA_GA) self.assertEqual(data.rp_id_hash, _RP_ID_HASH) self.assertEqual(data.flags, 0x01) self.assertEqual(data.counter, 29) self.assertIsNone(data.credential_data) self.assertIsNone(data.extensions) _MC_RESP = bytes.fromhex( "a301667061636b65640258c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae12410000001cf8a011f38c0a4d15800617111f9edc7d0040fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a529003a363616c67266373696758483046022100cc1ef43edf07de8f208c21619c78a565ddcf4150766ad58781193be8e0a742ed022100f1ed7c7243e45b7d8e5bda6b1abf10af7391789d1ef21b70bd69fed48dba4cb163783563815901973082019330820138a003020102020900859b726cb24b4c29300a06082a8648ce3d0403023047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e301e170d3136313230343131353530305a170d3236313230323131353530305a3047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3059301306072a8648ce3d020106082a8648ce3d03010703420004ad11eb0e8852e53ad5dfed86b41e6134a18ec4e1af8f221a3c7d6e636c80ea13c3d504ff2e76211bb44525b196c44cb4849979cf6f896ecd2bb860de1bf4376ba30d300b30090603551d1304023000300a06082a8648ce3d0403020349003046022100e9a39f1b03197525f7373e10ce77e78021731b94d0c03f3fda1fd22db3d030e7022100c4faec3445a820cf43129cdb00aabefd9ae2d874f9c5d343cb2f113da23723f3" # noqa E501 ) _GA_RESP = bytes.fromhex( "a301a26269645840fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b1578364747970656a7075626c69632d6b65790258250021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae12010000001d035846304402206765cbf6e871d3af7f01ae96f06b13c90f26f54b905c5166a2c791274fc2397102200b143893586cc799fba4da83b119eaea1bd80ac3ce88fcedb3efbd596a1f4f63" # noqa E501 ) _CRED_ID = bytes.fromhex( "FE3AAC036D14C1E1C65518B698DD1DA8F596BC33E11072813466C6BF3845691509B80FB76D59309B8D39E0A93452688F6CA3A39A76F3FC52744FB73948B15783" # noqa E501 ) _CRED = {"type": "public-key", "id": _CRED_ID} _SIGNATURE = bytes.fromhex( "304402206765CBF6E871D3AF7F01AE96F06B13C90F26F54B905C5166A2C791274FC2397102200B143893586CC799FBA4DA83B119EAEA1BD80AC3CE88FCEDB3EFBD596A1F4F63" # noqa E501 ) class TestAttestationObject(unittest.TestCase): def test_fido_u2f_attestation(self): att = AttestationObject.from_ctap1( bytes.fromhex( "1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE" ), RegistrationData( bytes.fromhex( "0504E87625896EE4E46DC032766E8087962F36DF9DFE8B567F3763015B1990A60E1427DE612D66418BDA1950581EBC5C8C1DAD710CB14C22F8C97045F4612FB20C91403EBD89BF77EC509755EE9C2635EFAAAC7B2B9C5CEF1736C3717DA48534C8C6B654D7FF945F50B5CC4E78055BDD396B64F78DA2C5F96200CCD415CD08FE4200383082024A30820132A0030201020204046C8822300D06092A864886F70D01010B0500302E312C302A0603550403132359756269636F2055324620526F6F742043412053657269616C203435373230303633313020170D3134303830313030303030305A180F32303530303930343030303030305A302C312A302806035504030C2159756269636F205532462045452053657269616C203234393138323332343737303059301306072A8648CE3D020106082A8648CE3D030107034200043CCAB92CCB97287EE8E639437E21FCD6B6F165B2D5A3F3DB131D31C16B742BB476D8D1E99080EB546C9BBDF556E6210FD42785899E78CC589EBE310F6CDB9FF4A33B3039302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C020101040403020430300D06092A864886F70D01010B050003820101009F9B052248BC4CF42CC5991FCAABAC9B651BBE5BDCDC8EF0AD2C1C1FFB36D18715D42E78B249224F92C7E6E7A05C49F0E7E4C881BF2E94F45E4A21833D7456851D0F6C145A29540C874F3092C934B43D222B8962C0F410CEF1DB75892AF116B44A96F5D35ADEA3822FC7146F6004385BCB69B65C99E7EB6919786703C0D8CD41E8F75CCA44AA8AB725AD8E799FF3A8696A6F1B2656E631B1E40183C08FDA53FA4A8F85A05693944AE179A1339D002D15CABD810090EC722EF5DEF9965A371D415D624B68A2707CAD97BCDD1785AF97E258F33DF56A031AA0356D8E8D5EBCADC74E071636C6B110ACE5CC9B90DFEACAE640FF1BB0F1FE5DB4EFF7A95F060733F530450220324779C68F3380288A1197B6095F7A6EB9B1B1C127F66AE12A99FE8532EC23B9022100E39516AC4D61EE64044D50B415A6A4D4D84BA6D895CB5AB7A1AA7D081DE341FA" # noqa E501 ) ), ) Attestation.for_type(att.fmt)().verify( att.att_stmt, att.auth_data, bytes.fromhex( "687134968222EC17202E42505F8ED2B16AE22F16BB05B88C25DB9E602645F141" ), ) def test_packed_attestation(self): att = AttestationResponse.from_dict( cbor.decode( bytes.fromhex( "a301667061636b65640258c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae124100000003f8a011f38c0a4d15800617111f9edc7d004060a386206a3aacecbdbb22d601853d955fdc5d11adfbd1aa6a950d966b348c7663d40173714a9f987df6461beadfb9cd6419ffdfe4d4cf2eec1aa605a4f59bdaa50102032620012158200edb27580389494d74d2373b8f8c2e8b76fa135946d4f30d0e187e120b423349225820e03400d189e85a55de9ab0f538ed60736eb750f5f0306a80060fe1b13010560d03a363616c6726637369675847304502200d15daf337d727ab4719b4027114a2ac43cd565d394ced62c3d9d1d90825f0b3022100989615e7394c87f4ad91f8fdae86f7a3326df332b3633db088aac76bffb9a46b63783563815902bb308202b73082019fa00302010202041d31330d300d06092a864886f70d01010b0500302a3128302606035504030c1f59756269636f2050726576696577204649444f204174746573746174696f6e301e170d3138303332383036333932345a170d3139303332383036333932345a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203438393736333539373059301306072a8648ce3d020106082a8648ce3d030107034200047d71e8367cafd0ea6cf0d61e4c6a416ba5bb6d8fad52db2389ad07969f0f463bfdddddc29d39d3199163ee49575a3336c04b3309d607f6160c81e023373e0197a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e323013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c01010404120410f8a011f38c0a4d15800617111f9edc7d300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101009b904ceadbe1f1985486fead02baeaa77e5ab4e6e52b7e6a2666a4dc06e241578169193b63dadec5b2b78605a128b2e03f7fe2a98eaeb4219f52220995f400ce15d630cf0598ba662d7162459f1ad1fc623067376d4e4091be65ac1a33d8561b9996c0529ec1816d1710786384d5e8783aa1f7474cb99fe8f5a63a79ff454380361c299d67cb5cc7c79f0d8c09f8849b0500f6d625408c77cbbc26ddee11cb581beb7947137ad4f05aaf38bd98da10042ddcac277604a395a5b3eaa88a5c8bb27ab59c8127d59d6bbba5f11506bf7b75fda7561a0837c46f025fd54dcf1014fc8d17c859507ac57d4b1dea99485df0ba8f34d00103c3eef2ef3bbfec7a6613de" # noqa E501 ) ) ) Attestation.for_type(att.fmt)().verify( att.att_stmt, att.auth_data, bytes.fromhex( "985B6187D042FB1258892ED637CEC88617DDF5F6632351A545617AA2B75261BF" ), ) class TestCtap2(unittest.TestCase): def mock_ctap(self): device = mock.MagicMock() device.call.return_value = b"\0" + _INFO return Ctap2(device) def test_send_cbor_ok(self): ctap = self.mock_ctap() ctap.device.call.return_value = b"\0" + cbor.encode({1: b"response"}) self.assertEqual({1: b"response"}, ctap.send_cbor(2, b"foobar")) ctap.device.call.assert_called_with( 0x10, b"\2" + cbor.encode(b"foobar"), mock.ANY, None ) def test_get_info(self): ctap = self.mock_ctap() info = ctap.get_info() ctap.device.call.assert_called_with(0x10, b"\4", mock.ANY, None) self.assertIsInstance(info, Info) def test_make_credential(self): ctap = self.mock_ctap() ctap.device.call.return_value = b"\0" + _MC_RESP resp = ctap.make_credential(1, 2, 3, 4) ctap.device.call.assert_called_with( 0x10, b"\1" + cbor.encode({1: 1, 2: 2, 3: 3, 4: 4}), mock.ANY, None ) self.assertIsInstance(resp, AttestationResponse) self.assertEqual(resp, AttestationResponse.from_dict(cbor.decode(_MC_RESP))) self.assertEqual(resp.fmt, "packed") self.assertEqual(resp.auth_data, _AUTH_DATA_MC) self.assertSetEqual(set(resp.att_stmt.keys()), {"alg", "sig", "x5c"}) def test_get_assertion(self): ctap = self.mock_ctap() ctap.device.call.return_value = b"\0" + _GA_RESP resp = ctap.get_assertion(1, 2) ctap.device.call.assert_called_with( 0x10, b"\2" + cbor.encode({1: 1, 2: 2}), mock.ANY, None ) self.assertIsInstance(resp, AssertionResponse) self.assertEqual(resp, AssertionResponse.from_dict(cbor.decode(_GA_RESP))) self.assertEqual(resp.credential, _CRED) self.assertEqual(resp.auth_data, _AUTH_DATA_GA) self.assertEqual(resp.signature, _SIGNATURE) self.assertIsNone(resp.user) self.assertIsNone(resp.number_of_credentials) EC_PRIV = 0x7452E599FEE739D8A653F6A507343D12D382249108A651402520B72F24FE7684 EC_PUB_X = bytes.fromhex( "44D78D7989B97E62EA993496C9EF6E8FD58B8B00715F9A89153DDD9C4657E47F" ) EC_PUB_Y = bytes.fromhex( "EC802EE7D22BD4E100F12E48537EB4E7E96ED3A47A0A3BD5F5EEAB65001664F9" ) DEV_PUB_X = bytes.fromhex( "0501D5BC78DA9252560A26CB08FCC60CBE0B6D3B8E1D1FCEE514FAC0AF675168" ) DEV_PUB_Y = bytes.fromhex( "D551B3ED46F665731F95B4532939C25D91DB7EB844BD96D4ABD4083785F8DF47" ) SHARED = bytes.fromhex( "c42a039d548100dfba521e487debcbbb8b66bb7496f8b1862a7a395ed83e1a1c" ) TOKEN_ENC = bytes.fromhex("7A9F98E31B77BE90F9C64D12E9635040") TOKEN = bytes.fromhex("aff12c6dcfbf9df52f7a09211e8865cd") PIN_HASH_ENC = bytes.fromhex("afe8327ce416da8ee3d057589c2ce1a9") class TestClientPin(unittest.TestCase): @mock.patch("cryptography.hazmat.primitives.asymmetric.ec.generate_private_key") def test_establish_shared_secret(self, patched_generate): ctap = mock.MagicMock() ctap.info.options = {"clientPin": True} prot = ClientPin(ctap, PinProtocolV1()) patched_generate.return_value = ec.derive_private_key( EC_PRIV, ec.SECP256R1(), default_backend() ) ctap.client_pin.return_value = { 1: {1: 2, 3: -25, -1: 1, -2: DEV_PUB_X, -3: DEV_PUB_Y} } key_agreement, shared = prot._get_shared_secret() self.assertEqual(shared, SHARED) self.assertEqual(key_agreement[-2], EC_PUB_X) self.assertEqual(key_agreement[-3], EC_PUB_Y) def test_get_pin_token(self): ctap = mock.MagicMock() ctap.info.options = {"clientPin": True} prot = ClientPin(ctap, PinProtocolV1()) prot._get_shared_secret = mock.Mock(return_value=({}, SHARED)) prot.ctap.client_pin.return_value = {2: TOKEN_ENC} self.assertEqual(prot.get_pin_token("1234"), TOKEN) prot.ctap.client_pin.assert_called_once() self.assertEqual( prot.ctap.client_pin.call_args[1]["pin_hash_enc"], PIN_HASH_ENC ) def test_set_pin(self): ctap = mock.MagicMock() ctap.info.options = {"clientPin": True} prot = ClientPin(ctap, PinProtocolV1()) prot._get_shared_secret = mock.Mock(return_value=({}, SHARED)) prot.set_pin("1234") prot.ctap.client_pin.assert_called_with( 1, 3, key_agreement={}, new_pin_enc=bytes.fromhex( "0222fc42c6dd76a274a7057858b9b29d98e8a722ec2dc6668476168c5320473cec9907b4cd76ce7943c96ba5683943211d84471e64d9c51e54763488cd66526a" # noqa E501 ), pin_uv_param=bytes.fromhex("7b40c084ccc5794194189ab57836475f"), ) def test_change_pin(self): ctap = mock.MagicMock() ctap.info.options = {"clientPin": True} prot = ClientPin(ctap, PinProtocolV1()) prot._get_shared_secret = mock.Mock(return_value=({}, SHARED)) prot.change_pin("1234", "4321") prot.ctap.client_pin.assert_called_with( 1, 4, key_agreement={}, new_pin_enc=bytes.fromhex( "4280e14aac4fcbf02dd079985f0c0ffc9ea7d5f9c173fd1a4c843826f7590cb3c2d080c6923e2fe6d7a52c31ea1309d3fcca3dedae8a2ef14b6330cafc79339e" # noqa E501 ), pin_uv_param=bytes.fromhex("fb97e92f3724d7c85e001d7f93e6490a"), pin_hash_enc=bytes.fromhex("afe8327ce416da8ee3d057589c2ce1a9"), ) def test_short_pin(self): ctap = mock.MagicMock() ctap.info.options = {"clientPin": True} prot = ClientPin(ctap, PinProtocolV1()) with self.assertRaises(ValueError): prot.set_pin("123") def test_long_pin(self): ctap = mock.MagicMock() ctap.info.options = {"clientPin": True} prot = ClientPin(ctap, PinProtocolV1()) with self.assertRaises(ValueError): prot.set_pin("1" * 256) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/test_hid.py0000644000000000000000000000376414413232070012771 0ustar00# Copyright (c) 2013 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 fido2.hid import CtapHidDevice import unittest class HidTest(unittest.TestCase): def get_device(self): try: devs = list(CtapHidDevice.list_devices()) assert len(devs) == 1 return devs[0] except Exception: self.skipTest("Tests require a single FIDO HID device") def test_ping(self): msg1 = b"hello world!" msg2 = b" " msg3 = b"" dev = self.get_device() self.assertEqual(dev.ping(msg1), msg1) self.assertEqual(dev.ping(msg2), msg2) self.assertEqual(dev.ping(msg3), msg3) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/test_mds3.py0000644000000000000000000005132014413232070013062 0ustar00from fido2.mds3 import parse_blob, MdsAttestationVerifier from base64 import b64decode # Example data from: # https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#examples EXAMPLE_CA = b64decode( """ MIIGGTCCBAGgAwIBAgIUdT9qLX0sVMRe8l0sLmHd3mZovQ0wDQYJKoZIhvcNAQEL BQAwgZsxHzAdBgNVBAMMFkVYQU1QTEUgTURTMyBURVNUIFJPT1QxIjAgBgkqhkiG 9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20xFDASBgNVBAoMC0V4YW1wbGUgT1JH MRAwDgYDVQQLDAdFeGFtcGxlMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTVkxEjAQ BgNVBAcMCVdha2VmaWVsZDAeFw0yMTA0MTkxMTM1MDdaFw00ODA5MDQxMTM1MDda MIGbMR8wHQYDVQQDDBZFWEFNUExFIE1EUzMgVEVTVCBST09UMSIwIAYJKoZIhvcN AQkBFhNleGFtcGxlQGV4YW1wbGUuY29tMRQwEgYDVQQKDAtFeGFtcGxlIE9SRzEQ MA4GA1UECwwHRXhhbXBsZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1ZMRIwEAYD VQQHDAlXYWtlZmllbGQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDD jF5wyEWuhwDHsZosGdGFTCcI677rW881vV+UfW38J+K2ioFFNeGVsxbcebK6AVOi CDPFj0974IpeD9SFOhwAHoDu/LCfXdQWp8ZgQ91ULYWoW8o7NNSp01nbN9zmaO6/ xKNCa0bzjmXoGqglqnP1AtRcWYvXOSKZy1rcPeDv4Dhcpdp6W72fBw0eWIqOhsrI tuY2/N8ItBPiG03EX72nACq4nZJ/nAIcUbER8STSFPPzvE97TvShsi1FD8aO6l1W kR/QkreAGjMI++GbB2Qc1nN9Y/VEDbMDhQtxXQRdpFwubTjejkN9hKOtF3B71Yrw Irng3V9RoPMFdapWMzSlI+WWHog0oTj1PqwJDDg7+z1I6vSDeVWAMKr9mq1w1OGN zgBopIjd9lRWkRtt2kQSPX9XxqS4E1gDDr8MKbpM3JuubQtNCg9D7Ljvbz6vwvUr bPHH+oREvucsp0PZ5PpizloepGIcLFxDQqCulGY2n7Ahl0JOFXJqOFCaK3TWHwBv ZsaY5DgBuUvdUrwtgZNg2eg2omWXEepiVFQn3Fvj43Wh2npPMgIe5P0rwncXvROx aczd4rtajKS1ucoB9b9iKqM2+M1y/FDIgVf1fWEHwK7YdzxMlgOeLdeV/kqRU5PE UlLU9a2EwdOErrPbPKZmIfbs/L4B3k4zejMDH3Y+ZwIDAQABo1MwUTAdBgNVHQ4E FgQU8sWwq1TrurK7xMTwO1dKfeJBbCMwHwYDVR0jBBgwFoAU8sWwq1TrurK7xMTw O1dKfeJBbCMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAFw6M 1PiIfCPIBQ5EBUPNmRvRFuDpolOmDofnf/+mv63LqwQZAdo/W8tzZ9kOFhq24SiL w0H7fsdG/jeREXiIZMNoW/rA6Uac8sU+FYF7Q+qp6CQLlSQbDcpVMifTQjcBk2xh +aLK9SrrXBqnTAhwS+offGtAW8DpoLuH4tAcQmIjlgMlN65jnELCuqNR/wpA+zch 8LZW8saQ2cwRCwdr8mAzZoLbsDSVCHxQF3/kQjPT7Nao1q2iWcY3OYcRmKrieHDP 67yeLUbVmetfZis2d6ZlkqHLB4ZW1xX4otsEFkuTJA3HWDRsNyhTwx1YoCLsYut5 Zp0myqPNBq28w6qGMyyoJN0Z4RzMEO3R6i/MQNfhK55/8O2HciM6xb5t/aBSuHPK lBDrFWhpRnKYkaNtlUo35qV5IbKGKau3SdZdSRciaXUd/p81YmoF01UlhhMz/Rqr 1k2gyA0a9tF8+awCeanYt5izl8YO0FlrOU1SQ5UQw4szqqZqbrf4e8fRuU2TXNx4 zk+ImE7WRB44f6mSD746ZCBRogZ/SA5jUBu+OPe4/sEtERWRcQD+fXgce9ZEN0+p eyJIKAsl5Rm2Bmgyg5IoyWwSG5W+WekGyEokpslou2Yc6EjUj5ndZWz5EiHAiQ74 hNfDoCZIxVVLU3Qbp8a0S1bmsoT2JOsspIbtZUg= """ ) # NOTE: Signature changed to be properly ASN.1 formatted! EXAMPLE_BLOB = """ eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUlDWlRDQ0FndWdBd0lCQWdJQkFUQUtC Z2dxaGtqT1BRUURBakNCb3pFbk1DVUdBMVVFQXd3ZVJWaEJUVkJNUlNCTlJGTXpJRlJGVTFRZ1NVNVVS VkpOUlVSSlFWUkZNU0l3SUFZSktvWklodmNOQVFrQkZoTmxlR0Z0Y0d4bFFHVjRZVzF3YkdVdVkyOXRN UlF3RWdZRFZRUUtEQXRGZUdGdGNHeGxJRTlTUnpFUU1BNEdBMVVFQ3d3SFJYaGhiWEJzWlRFTE1Ba0dB MVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01BazFaTVJJd0VBWURWUVFIREFsWFlXdGxabWxsYkdRd0hoY05N akV3TkRFNU1URXpOVEEzV2hjTk16RXdOREUzTVRFek5UQTNXakNCcFRFcE1DY0dBMVVFQXd3Z1JWaEJU VkJNUlNCTlJGTXpJRk5KUjA1SlRrY2dRMFZTVkVsR1NVTkJWRVV4SWpBZ0Jna3Foa2lHOXcwQkNRRVdF MlY0WVcxd2JHVkFaWGhoYlhCc1pTNWpiMjB4RkRBU0JnTlZCQW9NQzBWNFlXMXdiR1VnVDFKSE1SQXdE Z1lEVlFRTERBZEZlR0Z0Y0d4bE1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDVFZreEVqQVFC Z05WQkFjTUNWZGhhMlZtYVdWc1pEQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJOUUpz NndUcWl4YytTK1ZEQWFqRmxQTmF0MTBLRVdKRTVqY1dPdm02cXBPOVNEQUFNWnZiNEhIcnZzK1A1WVJw SHJTbFVQZHZLK3VFUWJkV2czMVA5dWpMREFxTUFrR0ExVWRFd1FDTUFBd0hRWURWUjBPQkJZRUZMcXNh cGNYVjRab1ZIQW5ScFBad1FlN1l5MjBNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUUM2N3phOEVJdXlS aUtnTkRYSVAxczFhTHIzanpIOVdWWGZIeDRiSit6Q3NnSWdHL3RWQnV0T0pVVSt2dm9ISW8vb3RBVUFj SDViTkhQM3VJemlEUytQVFVjPSIsIk1JSUVIekNDQWdlZ0F3SUJBZ0lCQWpBTkJna3Foa2lHOXcwQkFR c0ZBRENCbXpFZk1CMEdBMVVFQXd3V1JWaEJUVkJNUlNCTlJGTXpJRlJGVTFRZ1VrOVBWREVpTUNBR0NT cUdTSWIzRFFFSkFSWVRaWGhoYlhCc1pVQmxlR0Z0Y0d4bExtTnZiVEVVTUJJR0ExVUVDZ3dMUlhoaGJY QnNaU0JQVWtjeEVEQU9CZ05WQkFzTUIwVjRZVzF3YkdVeEN6QUpCZ05WQkFZVEFsVlRNUXN3Q1FZRFZR UUlEQUpOV1RFU01CQUdBMVVFQnd3SlYyRnJaV1pwWld4a01CNFhEVEl4TURReE9URXhNelV3TjFvWERU UTRNRGt3TkRFeE16VXdOMW93Z2FNeEp6QWxCZ05WQkFNTUhrVllRVTFRVEVVZ1RVUlRNeUJVUlZOVUlF bE9WRVZTVFVWRVNVRlVSVEVpTUNBR0NTcUdTSWIzRFFFSkFSWVRaWGhoYlhCc1pVQmxlR0Z0Y0d4bExt TnZiVEVVTUJJR0ExVUVDZ3dMUlhoaGJYQnNaU0JQVWtjeEVEQU9CZ05WQkFzTUIwVjRZVzF3YkdVeEN6 QUpCZ05WQkFZVEFsVlRNUXN3Q1FZRFZRUUlEQUpOV1RFU01CQUdBMVVFQnd3SlYyRnJaV1pwWld4a01G a3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRU5HdW1CYlluRlFuVGpQMVJTZmM3MGhzaGdi aUkxWnRwd1E1bjZ4UkxBL1dxMFBTQ2ZMbDVxUStyN2RsY0sxZDNyM3ZMYSt2bTZHNnZLSEdDUEVlVXpx TXZNQzB3REFZRFZSMFRCQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVOazZGNFJKbkdHVkZlKzAvY2Jad2Zy WmQ3WlV3RFFZSktvWklodmNOQVFFTEJRQURnZ0lCQUNucDFmbTBGS2xXbVV0VHBsTHVZZzdtcHM0eFAv Q091OGRuYjM4dTFuTURWdU9UNCtDWmFpTTlBR3ozMTNHRDIyaGpMR3JtUHVZbjg2d0dPS0kzSE9yRXBz R2RNbWZ5N3RUbUtYL2VNL2VTM0ZFRFhabkU4MlBuNW9GSXlCVC9mOHNHdVh5T3NGWnFXQnZWZEJJSURs ZENwRDRteE1RWlpPWnRUcmx2M1d2QlFNQy9kc2ljT3hlM1FLWHZXSGk2UWIvUmh1YWlwM3JQbXdNZis0 SnBuSk8rSk1QcUFhVTFjQUg4SFZzZnJMQU1vS3MxNDhqMitjdmJwYVdtc1Q1cklvSC9lelZyUGFHL01P aUlncTc5dy9lZnV2U2k1QVg4SitrRG9MU0VmM2Q1d09na0pZQXFVcWNSeFhURUV0S0l6RE02aHphQlFG aUFXdlRuOUlsVldnbnRRYW1TWHZIK3R4YVRGOWlFbEh4VWY1SU5ZRlZjaUNwenRTcnlkZUh2L09DTlJm Ny9MVnJpY01TbG84UmgrTzN5UDlWKzJ1TmYzWDhzUUpOdHVmclFOYXFxMTh3aVhsaVRMdWZTbjAyL2cr bWtoSVVpTktmVE9KcHZDaktlQ25DRmN4UVUyL1hUM0toM0c4Z0RKd3NPNkVWUmpNVUp0NEFZS3plL2hF VUN3RjU1SUYybTNqSElvQ3U4alZmajI0Q2VFWDVkbmZ2U3IrU1Z2TjVRQjB1WjA1TTRybXlaWHlxQm0w ekszZlIraUUwL1pwSW51d0xDN1grVzgyelhsbk1rcGxJM1ErSnhkN2pmUTE1U1lORTJLNnJ2UklUMDF3 MFA5WnF5REY3a25HS3BSbHA3T3F4ZDM3YkQvVlViV3BRN2dJQWZzSk5INUtCTG93SEpGRmpXIl19.eyJ sZWdhbEhlYWRlciI6IlJldHJpZXZhbCBhbmQgdXNlIG9mIHRoaXMgQkxPQiBpbmRpY2F0ZXMgYWNjZXB 0YW5jZSBvZiB0aGUgYXBwcm9wcmlhdGUgYWdyZWVtZW50IGxvY2F0ZWQgYXQgaHR0cHM6Ly9maWRvYWx saWFuY2Uub3JnL21ldGFkYXRhL21ldGFkYXRhLWxlZ2FsLXRlcm1zLyIsIm5vIjoxNSwibmV4dFVwZGF 0ZSI6IjIwMjAtMDMtMzAiLCJlbnRyaWVzIjpbeyJhYWlkIjoiMTIzNCM1Njc4IiwibWV0YWRhdGFTdGF 0ZW1lbnQiOnsibGVnYWxIZWFkZXIiOiJodHRwczovL2ZpZG9hbGxpYW5jZS5vcmcvbWV0YWRhdGEvbWV 0YWRhdGEtc3RhdGVtZW50LWxlZ2FsLWhlYWRlci8iLCJkZXNjcmlwdGlvbiI6IkZJRE8gQWxsaWFuY2U gU2FtcGxlIFVBRiBBdXRoZW50aWNhdG9yIiwiYWFpZCI6IjEyMzQjNTY3OCIsImFsdGVybmF0aXZlRGV zY3JpcHRpb25zIjp7InJ1LVJVIjoi0J_RgNC40LzQtdGAIFVBRiDQsNGD0YLQtdC90YLQuNGE0LjQutC w0YLQvtGA0LAg0L7RgiBGSURPIEFsbGlhbmNlIiwiZnItRlIiOiJFeGVtcGxlIFVBRiBhdXRoZW50aWN hdG9yIGRlIEZJRE8gQWxsaWFuY2UifSwiYXV0aGVudGljYXRvclZlcnNpb24iOjIsInByb3RvY29sRmF taWx5IjoidWFmIiwic2NoZW1hIjozLCJ1cHYiOlt7Im1ham9yIjoxLCJtaW5vciI6MH0seyJtYWpvciI 6MSwibWlub3IiOjF9XSwiYXV0aGVudGljYXRpb25BbGdvcml0aG1zIjpbInNlY3AyNTZyMV9lY2RzYV9 zaGEyNTZfcmF3Il0sInB1YmxpY0tleUFsZ0FuZEVuY29kaW5ncyI6WyJlY2NfeDk2Ml9yYXciXSwiYXR 0ZXN0YXRpb25UeXBlcyI6WyJiYXNpY19mdWxsIl0sInVzZXJWZXJpZmljYXRpb25EZXRhaWxzIjpbW3s idXNlclZlcmlmaWNhdGlvbk1ldGhvZCI6ImZpbmdlcnByaW50X2ludGVybmFsIiwiYmFEZXNjIjp7InN lbGZBdHRlc3RlZEZBUiI6MC4wMDAwMiwibWF4UmV0cmllcyI6NSwiYmxvY2tTbG93ZG93biI6MzAsIm1 heFRlbXBsYXRlcyI6NX19XV0sImtleVByb3RlY3Rpb24iOlsiaGFyZHdhcmUiLCJ0ZWUiXSwiaXNLZXl SZXN0cmljdGVkIjp0cnVlLCJtYXRjaGVyUHJvdGVjdGlvbiI6WyJ0ZWUiXSwiY3J5cHRvU3RyZW5ndGg iOjEyOCwiYXR0YWNobWVudEhpbnQiOlsiaW50ZXJuYWwiXSwidGNEaXNwbGF5IjpbImFueSIsInRlZSJ dLCJ0Y0Rpc3BsYXlDb250ZW50VHlwZSI6ImltYWdlL3BuZyIsInRjRGlzcGxheVBOR0NoYXJhY3Rlcml zdGljcyI6W3sid2lkdGgiOjMyMCwiaGVpZ2h0Ijo0ODAsImJpdERlcHRoIjoxNiwiY29sb3JUeXBlIjo yLCJjb21wcmVzc2lvbiI6MCwiZmlsdGVyIjowLCJpbnRlcmxhY2UiOjB9XSwiYXR0ZXN0YXRpb25Sb29 0Q2VydGlmaWNhdGVzIjpbIk1JSUNQVENDQWVPZ0F3SUJBZ0lKQU91ZXh2VTNPeTJ3TUFvR0NDcUdTTTQ 5QkFNQ01Ic3hJREFlQmdOVkJBTU1GMU5oYlhCc1pTQkJkSFJsYzNSaGRHbHZiaUJTYjI5ME1SWXdGQVl EVlFRS0RBMUdTVVJQSUVGc2JHbGhibU5sTVJFd0R3WURWUVFMREFoVlFVWWdWRmRITERFU01CQUdBMVV FQnd3SlVHRnNieUJCYkhSdk1Rc3dDUVlEVlFRSURBSkRRVEVMTUFrR0ExVUVCaE1DVlZNd0hoY05NVFF 3TmpFNE1UTXpNek15V2hjTk5ERXhNVEF6TVRNek16TXlXakI3TVNBd0hnWURWUVFEREJkVFlXMXdiR1V nUVhSMFpYTjBZWFJwYjI0Z1VtOXZkREVXTUJRR0ExVUVDZ3dOUmtsRVR5QkJiR3hwWVc1alpURVJNQTh HQTFVRUN3d0lWVUZHSUZSWFJ5d3hFakFRQmdOVkJBY01DVkJoYkc4Z1FXeDBiekVMTUFrR0ExVUVDQXd DUTBFeEN6QUpCZ05WQkFZVEFsVlRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVIOGh 2MkQwSFhhNTkvQm1wUTdSWmVoTC9GTUd6RmQxUUJnOXZBVXBPWjNham51UTk0UFI3YU16SDMzblVTQnI 4ZkhZRHJxT0JiNThweEdxSEpSeVgvNk5RTUU0d0hRWURWUjBPQkJZRUZQb0hBM0NMaHhGYkMwSXQ3ekU 0dzhoazVFSi9NQjhHQTFVZEl3UVlNQmFBRlBvSEEzQ0xoeEZiQzBJdDd6RTR3OGhrNUVKL01Bd0dBMVV kRXdRRk1BTUJBZjh3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUloQUowNlFTWHQ5aWhJYkVLWUtJanNQa3J pVmRMSWd0ZnNiRFN1N0VySmZ6cjRBaUJxb1lDWmYwK3pJNTVhUWVBSGpJekE5WG02M3JydUF4Qlo5cHM 5ejJYTmxRPT0iXSwiaWNvbiI6ImRhdGE6aW1hZ2UvcG5nO2Jhc2U2NCxpVkJPUncwS0dnb0FBQUFOU1V oRVVnQUFBRThBQUFBdkNBWUFBQUNpd0pmY0FBQUFBWE5TUjBJQXJzNGM2UUFBQUFSblFVMUJBQUN4and 2OFlRVUFBQUFKY0VoWmN3QUFEc01BQUE3REFjZHZxR1FBQUFhaFNVUkJWR2hEN1pyNWJ4UmxHTWY5S3p UQjhBTS9ZRWhFMlc3cFFaY1dLS0JjbFNwSEFUbEVMQVJFN2tORUNDQTNGa1dLMENLS1NDRklzS0JjZ1Z DRFdHTkVTZEFZaWR3Z2dnSkJpUmlNaEZjLzR3eTg4ODR6dTlOZGxuR1RmWkpQMm4zbk8rKzg4OTMzZnZ lQkJ4K1BxQ3pKa1RVdkJiTG1wVURXdkJUSW1wY0NTWnZYTENkWDlSMDVTazE5YmI1YXRmNTk5ZkcrL2V yQTU0MXE0N2FQMUxMVmE5U0l5Vk5VaThJaThkNWtHVHNpMzBORnY3YWk5bjdRWlBNd2JkeXMyZXJVMlh NcVVkeTgrWmNhTm1HaW1FOHlYTjNSVWQzYTE4bkYwZlVsb3ZaKzBDVHpXcGQyVmorZU9tMWJFeXk2RHg 0aTVwVU1HV3ZlbzUwNnEyMjdkdHVXQkl1ZmZyNm9XcFYwRlBOTGhvdzE3NTFObTIxTHZQSDNyVnRXamZ 6NjZMZnFsOHRYN0ZSbDlZRlNYc21Tc2ViOWNlT0diWWs3TU5VY0dQZzhac2JNZTlyZlFVYWFWL0pNWDl zcWR6RENTdnAwa1pIbVRaZzl4N2JMSGNNblRoYjE2ZUorbVZmUXE4eWFVWlFORzY0aVhaKzAva3E2dU9 aRk8wUXRhdGRXS2ZYblJROTlCajkxUjVPSUZuazU0ak4wbWtVaXFsTzNYRFcrTWwrOThtS0I2dFc3cld wWmNQYyswemc0dExyWWxVYzg2RTZlR0RqSU11YlZwY3VzZWFyZmdJWUdSazZicmhaVnIvSmNIem9vTDc 1NTBqZWRMRXhvcFdjQXBpMlpVcWh1N0pMdnJWc1FVODF6a3pPUGVlbU1SWXZWdVFzWDdQYmlEUVk1SnZ ab25mdEsrMVZZOEg5dXR4NTMwaDBvYitqbVJZcWo2b3VhWXZFZW5XL1dsWWpwOGN3Yk1tNjgydFB3cVc xUjR0ai8yU0gxM0lSSllsNG1vWnZYcGlTcURyN2RYdFFIeGEvUEszLytCV3NLMWRUZ0h1NlY4dFFKM2J 3Rmt3cEZyVU9RNTBzMXIzbGV2bTh6WmNxMTcrQkJhdzdLOGxFSzVxemtZZWFyazlBOHA3UDNHekRLK25 kM0RRb3crNlVDOFNWTjgyaXV2MzhpbTdOdGFYdFYxQ1ZxNlJndzRwa3NtYmRpM2J1MkRlN1lmYUJCeGN xZnZxUHJVakZRTlRRMjJsZmRVVlZUNjhyVEpLRjVEblNtVWpnZHFnNG1TUzlwbXNmREpSM0c2VG9IMGl XOWFWN0xXTEhZWEtsbFREdDBMVEF0a1lJYWFtcDFRalZ2Kyt1eUdVeFZkSjBETlZYU20rYjFxUnhwbDg 0ZGRmWDFMcDFPL2Q2OXRzb2QwdnM1aEdyZTl4dThvK2ZwTFIxY0doTlRENlo1N0M5S01XWGVmSmRPWjk 0YmI5b3FkMVJPblM3cUlUVHpIaW1NcWl2Yk8zZzBEZFZ5azNXUUJoQnp0SzM1WUtOZE9uYzhPM2FjUzZ mRFpGZ0thWExzRUpwNXJkcmxpQnFwODljSmNzL203VHZzMHJrakdmTjRiMGtQb1puM1VKdUlPcm5aMjJ 5UDFmbXZVeCtPNWdTcWViVjFtK3pTdVlOVmhxN1RXYkRpTFZ2bGpwbExsb3A2Q0xYUCsycXR2R0xJTC8 xdmltSVNkTUJnelNvRlp5dTZUcWQranp4Z3NQYVY5QkNxZWUvTmpZazZ2NmxLOWN3aVVjL1NUdGYxSER wTTNiNTkyeTdoM1RoeDVveks2OUhMcFlXdUF3YXFTNWN2MjZxN2NlYjhlZlZZYVJlUDNpRlU4emoxa25 Td1pYSE1tbkNqWTBPZ2FsbzdVUWZTQ00zcVFRcjJIL1hGUDdzc1h4NDVZbDkxQnllQ2VwNG1vWm9IKzF mRzN4RDR0VDd4OGt3eWo4bndiOWV2MjZWMEI2ZCs3SDR6S3Z1ZEFINTM3RmpxeXpPSGRKbkhFdXptWHE vV2p4T2J2Tk1idjduaHl3c1gyYVZzV3RDOCs0OGFMZWFwRTdwNXdLWmkwQTJBUVJWNW52UjRFK3VKYyt iNjFrQXBxSW54QmdtZC80VjVRUC9tdDE4SERDN3NSSGZ0bWV1NWxtaFYwcm4vQUxYMjMyYnFkNEJGbkR 4N1ZpMWNXUzJ1ZmYwSWJCNDdxZXh4bVVqOVF1dFlqdXBkM3RZRDZhYldCQk1yaCthcE5iT0tyTkYxK3V nQ2E0cmlYR2Z3TVBQdFZpYXZoVTNZTU9BQW51VWIvUjA3TDB5T1NlT2FkRTg4QXBzWEZHZmYzMHluaGx KZ001MUNVNnZOOUV6Z25wdkhCRlV5aVZyYWVQaXdKNTNERjVaVFpub21FTmc4NWtOVWQyb0ppMldwcjR PbW1rZk40eDR6SGZpVkZjOER2OE56dWhOcU9pZGlsR3ZBNkRHdWVad083OEFBUW42Y2lFazYrcnc1VmN 2anZxTkRZUE9vSVV3YUtTaHJ4QXVYTGxrSDRhWXVHZk1ZRGMxMFdGNVRhMzFoUEpPZmNVaHJVL0psSU5 pNmM2ZWxSWWRCcG82KytZZmp4NjFsR05mUm00TUQ1ckoxajNGb0dIbmpEU0JOYXJZVWdNTHlNc3pLcGI 3dFhwb0hmUHM4aDNXcDFMek5mTms1NFh4QzF3REdVbVl6WFllZmg2ei9jS3RWbTRFQnhhOVZRR0R6WXI zTHJVTVJqSEVLa2s3emFGS1lRQTJoR1FVMXorODVORldwWERya3ozdngxMEdxeFE2QnplTmJvQms1bjh rNG5lYlJoK2sxaFdmeFRGMEQxRXlXVXM1bnYrZGdRcUtheHp1Q2RFMGlzSGwwMk5ROGFoMG1YcjEyTGE zbTBmOXdpazkrd0xOVE1ZLzg2TVBvOHlpMzFPZnhtVDZQV29xRzkrRFp1a1luYTU2bVNadDVXV1N5NXF WQTFyd1V5SnFYQWxuemtpYWkvZ0hTRDdSa1R5aWhvZ0FBQUFCSlJVNUVya0pnZ2c9PSJ9LCJzdGF0dXN SZXBvcnRzIjpbeyJzdGF0dXMiOiJGSURPX0NFUlRJRklFRCIsImVmZmVjdGl2ZURhdGUiOiIyMDE0LTA xLTA0In1dLCJ0aW1lT2ZMYXN0U3RhdHVzQ2hhbmdlIjoiMjAxNC0wMS0wNCJ9LHsiYWFndWlkIjoiMDE zMmQxMTAtYmY0ZS00MjA4LWE0MDMtYWI0ZjVmMTJlZmU1IiwibWV0YWRhdGFTdGF0ZW1lbnQiOnsibGV nYWxIZWFkZXIiOiJodHRwczovL2ZpZG9hbGxpYW5jZS5vcmcvbWV0YWRhdGEvbWV0YWRhdGEtc3RhdGV tZW50LWxlZ2FsLWhlYWRlci8iLCJkZXNjcmlwdGlvbiI6IkZJRE8gQWxsaWFuY2UgU2FtcGxlIEZJRE8 yIEF1dGhlbnRpY2F0b3IiLCJhYWd1aWQiOiIwMTMyZDExMC1iZjRlLTQyMDgtYTQwMy1hYjRmNWYxMmV mZTUiLCJhbHRlcm5hdGl2ZURlc2NyaXB0aW9ucyI6eyJydS1SVSI6ItCf0YDQuNC80LXRgCBGSURPMiD QsNGD0YLQtdC90YLQuNGE0LjQutCw0YLQvtGA0LAg0L7RgiBGSURPIEFsbGlhbmNlIiwiZnItRlIiOiJ FeGVtcGxlIEZJRE8yIGF1dGhlbnRpY2F0b3IgZGUgRklETyBBbGxpYW5jZSIsInpoLUNOIjoi5L6G6Ie qRklETyBBbGxpYW5jZeeahOekuuS-i0ZJRE8y6Lqr5Lu96amX6K2J5ZmoIn0sInByb3RvY29sRmFtaWx 5IjoiZmlkbzIiLCJzY2hlbWEiOjMsImF1dGhlbnRpY2F0b3JWZXJzaW9uIjo1LCJ1cHYiOlt7Im1ham9 yIjoxLCJtaW5vciI6MH1dLCJhdXRoZW50aWNhdGlvbkFsZ29yaXRobXMiOlsic2VjcDI1NnIxX2VjZHN hX3NoYTI1Nl9yYXciLCJyc2Fzc2FfcGtjc3YxNV9zaGEyNTZfcmF3Il0sInB1YmxpY0tleUFsZ0FuZEV uY29kaW5ncyI6WyJjb3NlIl0sImF0dGVzdGF0aW9uVHlwZXMiOlsiYmFzaWNfZnVsbCJdLCJ1c2VyVmV yaWZpY2F0aW9uRGV0YWlscyI6W1t7InVzZXJWZXJpZmljYXRpb25NZXRob2QiOiJub25lIn1dLFt7InV zZXJWZXJpZmljYXRpb25NZXRob2QiOiJwcmVzZW5jZV9pbnRlcm5hbCJ9XSxbeyJ1c2VyVmVyaWZpY2F 0aW9uTWV0aG9kIjoicGFzc2NvZGVfZXh0ZXJuYWwiLCJjYURlc2MiOnsiYmFzZSI6MTAsIm1pbkxlbmd 0aCI6NH19XSxbeyJ1c2VyVmVyaWZpY2F0aW9uTWV0aG9kIjoicGFzc2NvZGVfZXh0ZXJuYWwiLCJjYUR lc2MiOnsiYmFzZSI6MTAsIm1pbkxlbmd0aCI6NH19LHsidXNlclZlcmlmaWNhdGlvbk1ldGhvZCI6InB yZXNlbmNlX2ludGVybmFsIn1dXSwia2V5UHJvdGVjdGlvbiI6WyJoYXJkd2FyZSIsInNlY3VyZV9lbGV tZW50Il0sIm1hdGNoZXJQcm90ZWN0aW9uIjpbIm9uX2NoaXAiXSwiY3J5cHRvU3RyZW5ndGgiOjEyOCw iYXR0YWNobWVudEhpbnQiOlsiZXh0ZXJuYWwiLCJ3aXJlZCIsIndpcmVsZXNzIiwibmZjIl0sInRjRGl zcGxheSI6W10sImF0dGVzdGF0aW9uUm9vdENlcnRpZmljYXRlcyI6WyJNSUlDUFRDQ0FlT2dBd0lCQWd JSkFPdWV4dlUzT3kyd01Bb0dDQ3FHU000OUJBTUNNSHN4SURBZUJnTlZCQU1NRjFOaGJYQnNaU0JCZEh SbGMzUmhkR2x2YmlCU2IyOTBNUll3RkFZRFZRUUtEQTFHU1VSUElFRnNiR2xoYm1ObE1SRXdEd1lEVlF RTERBaFZRVVlnVkZkSExERVNNQkFHQTFVRUJ3d0pVR0ZzYnlCQmJIUnZNUXN3Q1FZRFZRUUlEQUpEUVR FTE1Ba0dBMVVFQmhNQ1ZWTXdIaGNOTVRRd05qRTRNVE16TXpNeVdoY05OREV4TVRBek1UTXpNek15V2p CN01TQXdIZ1lEVlFRRERCZFRZVzF3YkdVZ1FYUjBaWE4wWVhScGIyNGdVbTl2ZERFV01CUUdBMVVFQ2d 3TlJrbEVUeUJCYkd4cFlXNWpaVEVSTUE4R0ExVUVDd3dJVlVGR0lGUlhSeXd4RWpBUUJnTlZCQWNNQ1Z CaGJHOGdRV3gwYnpFTE1Ba0dBMVVFQ0F3Q1EwRXhDekFKQmdOVkJBWVRBbFZUTUZrd0V3WUhLb1pJemo wQ0FRWUlLb1pJemowREFRY0RRZ0FFSDhodjJEMEhYYTU5L0JtcFE3UlplaEwvRk1HekZkMVFCZzl2QVV wT1ozYWpudVE5NFBSN2FNekgzM25VU0JyOGZIWURycU9CYjU4cHhHcUhKUnlYLzZOUU1FNHdIUVlEVlI wT0JCWUVGUG9IQTNDTGh4RmJDMEl0N3pFNHc4aGs1RUovTUI4R0ExVWRJd1FZTUJhQUZQb0hBM0NMaHh GYkMwSXQ3ekU0dzhoazVFSi9NQXdHQTFVZEV3UUZNQU1CQWY4d0NnWUlLb1pJemowRUF3SURTQUF3UlF JaEFKMDZRU1h0OWloSWJFS1lLSWpzUGtyaVZkTElndGZzYkRTdTdFckpmenI0QWlCcW9ZQ1pmMCt6STU 1YVFlQUhqSXpBOVhtNjNycnVBeEJaOXBzOXoyWE5sUT09Il0sImljb24iOiJkYXRhOmltYWdlL3BuZzt iYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQUU4QUFBQXZDQVlBQUFDaXdKZmNBQUFBQVhOU1I wSUFyczRjNlFBQUFBUm5RVTFCQUFDeGp3djhZUVVBQUFBSmNFaFpjd0FBRHNNQUFBN0RBY2R2cUdRQUF BYWhTVVJCVkdoRDdacjVieFJsR01mOUt6VEI4QU0vWUVoRTJXN3BRWmNXS0tCY2xTcEhBVGxFTEFSRTd rTkVDQ0EzRmtXSzBDS0tTQ0ZJc0tCY2dWQ0RXR05FU2RBWWlkd2dnZ0pCaVJpTWhGYy80d3k4ODg0enU 5TmRsbkdUZlpKUDJuM25PKys4ODkzM2Z2ZUJCeCtQcUN6SmtUVXZCYkxtcFVEV3ZCVEltcGNDU1p2WEx DZFg5UjA1U2sxOWJiNWF0ZjU5OWZHKy9lckE1NDFxNDdhUDFMTFZhOVNJeVZOVWk4SWk4ZDVrR1RzaTM wTkZ2N2FpOW43UVpQTXdiZHlzMmVyVTJYTXFVZHk4K1pjYU5tR2ltRTh5WE4zUlVkM2ExOG5GMGZVbG9 2WiswQ1R6V3BkMlZqK2VPbTFiRXl5NkR4NGk1cFVNR1d2ZW81MDZxMjI3ZHR1V0JJdWZmcjZvV3BWMEZ QTkxob3cxNzUxTm0yMUx2UEgzclZ0V2pmejY2TGZxbDh0WDdGUmw5WUZTWHNtU3NlYjljZU9HYllrN01 OVWNHUGc4WnNiTWU5cmZRVWFhVi9KTVg5c3FkekRDU3ZwMGtaSG1UWmc5eDdiTEhjTW5UaGIxNmVKK21 WZlFxOHlhVVpRTkc2NGlYWiswL2txNnVPWkZPMFF0YXRkV0tmWG5SUTk5Qmo5MVI1T0lGbms1NGpOMG1 rVWlxbE8zWERXK01sKzk4bUtCNnRXN3JXcFpjUGMrMHpnNHRMcllsVWM4NkU2ZUdEaklNdWJWcGN1c2V hcmZnSVlHUms2YnJoWlZyL0pjSHpvb0w3NTUwamVkTEV4b3BXY0FwaTJaVXFodTdKTHZyVnNRVTgxemt 6T1BlZW1NUll2VnVRc1g3UGJpRFFZNUp2Wm9uZnRLKzFWWThIOXV0eDUzMGgwb2Iram1SWXFqNm91YVl 2RWVuVy9XbFlqcDhjd2JNbTY4MnRQd3FXMVI0dGovMlNIMTNJUkpZbDRtb1p2WHBpU3FEcjdkWHRRSHh hL1BLMy8rQldzSzFkVGdIdTZWOHRRSjNid0Zrd3BGclVPUTUwczFyM2xldm04elpjcTE3K0JCYXc3Szh sRUs1cXprWWVhcms5QThwN1AzR3pESytuZDNEUW93KzZVQzhTVk44Mml1djM4aW03TnRhWHRWMUNWcTZ SZ3c0cGtzbWJkaTNidTJEZTdZZmFCQnhjcWZ2cVByVWpGUU5UUTIybGZkVVZWVDY4clRKS0Y1RG5TbVV qZ2RxZzRtU1M5cG1zZkRKUjNHNlRvSDBpVzlhVjdMV0xIWVhLbGxURHQwTFRBdGtZSWFhbXAxUWpWdis rdXlHVXhWZEowRE5WWFNtK2IxcVJ4cGw4NGRkZlgxTHAxTy9kNjl0c29kMHZzNWhHcmU5eHU4bytmcEx SMWNHaE5URDZaNTdDOUtNV1hlZkpkT1o5NGJiOW9xZDFST25TN3FJVFR6SGltTXFpdmJPM2cwRGRWeWs zV1FCaEJ6dEszNVlLTmRPbmM4TzNhY1M2ZkRaRmdLYVhMc0VKcDVyZHJsaUJxcDg5Y0pjcy9tN1R2czB ya2pHZk40YjBrUG9abjNVSnVJT3JuWjIyeVAxZm12VXgrTzVnU3FlYlYxbSt6U3VZTlZocTdUV2JEaUx WdmxqcGxMbG9wNkNMWFArMnF0dkdMSUwvMXZpbUlTZE1CZ3pTb0ZaeXU2VHFkK2p6eGdzUGFWOUJDcWV lL05qWWs2djZsSzljd2lVYy9TVHRmMUhEcE0zYjU5Mnk3aDNUaHg1b3pLNjlITHBZV3VBd2FxUzVjdjI 2cTdjZWI4ZWZWWWFSZVAzaUZVOHpqMWtuU3daWEhNbW5DalkwT2dhbG83VVFmU0NNM3FRUXIySC9YRlA 3c3NYeDQ1WWw5MUJ5ZUNlcDRtb1pvSCsxZkczeEQ0dFQ3eDhrd3lqOG53YjlldjI2VjBCNmQrN0g0ekt 2dWRBSDUzN0ZqcXl6T0hkSm5IRXV6bVhxL1dqeE9idk5NYnY3bmh5d3NYMmFWc1d0QzgrNDhhTGVhcEU 3cDV3S1ppMEEyQVFSVjVudlI0RSt1SmMrYjYxa0FwcUlueEJnbWQvNFY1UVAvbXQxOEhEQzdzUkhmdG1 ldTVsbWhWMHJuL0FMWDIzMmJxZDRCRm5EeDdWaTFjV1MydWZmMEliQjQ3cWV4eG1VajlRdXRZanVwZDN 0WUQ2YWJXQkJNcmgrYXBOYk9Lck5GMSt1Z0NhNHJpWEdmd01QUHRWaWF2aFUzWU1PQUFudVViL1IwN0w weU9TZU9hZEU4OEFwc1hGR2ZmMzB5bmhsSmdNNTFDVTZ2TjlFemducHZIQkZVeWlWcmFlUGl3SjUzREY 1WlRabm9tRU5nODVrTlVkMm9KaTJXcHI0T21ta2ZONHg0ekhmaVZGYzhEdjhOenVoTnFPaWRpbEd2QTZ ER3VlWndPNzhBQVFuNmNpRWs2K3J3NVZjdmp2cU5EWVBPb0lVd2FLU2hyeEF1WExsa0g0YVl1R2ZNWUR jMTBXRjVUYTMxaFBKT2ZjVWhyVS9KbElOaTZjNmVsUllkQnBvNisrWWZqeDYxbEdOZlJtNE1ENXJKMWo zRm9HSG5qRFNCTmFyWVVnTUx5TXN6S3BiN3RYcG9IZlBzOGgzV3AxTHpOZk5rNTRYeEMxd0RHVW1Zelh ZZWZoNnovY0t0Vm00RUJ4YTlWUUdEellyM0xyVU1SakhFS2trN3phRktZUUEyaEdRVTF6Kzg1TkZXcFh Ecmt6M3Z4MTBHcXhRNkJ6ZU5ib0JrNW44azRuZWJSaCtrMWhXZnhURjBEMUV5V1VzNW52K2RnUXFLYXh 6dUNkRTBpc0hsMDJOUThhaDBtWHIxMkxhM20wZjl3aWs5K3dMTlRNWS84Nk1Qbzh5aTMxT2Z4bVQ2UFd vcUc5K0RadWtZbmE1Nm1TWnQ1V1dTeTVxVkExcndVeUpxWEFsbnpraWFpL2dIU0Q3UmtUeWlob2dBQUF BQkpSVTVFcmtKZ2dnPT0iLCJzdXBwb3J0ZWRFeHRlbnNpb25zIjpbeyJpZCI6ImhtYWMtc2VjcmV0Iiw iZmFpbF9pZl91bmtub3duIjpmYWxzZX0seyJpZCI6ImNyZWRQcm90ZWN0IiwiZmFpbF9pZl91bmtub3d uIjpmYWxzZX1dLCJhdXRoZW50aWNhdG9yR2V0SW5mbyI6eyJ2ZXJzaW9ucyI6WyJVMkZfVjIiLCJGSUR PXzJfMCJdLCJleHRlbnNpb25zIjpbImNyZWRQcm90ZWN0IiwiaG1hYy1zZWNyZXQiXSwiYWFndWlkIjo iMDEzMmQxMTBiZjRlNDIwOGE0MDNhYjRmNWYxMmVmZTUiLCJvcHRpb25zIjp7InBsYXQiOiJmYWxzZSI sInJrIjoidHJ1ZSIsImNsaWVudFBpbiI6InRydWUiLCJ1cCI6InRydWUiLCJ1diI6InRydWUiLCJ1dlR va2VuIjoiZmFsc2UiLCJjb25maWciOiJmYWxzZSJ9LCJtYXhNc2dTaXplIjoxMjAwLCJwaW5VdkF1dGh Qcm90b2NvbHMiOlsxXSwibWF4Q3JlZGVudGlhbENvdW50SW5MaXN0IjoxNiwibWF4Q3JlZGVudGlhbEl kTGVuZ3RoIjoxMjgsInRyYW5zcG9ydHMiOlsidXNiIiwibmZjIl0sImFsZ29yaXRobXMiOlt7InR5cGU iOiJwdWJsaWMta2V5IiwiYWxnIjotN30seyJ0eXBlIjoicHVibGljLWtleSIsImFsZyI6LTI1N31dLCJ tYXhBdXRoZW50aWNhdG9yQ29uZmlnTGVuZ3RoIjoxMDI0LCJkZWZhdWx0Q3JlZFByb3RlY3QiOjIsImZ pcm13YXJlVmVyc2lvbiI6NX19LCJzdGF0dXNSZXBvcnRzIjpbeyJzdGF0dXMiOiJGSURPX0NFUlRJRkl FRCIsImVmZmVjdGl2ZURhdGUiOiIyMDE5LTAxLTA0In0seyJzdGF0dXMiOiJGSURPX0NFUlRJRklFRF9 MMSIsImVmZmVjdGl2ZURhdGUiOiIyMDIwLTExLTE5IiwiY2VydGlmaWNhdGlvbkRlc2NyaXB0b3IiOiJ GSURPIEFsbGlhbmNlIFNhbXBsZSBGSURPMiBBdXRoZW50aWNhdG9yIiwiY2VydGlmaWNhdGVOdW1iZXI iOiJGSURPMjEwMDAyMDE1MTIyMTAwMSIsImNlcnRpZmljYXRpb25Qb2xpY3lWZXJzaW9uIjoiMS4wLjE iLCJjZXJ0aWZpY2F0aW9uUmVxdWlyZW1lbnRzVmVyc2lvbiI6IjEuMC4xIn1dLCJ0aW1lT2ZMYXN0U3R hdHVzQ2hhbmdlIjoiMjAxOS0wMS0wNCJ9XX0.MEYCIQD6RzXCuiskDXpvEtdfN4OQUQ4KxsoDLZYMTOg Jj4B6PwIhAM3RtYg4CaGkcbFJrcJeCbAXCAC7LbfQSr8EdM79GyGw """.replace( "\n", "" ).encode() AAGUID = bytes.fromhex("0132d110bf4e4208a403ab4f5f12efe5") def test_parse_blob(): data = parse_blob(EXAMPLE_BLOB, EXAMPLE_CA) assert data.no == 15 assert len(data.entries) == 2 def test_find_by_aaguid(): data = parse_blob(EXAMPLE_BLOB, EXAMPLE_CA) mds = MdsAttestationVerifier(data) entry = mds.find_entry_by_aaguid(AAGUID) assert ( entry.metadata_statement.description == "FIDO Alliance Sample FIDO2 Authenticator" ) def test_find_by_aaguid_miss(): data = parse_blob(EXAMPLE_BLOB, EXAMPLE_CA) mds = MdsAttestationVerifier(data) entry = mds.find_entry_by_aaguid(bytes.fromhex("0102030405060708090a0b0c0d0e0f")) assert entry is None def test_find_by_chain_miss(): data = parse_blob(EXAMPLE_BLOB, EXAMPLE_CA) mds = MdsAttestationVerifier(data) entry = mds.find_entry_by_chain([EXAMPLE_CA]) assert entry is None def test_filter_entries(): data = parse_blob(EXAMPLE_BLOB, EXAMPLE_CA) mds = MdsAttestationVerifier(data, entry_filter=lambda e: e.aaguid != AAGUID) entry = mds.find_entry_by_aaguid(AAGUID) assert entry is None mds = MdsAttestationVerifier(data, entry_filter=lambda e: e.aaguid == AAGUID) assert mds.find_entry_by_aaguid(AAGUID) def test_lookup_filter_does_not_affect_find_entry_by_aaguid(): data = parse_blob(EXAMPLE_BLOB, EXAMPLE_CA) mds = MdsAttestationVerifier( data, attestation_filter=lambda e, _: e.aaguid != AAGUID ) assert mds.find_entry_by_aaguid(AAGUID) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/test_pcsc.py0000644000000000000000000000644014413232070013147 0ustar00# 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. import unittest import sys from unittest import mock from fido2.hid import CTAPHID sys.modules["smartcard"] = mock.Mock() sys.modules["smartcard.Exceptions"] = mock.Mock() sys.modules["smartcard.System"] = mock.Mock() sys.modules["smartcard.CardConnection"] = mock.Mock() sys.modules["smartcard.pcsc"] = mock.Mock() sys.modules["smartcard.pcsc.PCSCExceptions"] = mock.Mock() sys.modules["smartcard.pcsc.PCSCContext"] = mock.Mock() from fido2.pcsc import CtapPcscDevice # noqa E402 class PcscTest(unittest.TestCase): def test_pcsc_call_cbor(self): connection = mock.Mock() connection.transmit.side_effect = [(b"U2F_V2", 0x90, 0x00), (b"", 0x90, 0x00)] CtapPcscDevice(connection, "Mock") connection.transmit.assert_called_with( [0x80, 0x10, 0x80, 0x00, 0x01, 0x04, 0x00], None ) def test_pcsc_call_u2f(self): connection = mock.Mock() connection.transmit.side_effect = [ (b"U2F_V2", 0x90, 0x00), (b"", 0x90, 0x00), (b"u2f_resp", 0x90, 0x00), ] dev = CtapPcscDevice(connection, "Mock") res = dev.call(CTAPHID.MSG, b"\x00\x01\x00\x00\x05" + b"\x01" * 5 + b"\x00") connection.transmit.assert_called_with( [0x00, 0x01, 0x00, 0x00, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00], None ) self.assertEqual(res, b"u2f_resp\x90\x00") def test_pcsc_call_version_2(self): connection = mock.Mock() connection.transmit.side_effect = [(b"U2F_V2", 0x90, 0x00), (b"", 0x90, 0x00)] dev = CtapPcscDevice(connection, "Mock") self.assertEqual(dev.version, 2) def test_pcsc_call_version_1(self): connection = mock.Mock() connection.transmit.side_effect = [(b"U2F_V2", 0x90, 0x00), (b"", 0x63, 0x85)] dev = CtapPcscDevice(connection, "Mock") self.assertEqual(dev.version, 1) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/test_rpid.py0000644000000000000000000000531414413232070013154 0ustar00# coding=utf-8 # Copyright (c) 2013 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 fido2.rpid import verify_rp_id import unittest class TestRpId(unittest.TestCase): def test_valid_ids(self): self.assertTrue(verify_rp_id("example.com", "https://register.example.com")) self.assertTrue(verify_rp_id("example.com", "https://fido.example.com")) self.assertTrue(verify_rp_id("example.com", "https://www.example.com:444")) def test_invalid_ids(self): self.assertFalse(verify_rp_id("example.com", "http://example.com")) self.assertFalse(verify_rp_id("example.com", "http://www.example.com")) self.assertFalse(verify_rp_id("example.com", "https://example-test.com")) self.assertFalse( verify_rp_id("companyA.hosting.example.com", "https://register.example.com") ) self.assertFalse( verify_rp_id( "companyA.hosting.example.com", "https://companyB.hosting.example.com" ) ) def test_suffix_list(self): self.assertFalse(verify_rp_id("co.uk", "https://foobar.co.uk")) self.assertTrue(verify_rp_id("foobar.co.uk", "https://site.foobar.co.uk")) self.assertFalse(verify_rp_id("appspot.com", "https://example.appspot.com")) self.assertTrue( verify_rp_id("example.appspot.com", "https://example.appspot.com") ) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1688650037.942279 fido2-1.1.2/tests/test_server.py0000644000000000000000000001611214451540466013537 0ustar00import unittest from fido2.server import Fido2Server, U2FFido2Server, verify_app_id from fido2.webauthn import ( CollectedClientData, PublicKeyCredentialRpEntity, UserVerificationRequirement, AttestedCredentialData, AuthenticatorData, ) from fido2.utils import websafe_encode from .test_ctap2 import _ATT_CRED_DATA, _CRED_ID from .utils import U2FDevice class TestAppId(unittest.TestCase): def test_valid_ids(self): self.assertTrue( verify_app_id("https://example.com", "https://register.example.com") ) self.assertTrue( verify_app_id("https://example.com", "https://fido.example.com") ) self.assertTrue( verify_app_id("https://example.com", "https://www.example.com:444") ) self.assertTrue( verify_app_id( "https://companyA.hosting.example.com", "https://fido.companyA.hosting.example.com", ) ) self.assertTrue( verify_app_id( "https://companyA.hosting.example.com", "https://xyz.companyA.hosting.example.com", ) ) def test_invalid_ids(self): self.assertFalse(verify_app_id("https://example.com", "http://example.com")) self.assertFalse(verify_app_id("https://example.com", "http://www.example.com")) self.assertFalse( verify_app_id("https://example.com", "https://example-test.com") ) self.assertFalse( verify_app_id( "https://companyA.hosting.example.com", "https://register.example.com" ) ) self.assertFalse( verify_app_id( "https://companyA.hosting.example.com", "https://companyB.hosting.example.com", ) ) def test_effective_tld_names(self): self.assertFalse( verify_app_id("https://appspot.com", "https://foo.appspot.com") ) self.assertFalse(verify_app_id("https://co.uk", "https://example.co.uk")) class TestPublicKeyCredentialRpEntity(unittest.TestCase): def test_id_hash(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") rp_id_hash = ( b"\xa3y\xa6\xf6\xee\xaf\xb9\xa5^7\x8c\x11\x804\xe2u\x1eh/" b"\xab\x9f-0\xab\x13\xd2\x12U\x86\xce\x19G" ) self.assertEqual(rp.id_hash, rp_id_hash) USER = {"id": b"user_id", "name": "A. User"} class TestFido2Server(unittest.TestCase): def test_register_begin_rp(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) request, state = server.register_begin(USER) self.assertEqual( request["publicKey"]["rp"], {"id": "example.com", "name": "Example"} ) def test_register_begin_custom_challenge(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) challenge = b"1234567890123456" request, state = server.register_begin(USER, challenge=challenge) self.assertEqual(request["publicKey"]["challenge"], websafe_encode(challenge)) def test_register_begin_custom_challenge_too_short(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) challenge = b"123456789012345" with self.assertRaises(ValueError): request, state = server.register_begin(USER, challenge=challenge) def test_authenticate_complete_invalid_signature(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.PREFERRED, } client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://example.com", ) _AUTH_DATA = bytes.fromhex( "A379A6F6EEAFB9A55E378C118034E2751E682FAB9F2D30AB13D2125586CE1947010000001D" ) with self.assertRaisesRegex(ValueError, "Invalid signature."): server.authenticate_complete( state, [AttestedCredentialData(_ATT_CRED_DATA)], _CRED_ID, client_data, AuthenticatorData(_AUTH_DATA), b"INVALID", ) class TestU2FFido2Server(unittest.TestCase): def test_u2f(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") app_id = b"https://example.com" server = U2FFido2Server(app_id=app_id.decode("ascii"), rp=rp) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.PREFERRED, } client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://example.com", ) param = b"TOMATO GIVES " device = U2FDevice(param, app_id) auth_data = AttestedCredentialData.from_ctap1(param, device.public_key_bytes) authenticator_data, signature = device.sign(client_data) server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, ) def test_u2f_facets(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") app_id = b"https://www.example.com/facets.json" def verify_u2f_origin(origin): return origin in ("https://oauth.example.com", "https://admin.example.com") server = U2FFido2Server( app_id=app_id.decode("ascii"), rp=rp, verify_u2f_origin=verify_u2f_origin ) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.PREFERRED, } client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://oauth.example.com", ) param = b"TOMATO GIVES " device = U2FDevice(param, app_id) auth_data = AttestedCredentialData.from_ctap1(param, device.public_key_bytes) authenticator_data, signature = device.sign(client_data) server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, ) # Now with something not whitelisted client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://publicthingy.example.com", ) authenticator_data, signature = device.sign(client_data) with self.assertRaisesRegex( ValueError, "Invalid origin in CollectedClientData." ): server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1682067802.4645913 fido2-1.1.2/tests/test_tpm.py0000644000000000000000000000566314420450532013030 0ustar00# 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 fido2.attestation.tpm import TpmAttestationFormat, TpmPublicFormat import unittest class TestTpmObject(unittest.TestCase): def test_parse_tpm(self): data = bytes.fromhex( "ff54434780170022000b68cec627cc6411099a1f809fde4379f649aa170c7072d1adf230de439efc80810014f7c8b0cdeb31328648130a19733d6fff16e76e1300000003ef605603446ed8c56aa7608d01a6ea5651ee67a8a20022000bdf681917e18529c61e1b85a1e7952f3201eb59c609ed5d8e217e5de76b228bbd0022000b0a10d216b0c3ab82bfdc1f0a016ab9493384c7aee1937ee8800f76b30c9b71a7" # noqa ) tpm = TpmAttestationFormat.parse(data) self.assertEqual( tpm.data, bytes.fromhex("f7c8b0cdeb31328648130a19733d6fff16e76e13") ) def test_parse_too_short_of_a_tpm(self): with self.assertRaises(ValueError): TpmAttestationFormat.parse(bytes.fromhex("ff5443")) with self.assertRaises(ValueError) as e: data = bytes.fromhex( "ff54434780170022000b68cec627cc6411099a1f809fde4379f649aa170c7072d1adf230de439efc80810014f7c8b0cdeb31328648" # noqa ) TpmAttestationFormat.parse(data) self.assertEqual( e.exception.args[0], "Not enough data to read (need: 20, had: 9)." ) def test_parse_public_ecc(self): data = bytes.fromhex( "0023000b00060472000000100010000300100020b9174cd199f77552afcffe6b1f069c032ffdc4f56068dec4e189e7967b3bf6b0002037bf8aa7d93fddb9507319141c6fa31c8e48a1c6da013603a9f6e3913d157c66" # noqa ) TpmPublicFormat.parse(data) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/test_utils.py0000644000000000000000000000726414413232070013364 0ustar00# coding=utf-8 # Copyright (c) 2013 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. import unittest from fido2.utils import hmac_sha256, sha256, websafe_encode, websafe_decode class TestSha256(unittest.TestCase): def test_sha256_vectors(self): self.assertEqual( sha256(b"abc"), bytes.fromhex( "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" ), ) self.assertEqual( sha256(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), bytes.fromhex( "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" ), ) class TestHmacSha256(unittest.TestCase): def test_hmac_sha256_vectors(self): self.assertEqual( hmac_sha256(b"\x0b" * 20, b"Hi There"), bytes.fromhex( "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7" ), ) self.assertEqual( hmac_sha256(b"Jefe", b"what do ya want for nothing?"), bytes.fromhex( "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843" ), ) class TestWebSafe(unittest.TestCase): # Base64 vectors adapted from https://tools.ietf.org/html/rfc4648#section-10 def test_websafe_decode(self): self.assertEqual(websafe_decode(b""), b"") self.assertEqual(websafe_decode(b"Zg"), b"f") self.assertEqual(websafe_decode(b"Zm8"), b"fo") self.assertEqual(websafe_decode(b"Zm9v"), b"foo") self.assertEqual(websafe_decode(b"Zm9vYg"), b"foob") self.assertEqual(websafe_decode(b"Zm9vYmE"), b"fooba") self.assertEqual(websafe_decode(b"Zm9vYmFy"), b"foobar") def test_websafe_decode_unicode(self): self.assertEqual(websafe_decode(""), b"") self.assertEqual(websafe_decode("Zm9vYmFy"), b"foobar") def test_websafe_encode(self): self.assertEqual(websafe_encode(b""), "") self.assertEqual(websafe_encode(b"f"), "Zg") self.assertEqual(websafe_encode(b"fo"), "Zm8") self.assertEqual(websafe_encode(b"foo"), "Zm9v") self.assertEqual(websafe_encode(b"foob"), "Zm9vYg") self.assertEqual(websafe_encode(b"fooba"), "Zm9vYmE") self.assertEqual(websafe_encode(b"foobar"), "Zm9vYmFy") ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1688650037.942279 fido2-1.1.2/tests/test_webauthn.py0000644000000000000000000002776514451540466014066 0ustar00# 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 fido2.webauthn import ( Aaguid, AuthenticatorSelectionCriteria, CollectedClientData, ResidentKeyRequirement, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, PublicKeyCredentialParameters, PublicKeyCredentialDescriptor, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, ) from fido2.utils import websafe_encode import unittest import json class TestAaguid(unittest.TestCase): def test_aaguid(self): bs = b"\1" * 16 a = Aaguid(bs) assert a assert a == bs assert bs == a def test_aaguid_none(self): a = Aaguid(b"\0" * 16) assert not a assert a == Aaguid.NONE assert Aaguid.NONE == a def test_aaguid_wrong_length(self): with self.assertRaises(ValueError): Aaguid(b"1234") with self.assertRaises(ValueError): Aaguid.fromhex("11" * 15) with self.assertRaises(ValueError): Aaguid(b"\2" * 17) def test_aaguid_parse(self): a = Aaguid.parse("00000000-0000-0000-0000-000000000000") assert a == Aaguid.NONE b = Aaguid.parse("01020304-0102-0304-0506-010203040506") assert b == Aaguid.fromhex("01020304010203040506010203040506") assert b == Aaguid(bytes.fromhex("01020304010203040506010203040506")) class TestWebAuthnDataTypes(unittest.TestCase): def test_collected_client_data(self): o = CollectedClientData( b'{"type":"webauthn.create","challenge":"cdySOP-1JI4J_BpOeO9ut25rlZJueF16aO6auTTYAis","origin":"https://demo.yubico.com","crossOrigin":false}' # noqa ) assert o.type == "webauthn.create" assert o.origin == "https://demo.yubico.com" assert o.challenge == bytes.fromhex( "71dc9238ffb5248e09fc1a4e78ef6eb76e6b95926e785d7a68ee9ab934d8022b" ) assert o.cross_origin is False assert ( o.b64 == "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiY2R5U09QLTFKSTRKX0JwT2VPOXV0MjVybFpKdWVGMTZhTzZhdVRUWUFpcyIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ" # noqa ) assert o.hash == bytes.fromhex( "8b20a0b904b4747aacae71d55bf60b4eb2583f7e639f55f40baac23c2600c178" ) assert o == CollectedClientData.create( "webauthn.create", "cdySOP-1JI4J_BpOeO9ut25rlZJueF16aO6auTTYAis", "https://demo.yubico.com", ) o = CollectedClientData.create( "webauthn.create", "cdySOP-1JI4J_BpOeO9ut25rlZJueF16aO6auTTYAis", "https://demo.yubico.com", True, ) assert o.cross_origin is True def test_authenticator_selection_criteria(self): o = AuthenticatorSelectionCriteria( "platform", require_resident_key=True, user_verification="required" ) self.assertEqual( dict(o), { "authenticatorAttachment": "platform", "requireResidentKey": True, "residentKey": "required", "userVerification": "required", }, ) self.assertEqual(o.authenticator_attachment, "platform") self.assertEqual(o.require_resident_key, True) self.assertEqual(o.user_verification, "required") self.assertIsNone( AuthenticatorSelectionCriteria( authenticator_attachment="invalid" ).authenticator_attachment ) self.assertIsNone( AuthenticatorSelectionCriteria( user_verification="invalid" ).user_verification ) self.assertEqual( AuthenticatorSelectionCriteria(resident_key="invalid").resident_key, "discouraged", ) o = AuthenticatorSelectionCriteria() self.assertEqual(o.resident_key, "discouraged") self.assertEqual(o.require_resident_key, False) self.assertIsNone(o.authenticator_attachment) self.assertIsNone(o.user_verification) o = AuthenticatorSelectionCriteria(require_resident_key=True) self.assertEqual(o.resident_key, ResidentKeyRequirement.REQUIRED) self.assertEqual(o.require_resident_key, True) o = AuthenticatorSelectionCriteria(resident_key=False) self.assertEqual(o.require_resident_key, False) o = AuthenticatorSelectionCriteria(resident_key="required") self.assertEqual(o.resident_key, ResidentKeyRequirement.REQUIRED) self.assertEqual(o.require_resident_key, True) o = AuthenticatorSelectionCriteria(resident_key="preferred") self.assertEqual(o.resident_key, ResidentKeyRequirement.PREFERRED) self.assertEqual(o.require_resident_key, False) o = AuthenticatorSelectionCriteria(resident_key="discouraged") self.assertEqual(o.resident_key, ResidentKeyRequirement.DISCOURAGED) self.assertEqual(o.require_resident_key, False) def test_rp_entity(self): o = PublicKeyCredentialRpEntity("Example", "example.com") self.assertEqual(o, {"id": "example.com", "name": "Example"}) self.assertEqual(o.id, "example.com") self.assertEqual(o.name, "Example") with self.assertRaises(TypeError): PublicKeyCredentialRpEntity(id="example.com") with self.assertRaises(TypeError): PublicKeyCredentialRpEntity() def test_user_entity(self): o = PublicKeyCredentialUserEntity("Example", b"user", display_name="Display") self.assertEqual( o, { "id": websafe_encode(b"user"), "name": "Example", "displayName": "Display", }, ) self.assertEqual(o.id, b"user") self.assertEqual(o.name, "Example") self.assertEqual(o.display_name, "Display") with self.assertRaises(TypeError): PublicKeyCredentialUserEntity(name=b"user") with self.assertRaises(TypeError): PublicKeyCredentialUserEntity() def test_parameters(self): o = PublicKeyCredentialParameters("public-key", -7) self.assertEqual(o, {"type": "public-key", "alg": -7}) self.assertEqual(o.type, "public-key") self.assertEqual(o.alg, -7) p = PublicKeyCredentialParameters("invalid-type", -7) assert p.type is None with self.assertRaises(TypeError): PublicKeyCredentialParameters("public-key") with self.assertRaises(TypeError): PublicKeyCredentialParameters() def test_descriptor(self): o = PublicKeyCredentialDescriptor("public-key", b"credential_id") self.assertEqual( o, {"type": "public-key", "id": websafe_encode(b"credential_id")} ) self.assertEqual(o.type, "public-key") self.assertEqual(o.id, b"credential_id") self.assertIsNone(o.transports) o = PublicKeyCredentialDescriptor( "public-key", b"credential_id", ["usb", "nfc"] ) self.assertEqual( o, { "type": "public-key", "id": websafe_encode(b"credential_id"), "transports": ["usb", "nfc"], }, ) self.assertEqual(o.transports, ["usb", "nfc"]) PublicKeyCredentialDescriptor("public-key", b"credential_id", ["valid_value"]) d = PublicKeyCredentialDescriptor("wrong-type", b"credential_id") assert d.type is None with self.assertRaises(TypeError): PublicKeyCredentialDescriptor("public-key") with self.assertRaises(TypeError): PublicKeyCredentialDescriptor() def test_creation_options(self): o = PublicKeyCredentialCreationOptions( PublicKeyCredentialRpEntity(id="example.com", name="Example"), PublicKeyCredentialUserEntity(id=b"user_id", name="A. User"), b"request_challenge", [{"type": "public-key", "alg": -7}], 10000, [{"type": "public-key", "id": websafe_encode(b"credential_id")}], { "authenticatorAttachment": "platform", "residentKey": "required", "userVerification": "required", }, "direct", ) self.assertEqual(o.rp, {"id": "example.com", "name": "Example"}) self.assertEqual(o.user, {"id": websafe_encode(b"user_id"), "name": "A. User"}) self.assertIsNone(o.extensions) js = json.dumps(dict(o)) o2 = PublicKeyCredentialCreationOptions.from_dict(json.loads(js)) self.assertEqual(o, o2) o = PublicKeyCredentialCreationOptions.from_dict( { "rp": {"id": "example.com", "name": "Example"}, "user": {"id": websafe_encode(b"user_id"), "name": "A. User"}, "challenge": websafe_encode(b"request_challenge"), "pubKeyCredParams": [{"type": "public-key", "alg": -7}], } ) self.assertEqual(o.user.id, b"user_id") self.assertEqual(o.challenge, b"request_challenge"), self.assertIsNone(o.timeout) self.assertIsNone(o.authenticator_selection) self.assertIsNone(o.attestation) self.assertIsNone( PublicKeyCredentialCreationOptions( {"id": "example.com", "name": "Example"}, {"id": websafe_encode(b"user_id"), "name": "A. User"}, b"request_challenge", [{"type": "public-key", "alg": -7}], attestation="invalid", ).attestation ) js = json.dumps(dict(o)) o2 = PublicKeyCredentialCreationOptions.from_dict(json.loads(js)) self.assertEqual(o, o2) def test_request_options(self): o = PublicKeyCredentialRequestOptions( b"request_challenge", 10000, "example.com", [PublicKeyCredentialDescriptor(type="public-key", id=b"credential_id")], "discouraged", ) self.assertEqual(o.challenge, b"request_challenge") self.assertEqual(o.rp_id, "example.com") self.assertEqual(o.timeout, 10000) self.assertIsNone(o.extensions) js = json.dumps(dict(o)) o2 = PublicKeyCredentialRequestOptions.from_dict(json.loads(js)) self.assertEqual(o, o2) o = PublicKeyCredentialRequestOptions(b"request_challenge") self.assertIsNone(o.timeout) self.assertIsNone(o.rp_id) self.assertIsNone(o.allow_credentials) self.assertIsNone(o.user_verification) self.assertIsNone( PublicKeyCredentialRequestOptions( b"request_challenge", user_verification="invalid" ).user_verification ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/test_webauthn_legacy_mapping.py0000644000000000000000000001121714413232070017071 0ustar00# 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 fido2.webauthn import ( PublicKeyCredentialUserEntity, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, ) from fido2.features import webauthn_json_mapping import unittest class TestLegacyMapping(unittest.TestCase): @classmethod def setUpClass(cls): webauthn_json_mapping._enabled = False @classmethod def tearDownClass(cls): webauthn_json_mapping._enabled = True def test_user_entity(self): o = PublicKeyCredentialUserEntity("Example", b"user", display_name="Display") self.assertEqual( o, {"id": b"user", "name": "Example", "displayName": "Display"} ) self.assertEqual(o.id, b"user") self.assertEqual(o.name, "Example") self.assertEqual(o.display_name, "Display") def test_creation_options(self): o = PublicKeyCredentialCreationOptions( {"id": "example.com", "name": "Example"}, {"id": b"user_id", "name": "A. User"}, b"request_challenge", [{"type": "public-key", "alg": -7}], 10000, [{"type": "public-key", "id": b"credential_id"}], { "authenticatorAttachment": "platform", "residentKey": "required", "userVerification": "required", }, "direct", ) self.assertEqual(o.rp, {"id": "example.com", "name": "Example"}) self.assertEqual(o.user, {"id": b"user_id", "name": "A. User"}) self.assertIsNone(o.extensions) o2 = PublicKeyCredentialCreationOptions.from_dict(dict(o)) self.assertEqual(o, o2) o = PublicKeyCredentialCreationOptions( {"id": "example.com", "name": "Example"}, {"id": b"user_id", "name": "A. User"}, b"request_challenge", [{"type": "public-key", "alg": -7}], ) self.assertIsNone(o.timeout) self.assertIsNone(o.authenticator_selection) self.assertIsNone(o.attestation) self.assertIsNone( PublicKeyCredentialCreationOptions( {"id": "example.com", "name": "Example"}, {"id": b"user_id", "name": "A. User"}, b"request_challenge", [{"type": "public-key", "alg": -7}], attestation="invalid", ).attestation ) def test_request_options(self): o = PublicKeyCredentialRequestOptions( b"request_challenge", 10000, "example.com", [{"type": "public-key", "id": b"credential_id"}], "discouraged", ) self.assertEqual(o.challenge, b"request_challenge") self.assertEqual(o.rp_id, "example.com") self.assertEqual(o.timeout, 10000) self.assertIsNone(o.extensions) o = PublicKeyCredentialRequestOptions(b"request_challenge") self.assertIsNone(o.timeout) self.assertIsNone(o.rp_id) self.assertIsNone(o.allow_credentials) self.assertIsNone(o.user_verification) self.assertIsNone( PublicKeyCredentialRequestOptions( b"request_challenge", user_verification="invalid" ).user_verification ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1680684087.5695398 fido2-1.1.2/tests/utils.py0000644000000000000000000000400114413232070012307 0ustar00from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from fido2.utils import sha256, bytes2int from fido2.webauthn import AuthenticatorData class U2FDevice(object): _priv_key_bytes = bytes.fromhex( "308184020100301006072a8648ce3d020106052b8104000a046d306b02010104201672f5ceb963e07d475f5db9a975b7ad42ac3bf71ccddfb6539cc651a1156a6ba144034200045a4be44eb94eebff3ed665dde31deb74a060fabd214c5f5713aa043efa805dac8f45e0e17afe2eafbd1802c413c1e485fd854af9f06ef20938398f6795f12e0e" # noqa E501 ) def __init__(self, credential_id, app_id): assert isinstance(credential_id, bytes) assert isinstance(app_id, bytes) # Note: do not use in production, no garantees is provided this is # cryptographically safe to use. priv_key_params = ConcatKDFHash( algorithm=hashes.SHA256(), length=32, otherinfo=credential_id + app_id, backend=default_backend(), ).derive(self._priv_key_bytes) self.app_id = app_id self.priv_key = ec.derive_private_key( bytes2int(priv_key_params), ec.SECP256R1(), default_backend() ) self.pub_key = self.priv_key.public_key() self.public_key_bytes = self.pub_key.public_bytes( serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint ) self.credential_id = self.key_handle = credential_id def sign(self, client_data): authenticator_data = AuthenticatorData.create( sha256(self.app_id), flags=AuthenticatorData.FLAG.UP, counter=0 ) signature = self.priv_key.sign( authenticator_data + client_data.hash, ec.ECDSA(hashes.SHA256()) ) return authenticator_data, signature fido2-1.1.2/PKG-INFO0000644000000000000000000000256400000000000010511 0ustar00Metadata-Version: 2.1 Name: fido2 Version: 1.1.2 Summary: FIDO2/WebAuthn library for implementing clients and servers. Home-page: https://github.com/Yubico/python-fido2 Keywords: fido2,webauthn,ctap,u2f Author: Dain Nilsson Author-email: dain@yubico.com Requires-Python: >=3.7,<4.0 Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: License :: OSI Approved :: BSD License Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) Classifier: Operating System :: MacOS Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Topic :: Internet Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules Provides-Extra: pcsc Requires-Dist: cryptography (>=2.6,!=35,<44) Requires-Dist: pyscard (>=1.9,<3) ; extra == "pcsc" Project-URL: Repository, https://github.com/Yubico/python-fido2