fido2-0.8.1/0000755000175000017500000000000013566742204012404 5ustar daindain00000000000000fido2-0.8.1/COPYING0000644000175000017500000000243013275566137013444 0ustar daindain00000000000000Copyright (c) 2018 Yubico AB All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. fido2-0.8.1/COPYING.APLv20000644000175000017500000002613613275566136014337 0ustar daindain00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. fido2-0.8.1/COPYING.MPLv20000644000175000017500000004052613275566136014352 0ustar daindain00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. fido2-0.8.1/examples/0000755000175000017500000000000013566742203014221 5ustar daindain00000000000000fido2-0.8.1/examples/acr122u.py0000644000175000017500000000450513544577440015763 0ustar daindain00000000000000from 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) pass return "n/a" def led_control( self, red=False, green=False, blink_count=0, red_end_blink=False, green_end_blink=False, ): """ Reader's led control :param red: boolean. red led on :param green: boolean. green let on :param blink_count: int. if needs to blink value > 0. blinks count :param red_end_blink: boolean. state of red led at the end of blinking :param green_end_blink: boolean. state of green led at the end of blinking :return: """ try: if blink_count > 0: cbyte = ( 0b00001100 + (0b01 if red_end_blink else 0b00) + (0b10 if green_end_blink else 0b00) ) cbyte |= (0b01000000 if red else 0b00000000) + ( 0b10000000 if green else 0b00000000 ) else: cbyte = 0b00001100 + (0b01 if red else 0b00) + (0b10 if green else 0b00) apdu = ( b"\xff\x00\x40" + bytes([cbyte & 0xFF]) + b"\4" + b"\5\3" + bytes([blink_count]) + b"\0" ) self.pcsc.apdu_exchange(apdu) except Exception as e: print("LED control error:", e) dev = next(CtapPcscDevice.list_devices()) print("CONNECT: %s" % dev) pcsc_device = Acr122uPcscDevice(dev) pcsc_device.led_control(False, True, 0) print("version: %s" % pcsc_device.reader_version()) pcsc_device.led_control(True, False, 0) time.sleep(1) pcsc_device.led_control(False, True, 3) fido2-0.8.1/examples/acr122usam.py0000644000175000017500000002466513544577440016475 0ustar daindain00000000000000# 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) pass return "n/a" def led_control( self, red=False, green=False, blink_count=0, red_end_blink=False, green_end_blink=False, ): """ Reader's led control :param red: boolean. red led on :param green: boolean. green let on :param blink_count: int. if needs to blink value > 0. blinks count :param red_end_blink: boolean. state of red led at the end of blinking :param green_end_blink: boolean. state of green led at the end of blinking :return: """ try: if blink_count > 0: cbyte = ( 0b00001100 + (0b01 if red_end_blink else 0b00) + (0b10 if green_end_blink else 0b00) ) cbyte |= (0b01000000 if red else 0b00000000) + ( 0b10000000 if green else 0b00000000 ) else: cbyte = 0b00001100 + (0b01 if red else 0b00) + (0b10 if green else 0b00) apdu = ( b"\xff\x00\x40" + bytes([cbyte & 0xFF]) + b"\4" + b"\5\3" + bytes([blink_count]) + b"\0" ) self.apdu_plain(apdu) except Exception as e: print("LED control error:", e) dev = next(Acr122uSamPcscDevice.list_devices()) print("CONNECT: %s" % dev) print("version: %s" % dev.reader_version()) print("atr: %s" % bytes(dev.get_atr()).hex()) print("ats: %s" % dev.ats.hex()) # uncomment if you want to see parameters from card's selection # dev.get_ats(True) # dev._select() dev.led_control(False, True, 0) chal = sha256(b"AAA") appid = sha256(b"BBB") ctap1 = CTAP1(dev) print("ctap1 version:", ctap1.get_version()) reg = ctap1.register(chal, appid) print("u2f register:", reg) reg.verify(appid, chal) print("Register message verify OK") auth = ctap1.authenticate(chal, appid, reg.key_handle) print("u2f authenticate: ", auth) res = auth.verify(appid, chal, reg.public_key) print("Authenticate message verify OK") dev.led_control() fido2-0.8.1/examples/acr1252u.py0000644000175000017500000001304513544577440016047 0ustar daindain00000000000000from fido2.pcsc import CtapPcscDevice import time # control codes: # 3225264 - magic number!!! # 0x42000000 + 3500 - cross platform way C_CODE = 3225264 class Acr1252uPcscDevice(object): def __init__(self, pcsc_device): self.pcsc = pcsc_device def reader_version(self): try: res = self.pcsc.control_exchange(C_CODE, b"\xe0\x00\x00\x18\x00") if len(res) > 0 and res.find(b"\xe1\x00\x00\x00") == 0: reslen = res[4] if reslen == len(res) - 5: strres = res[5 : 5 + reslen].decode("utf-8") return strres except Exception as e: print("Get version error:", e) return "n/a" def reader_serial_number(self): try: res = self.pcsc.control_exchange(C_CODE, b"\xe0\x00\x00\x33\x00") if len(res) > 0 and res.find(b"\xe1\x00\x00\x00") == 0: reslen = res[4] if reslen == len(res) - 5: strres = res[5 : 5 + reslen].decode("utf-8") return strres except Exception as e: print("Get serial number error:", e) return "n/a" def led_control(self, red=False, green=False): try: cbyte = (0b01 if red else 0b00) + (0b10 if green else 0b00) result = self.pcsc.control_exchange( C_CODE, b"\xe0\x00\x00\x29\x01" + bytes([cbyte]) ) if len(result) > 0 and result.find(b"\xe1\x00\x00\x00") == 0: result_length = result[4] if result_length == 1: ex_red = bool(result[5] & 0b01) ex_green = bool(result[5] & 0b10) return True, ex_red, ex_green except Exception as e: print("LED control error:", e) return False, False, False def led_status(self): try: result = self.pcsc.control_exchange(C_CODE, b"\xe0\x00\x00\x29\x00") if len(result) > 0 and result.find(b"\xe1\x00\x00\x00") == 0: result_length = result[4] if result_length == 1: ex_red = bool(result[5] & 0b01) ex_green = bool(result[5] & 0b10) return True, ex_red, ex_green except Exception as e: print("LED status error:", e) return False, False, False def get_polling_settings(self): try: res = self.pcsc.control_exchange(C_CODE, b"\xe0\x00\x00\x23\x00") if len(res) > 0 and res.find(b"\xe1\x00\x00\x00") == 0: reslen = res[4] if reslen == 1: return True, res[5] except Exception as e: print("Get polling settings error:", e) return False, 0 def set_polling_settings(self, settings): try: res = self.pcsc.control_exchange( C_CODE, b"\xe0\x00\x00\x23\x01" + bytes([settings & 0xFF]) ) if len(res) > 0 and res.find(b"\xe1\x00\x00\x00") == 0: reslen = res[4] if reslen == 1: return True, res[5] except Exception as e: print("Set polling settings error:", e) return False, 0 def get_picc_operation_parameter(self): try: res = self.pcsc.control_exchange(C_CODE, b"\xe0\x00\x00\x20\x00") if len(res) > 0 and res.find(b"\xe1\x00\x00\x00") == 0: reslen = res[4] if reslen == 1: return True, res[5] except Exception as e: print("Get PICC Operating Parameter error:", e) return False, 0 def set_picc_operation_parameter(self, param): try: res = self.pcsc.control_exchange( C_CODE, b"\xe0\x00\x00\x20\x01" + bytes([param]) ) if len(res) > 0 and res.find(b"\xe1\x00\x00\x00") == 0: reslen = res[4] if reslen == 1: return True, res[5] except Exception as e: print("Set PICC Operating Parameter error:", e) return False, 0 dev = next(CtapPcscDevice.list_devices()) print("CONNECT: %s" % dev) pcsc_device = Acr1252uPcscDevice(dev) if pcsc_device is not None: print("version: %s" % pcsc_device.reader_version()) print("serial number: %s" % pcsc_device.reader_serial_number()) print("") result, settings = pcsc_device.set_polling_settings(0x8B) print("write polling settings: %r 0x%x" % (result, settings)) result, settings = pcsc_device.get_polling_settings() print("polling settings: %r 0x%x" % (result, settings)) set_desc = [ [0, "Auto PICC Polling"], [1, "Turn off Antenna Field if no PICC is found"], [2, "Turn off Antenna Field if the PICC is inactive"], [3, "Activate the PICC when detected"], [7, "Enforce ISO 14443-A Part 4"], ] for x in set_desc: print(x[1], "on" if settings & (1 << x[0]) else "off") interval_desc = [250, 500, 1000, 2500] print("PICC Poll Interval for PICC", interval_desc[(settings >> 4) & 0b11], "ms") print("") print( "PICC operation parameter: %r 0x%x" % pcsc_device.get_picc_operation_parameter() ) print("") result, red, green = pcsc_device.led_control(True, False) print("led control result:", result, "red:", red, "green:", green) result, red, green = pcsc_device.led_status() print("led state result:", result, "red:", red, "green:", green) time.sleep(1) pcsc_device.led_control(False, False) fido2-0.8.1/examples/credential.py0000644000175000017500000001065513564737350016721 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found (starts from USB, then looks into NFC), creates a new credential for it, and authenticates the credential. This works with both FIDO 2.0 devices as well as with U2F devices. On Windows, the native WebAuthn API will be used. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice from fido2.client import Fido2Client, WindowsClient from fido2.server import Fido2Server from getpass import getpass import sys use_prompt = False pin = None uv = "discouraged" if WindowsClient.is_available(): # Use the Windows WebAuthn API if available client = WindowsClient("https://example.com") else: # Locate a device dev = next(CtapHidDevice.list_devices(), None) if dev is not None: print("Use USB HID channel.") use_prompt = True else: try: from fido2.pcsc import CtapPcscDevice dev = next(CtapPcscDevice.list_devices(), None) print("Use NFC channel.") except Exception as e: print("NFC channel search error:", e) if not dev: print("No FIDO device found") sys.exit(1) # Set up a FIDO 2 client using the origin https://example.com client = Fido2Client(dev, "https://example.com") # Prefer UV if supported if client.info.options.get("uv"): uv = "preferred" print("Authenticator supports User Verification") elif client.info.options.get("clientPin"): # Prompt for PIN if needed pin = getpass("Please enter PIN: ") else: print("PIN not set, won't use") server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="direct") user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, user_verification=uv, authenticator_attachment="cross-platform" ) # Create a credential if use_prompt: print("\nTouch your authenticator device now...\n") attestation_object, client_data = client.make_credential( create_options["publicKey"], pin=pin ) # Complete registration auth_data = server.register_complete(state, client_data, attestation_object) credentials = [auth_data.credential_data] print("New credential created!") print("CLIENT DATA:", client_data) print("ATTESTATION OBJECT:", attestation_object) print() print("CREDENTIAL DATA:", auth_data.credential_data) # Prepare parameters for getAssertion request_options, state = server.authenticate_begin(credentials, user_verification=uv) # Authenticate the credential if use_prompt: print("\nTouch your authenticator device now...\n") assertions, client_data = client.get_assertion(request_options["publicKey"], pin=pin) assertion = assertions[0] # Only one cred in allowCredentials, only one response. # Complete authenticator server.authenticate_complete( state, credentials, assertion.credential["id"], client_data, assertion.auth_data, assertion.signature, ) print("Credential authenticated!") print("CLIENT DATA:", client_data) print() print("ASSERTION DATA:", assertion) fido2-0.8.1/examples/get_info.py0000644000175000017500000000465113544577440016400 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to each attached FIDO device, and: 1. If the device supports CBOR commands, perform a getInfo command. 2. If the device supports WINK, perform the wink command. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice, CAPABILITY from fido2.ctap2 import CTAP2 try: from fido2.pcsc import CtapPcscDevice except ImportError: CtapPcscDevice = None def enumerate_devices(): for dev in CtapHidDevice.list_devices(): yield dev if CtapPcscDevice: for dev in CtapPcscDevice.list_devices(): yield dev for dev in enumerate_devices(): print("CONNECT: %s" % dev) print("CTAPHID protocol version: %d" % dev.version) if dev.capabilities & CAPABILITY.CBOR: ctap2 = CTAP2(dev) info = ctap2.get_info() print("DEVICE INFO: %s" % info) else: print("Device does not support CBOR") if dev.capabilities & CAPABILITY.WINK: dev.wink() print("WINK sent!") else: print("Device does not support WINK") dev.close() fido2-0.8.1/examples/hmac_secret.py0000644000175000017500000001150713564737350017061 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found which supports the HmacSecret extension, creates a new credential for it with the extension enabled, and uses it to derive two separate secrets. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice from fido2.client import Fido2Client from fido2.extensions import HmacSecretExtension from getpass import getpass from binascii import b2a_hex import sys import os try: from fido2.pcsc import CtapPcscDevice except ImportError: CtapPcscDevice = None def enumerate_devices(): for dev in CtapHidDevice.list_devices(): yield dev if CtapPcscDevice: for dev in CtapPcscDevice.list_devices(): yield dev # Locate a device for dev in enumerate_devices(): client = Fido2Client(dev, "https://example.com") if HmacSecretExtension.NAME in client.info.extensions: break else: print("No Authenticator with the HmacSecret extension found!") sys.exit(1) use_nfc = CtapPcscDevice and isinstance(dev, CtapPcscDevice) # Prepare parameters for makeCredential rp = {"id": "example.com", "name": "Example RP"} user = {"id": b"user_id", "name": "A. User"} challenge = b"Y2hhbGxlbmdl" # Prompt for PIN if needed pin = None if client.info.options.get("clientPin"): pin = getpass("Please enter PIN:") else: print("no pin") hmac_ext = HmacSecretExtension(client.ctap2) # Create a credential if not use_nfc: print("\nTouch your authenticator device now...\n") attestation_object, client_data = client.make_credential( { "rp": rp, "user": user, "challenge": challenge, "pubKeyCredParams": [{"type": "public-key", "alg": -7}], "extensions": hmac_ext.create_dict(), }, pin=pin, ) # HmacSecret result: hmac_result = hmac_ext.results_for(attestation_object.auth_data) credential = attestation_object.auth_data.credential_data print("New credential created, with the HmacSecret extension.") # Prepare parameters for getAssertion challenge = b"Q0hBTExFTkdF" # Use a new challenge for each call. allow_list = [{"type": "public-key", "id": credential.credential_id}] # Generate a salt for HmacSecret: salt = os.urandom(32) print("Authenticate with salt:", b2a_hex(salt)) # Authenticate the credential if not use_nfc: print("\nTouch your authenticator device now...\n") assertions, client_data = client.get_assertion( { "rpId": rp["id"], "challenge": challenge, "allowCredentials": allow_list, "extensions": hmac_ext.get_dict(salt), }, pin=pin, ) assertion = assertions[0] # Only one cred in allowList, only one response. hmac_res = hmac_ext.results_for(assertion.auth_data) print("Authenticated, secret:", b2a_hex(hmac_res[0])) # Authenticate again, using two salts to generate two secrets: # Generate a second salt for HmacSecret: salt2 = os.urandom(32) print("Authenticate with second salt:", b2a_hex(salt2)) if not use_nfc: print("\nTouch your authenticator device now...\n") # The first salt is reused, which should result in the same secret. assertions, client_data = client.get_assertion( { "rpId": rp["id"], "challenge": challenge, "allowCredentials": allow_list, "extensions": hmac_ext.get_dict(salt, salt2), }, pin=pin, ) assertion = assertions[0] # Only one cred in allowList, only one response. hmac_res = hmac_ext.results_for(assertion.auth_data) print("Old secret:", b2a_hex(hmac_res[0])) print("New secret:", b2a_hex(hmac_res[1])) fido2-0.8.1/examples/multi_device.py0000644000175000017500000000645313564737350017261 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to each FIDO device found, and causes them all to blink until the user triggers one to select it. A new credential is created for that authenticator, and the operation is cancelled for the others. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice, STATUS from fido2.client import Fido2Client, ClientError from threading import Event, Thread import sys # Locate a device devs = list(CtapHidDevice.list_devices()) if not devs: print("No FIDO device found") sys.exit(1) clients = [Fido2Client(d, "https://example.com") for d in devs] # Prepare parameters for makeCredential rp = {"id": "example.com", "name": "Example RP"} user = {"id": b"user_id", "name": "A. User"} challenge = b"Y2hhbGxlbmdl" cancel = Event() attestation, client_data = None, None has_prompted = False def on_keepalive(status): global has_prompted # Don't prompt for each device. if status == STATUS.UPNEEDED and not has_prompted: print("\nTouch your authenticator device now...\n") has_prompted = True def work(client): global attestation, client_data try: attestation, client_data = client.make_credential( { "rp": rp, "user": user, "challenge": challenge, "pubKeyCredParams": [{"type": "public-key", "alg": -7}], }, event=cancel, on_keepalive=on_keepalive, ) except ClientError as e: if e.code != ClientError.ERR.TIMEOUT: raise else: return cancel.set() print("New credential created!") print("ATTESTATION OBJECT:", attestation) print() print("CREDENTIAL DATA:", attestation.auth_data.credential_data) threads = [] for client in clients: t = Thread(target=work, args=(client,)) threads.append(t) t.start() for t in threads: t.join() if not cancel.is_set(): print("Operation timed out!") fido2-0.8.1/examples/resident_key.py0000644000175000017500000001072113565742566017274 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found (starts from USB, then looks into NFC), creates a new credential for it, and authenticates the credential. This works with both FIDO 2.0 devices as well as with U2F devices. On Windows, the native WebAuthn API will be used. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.hid import CtapHidDevice from fido2.client import Fido2Client, WindowsClient from fido2.server import Fido2Server from getpass import getpass import sys use_prompt = False pin = None uv = "discouraged" uv = "preferred" if WindowsClient.is_available(): # Use the Windows WebAuthn API if available client = WindowsClient("https://example.com") else: # Locate a device dev = next(CtapHidDevice.list_devices(), None) if dev is not None: print("Use USB HID channel.") use_prompt = True else: try: from fido2.pcsc import CtapPcscDevice dev = next(CtapPcscDevice.list_devices(), None) print("Use NFC channel.") except Exception as e: print("NFC channel search error:", e) if not dev: print("No FIDO device found") sys.exit(1) # Set up a FIDO 2 client using the origin https://example.com client = Fido2Client(dev, "https://example.com") # Prefer UV if supported if client.info.options.get("uv"): uv = "preferred" print("Authenticator supports User Verification") elif client.info.options.get("clientPin"): # Prompt for PIN if needed pin = getpass("Please enter PIN: ") else: print("PIN not set, won't use") server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="direct") user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, resident_key=True, user_verification=uv, authenticator_attachment="cross-platform", ) # Create a credential if use_prompt: print("\nTouch your authenticator device now...\n") attestation_object, client_data = client.make_credential( create_options["publicKey"], pin=pin ) # Complete registration auth_data = server.register_complete(state, client_data, attestation_object) credentials = [auth_data.credential_data] print("New credential created!") print("CLIENT DATA:", client_data) print("ATTESTATION OBJECT:", attestation_object) print() print("CREDENTIAL DATA:", auth_data.credential_data) # Prepare parameters for getAssertion request_options, state = server.authenticate_begin(user_verification=uv) # Authenticate the credential if use_prompt: print("\nTouch your authenticator device now...\n") assertions, client_data = client.get_assertion(request_options["publicKey"], pin=pin) assertion = assertions[0] # Only one cred in allowCredentials, only one response. # Complete authenticator server.authenticate_complete( state, credentials, assertion.credential["id"], client_data, assertion.auth_data, assertion.signature, ) print("Credential authenticated!") print("CLIENT DATA:", client_data) print() print("ASSERTION DATA:", assertion) fido2-0.8.1/examples/server/0000755000175000017500000000000013566742204015530 5ustar daindain00000000000000fido2-0.8.1/examples/server/Pipfile0000644000175000017500000000035613406733061017041 0ustar daindain00000000000000[[source]] verify_ssl = true name = "pypi" url = "https://pypi.org/simple" [packages] flask = "*" pyOpenSSL = "*" "5448283" = {editable = true, path = "./../.."} [scripts] server = "python server.py" server-u2f = "python server-u2f.py" fido2-0.8.1/examples/server/Pipfile.lock0000755000175000017500000002444013554263241017775 0ustar daindain00000000000000{ "_meta": { "hash": { "sha256": "bf2fa9b63243b7172b84ae28c7fc3abc291ed1bb91f4919f2b2c47bfc0ccdd4d" }, "pipfile-spec": 6, "requires": {}, "sources": [ { "name": "pypi", "url": "https://pypi.org/simple", "verify_ssl": true } ] }, "default": { "5448283": { "editable": true, "path": "./../.." }, "cffi": { "hashes": [ "sha256:08f99e8b38d5134d504aa7e486af8e4fde66a2f388bbecc270cdd1e00fa09ff8", "sha256:1112d2fc92a867a6103bce6740a549e74b1d320cf28875609f6e93857eee4f2d", "sha256:1b9ab50c74e075bd2ae489853c5f7f592160b379df53b7f72befcbe145475a36", "sha256:24eff2997436b6156c2f30bed215c782b1d8fd8c6a704206053c79af95962e45", "sha256:2eff642fbc9877a6449026ad66bf37c73bf4232505fb557168ba5c502f95999b", "sha256:362e896cea1249ed5c2a81cf6477fabd9e1a5088aa7ea08358a4c6b0998294d2", "sha256:40eddb3589f382cb950f2dcf1c39c9b8d7bd5af20665ce273815b0d24635008b", "sha256:5ed40760976f6b8613d4a0db5e423673ca162d4ed6c9ed92d1f4e58a47ee01b5", "sha256:632c6112c1e914c486f06cfe3f0cc507f44aa1e00ebf732cedb5719e6aa0466a", "sha256:64d84f0145e181f4e6cc942088603c8db3ae23485c37eeda71cb3900b5e67cb4", "sha256:6cb4edcf87d0e7f5bdc7e5c1a0756fbb37081b2181293c5fdf203347df1cd2a2", "sha256:6f19c9df4785305669335b934c852133faed913c0faa63056248168966f7a7d5", "sha256:719537b4c5cd5218f0f47826dd705fb7a21d83824920088c4214794457113f3f", "sha256:7b0e337a70e58f1a36fb483fd63880c9e74f1db5c532b4082bceac83df1523fa", "sha256:853376efeeb8a4ae49a737d5d30f5db8cdf01d9319695719c4af126488df5a6a", "sha256:85bbf77ffd12985d76a69d2feb449e35ecdcb4fc54a5f087d2bd54158ae5bb0c", "sha256:8978115c6f0b0ce5880bc21c967c65058be8a15f1b81aa5fdbdcbea0e03952d1", "sha256:8f7eec920bc83692231d7306b3e311586c2e340db2dc734c43c37fbf9c981d24", "sha256:8fe230f612c18af1df6f348d02d682fe2c28ca0a6c3856c99599cdacae7cf226", "sha256:92068ebc494b5f9826b822cec6569f1f47b9a446a3fef477e1d11d7fac9ea895", "sha256:b57e1c8bcdd7340e9c9d09613b5e7fdd0c600be142f04e2cc1cc8cb7c0b43529", "sha256:ba956c9b44646bc1852db715b4a252e52a8f5a4009b57f1dac48ba3203a7bde1", "sha256:ca42034c11eb447497ea0e7b855d87ccc2aebc1e253c22e7d276b8599c112a27", "sha256:dc9b2003e9a62bbe0c84a04c61b0329e86fccd85134a78d7aca373bbbf788165", "sha256:dd308802beb4b2961af8f037becbdf01a1e85009fdfc14088614c1b3c383fae5", "sha256:e77cd105b19b8cd721d101687fcf665fd1553eb7b57556a1ef0d453b6fc42faa", "sha256:f56dff1bd81022f1c980754ec721fb8da56192b026f17f0f99b965da5ab4fbd2", "sha256:fa4cc13c03ea1d0d37ce8528e0ecc988d2365e8ac64d8d86cafab4038cb4ce89", "sha256:fa8cf1cb974a9f5911d2a0303f6adc40625c05578d8e7ff5d313e1e27850bd59", "sha256:fb003019f06d5fc0aa4738492ad8df1fa343b8a37cbcf634018ad78575d185df", "sha256:fd409b7778167c3bcc836484a8f49c0e0b93d3e745d975749f83aa5d18a5822f", "sha256:fe5d65a3ee38122003245a82303d11ac05ff36531a8f5ce4bc7d4bbc012797e1" ], "version": "==1.13.0" }, "click": { "hashes": [ "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" ], "version": "==7.0" }, "cryptography": { "hashes": [ "sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c", "sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595", "sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad", "sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651", "sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2", "sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff", "sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d", "sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42", "sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d", "sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e", "sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912", "sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793", "sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13", "sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7", "sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0", "sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879", "sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f", "sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9", "sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2", "sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf", "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8" ], "version": "==2.8" }, "flask": { "hashes": [ "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52", "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6" ], "index": "pypi", "version": "==1.1.1" }, "itsdangerous": { "hashes": [ "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" ], "version": "==1.1.0" }, "jinja2": { "hashes": [ "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" ], "version": "==2.10.3" }, "markupsafe": { "hashes": [ "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" ], "version": "==1.1.1" }, "pycparser": { "hashes": [ "sha256:609929d70bf013d8d1132d92ad85766a60cc167339626343d68b99367aba5bcd", "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" ], "version": "==2.19" }, "pyopenssl": { "hashes": [ "sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200", "sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6" ], "index": "pypi", "version": "==19.0.0" }, "six": { "hashes": [ "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" ], "version": "==1.12.0" }, "werkzeug": { "hashes": [ "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7", "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4" ], "version": "==0.16.0" } }, "develop": {} } fido2-0.8.1/examples/server/README.adoc0000644000175000017500000000457113406733061017316 0ustar daindain00000000000000== WebAuthn Server Example This example shows a minimal website that uses python-fido2 to implement WebAuthn credential registration, and use. === Running To run this sample, you will need `pipenv`. For instructions on installing `pipenv`, see https://docs.pipenv.org. Run the following command in the `examples/server` directory to set up the example: $ pipenv install Once the environment has been created, you can run the server by running: $ pipenv run server When the server is running, use a browser supporting WebAuthn and open https://localhost:5000 to access the website. NOTE: As this server uses a self-signed certificate, you will get warnings in your browser about the connection not being secure. This is expected, and you can safely proceed to the site. === Using the website The site allows you to register a WebAuthn credential, and to authenticate it. Credentials are only stored in memory, and stopping the server will cause it to "forget" any registered credentials. ==== Registration 1. Click on the `Register` link to begin credential registration. 2. If not already inserted, insert your U2F/FIDO2 Authenticator now. 3. Touch the button to activate the Authenticator. 4. A popup will indicate whether the registration was successful. Click `OK`. ==== Authentication NOTE: You must register a credential prior to authentication. 1. Click on the `Authenticate` link to begin authentication. 2. If not already inserted, insert your U2F/FIDO2 Authenticator now. 3. Touch the button to activate the Authenticator. 4. A popup will indicate whether the authentication was successful. Click `OK`. === Supporting existing U2F credentials If you have existing U2F credentials that you wish to support, this library offers a U2FFido2Server class which can help with this. This directory includes a slightly altered version of the example server which uses this class to authenticate U2F credentials as well as WebAuthn credentials. To run this version of the server, run: $ pipenv run server-u2f This version allows registration both using the newer WebAuthn APIs and by using the legacy U2F APIs, so that you can test authentication using both credential types. The source code for this version of the server is in `server-u2f.py`. NOTE: There should be no need to support registration of new U2F credentials as new registrations should be using the WebAuthn APIs, even for existing users. fido2-0.8.1/examples/server/server-u2f.py0000644000175000017500000001405013564737350020106 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Example demo server to use a supported web browser to call the WebAuthn APIs to register and use a credential. See the file README.adoc in this directory for details. Navigate to https://localhost:5000 in a supported web browser. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.webauthn import PublicKeyCredentialRpEntity from fido2.client import ClientData from fido2.server import U2FFido2Server from fido2.ctap2 import AttestationObject, AuthenticatorData from fido2.ctap1 import RegistrationData from fido2.utils import sha256, websafe_encode from fido2 import cbor from flask import Flask, session, request, redirect, abort import os app = Flask(__name__, static_url_path="") app.secret_key = os.urandom(32) # Used for session. rp = PublicKeyCredentialRpEntity("localhost", "Demo server") # By using the U2FFido2Server class, we can support existing credentials # registered by the legacy u2f.register API for an appId. server = U2FFido2Server("https://localhost:5000", rp) # Registered credentials are stored globally, in memory only. Single user # support, state is lost when the server terminates. credentials = [] @app.route("/") def index(): return redirect("/index-u2f.html") @app.route("/api/register/begin", methods=["POST"]) def register_begin(): registration_data, state = server.register_begin( { "id": b"user_id", "name": "a_user", "displayName": "A. User", "icon": "https://example.com/image.png", }, credentials, ) session["state"] = state print("\n\n\n\n") print(registration_data) print("\n\n\n\n") return cbor.encode(registration_data) @app.route("/api/register/complete", methods=["POST"]) def register_complete(): data = cbor.decode(request.get_data()) client_data = ClientData(data["clientDataJSON"]) att_obj = AttestationObject(data["attestationObject"]) print("clientData", client_data) print("AttestationObject:", att_obj) auth_data = server.register_complete(session["state"], client_data, att_obj) credentials.append(auth_data.credential_data) print("REGISTERED CREDENTIAL:", auth_data.credential_data) return cbor.encode({"status": "OK"}) @app.route("/api/authenticate/begin", methods=["POST"]) def authenticate_begin(): if not credentials: abort(404) auth_data, state = server.authenticate_begin(credentials) session["state"] = state return cbor.encode(auth_data) @app.route("/api/authenticate/complete", methods=["POST"]) def authenticate_complete(): if not credentials: abort(404) data = cbor.decode(request.get_data()) credential_id = data["credentialId"] client_data = ClientData(data["clientDataJSON"]) auth_data = AuthenticatorData(data["authenticatorData"]) signature = data["signature"] print("clientData", client_data) print("AuthenticatorData", auth_data) server.authenticate_complete( session.pop("state"), credentials, credential_id, client_data, auth_data, signature, ) print("ASSERTION OK") return cbor.encode({"status": "OK"}) ############################################################################### # WARNING! # # The below functions allow the registration of legacy U2F credentials. # This is provided FOR TESTING PURPOSES ONLY. New credentials should be # registered using the WebAuthn APIs. ############################################################################### @app.route("/api/u2f/begin", methods=["POST"]) def u2f_begin(): registration_data, state = server.register_begin( { "id": b"user_id", "name": "a_user", "displayName": "A. User", "icon": "https://example.com/image.png", }, credentials, ) session["state"] = state print("\n\n\n\n") print(registration_data) print("\n\n\n\n") return cbor.encode(websafe_encode(registration_data["publicKey"]["challenge"])) @app.route("/api/u2f/complete", methods=["POST"]) def u2f_complete(): data = cbor.decode(request.get_data()) client_data = ClientData.from_b64(data["clientData"]) reg_data = RegistrationData.from_b64(data["registrationData"]) print("clientData", client_data) print("U2F RegistrationData:", reg_data) att_obj = AttestationObject.from_ctap1(sha256(b"https://localhost:5000"), reg_data) print("AttestationObject:", att_obj) auth_data = att_obj.auth_data credentials.append(auth_data.credential_data) print("REGISTERED U2F CREDENTIAL:", auth_data.credential_data) return cbor.encode({"status": "OK"}) if __name__ == "__main__": print(__doc__) app.run(ssl_context="adhoc", debug=True) fido2-0.8.1/examples/server/server.py0000644000175000017500000001064213565545322017414 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Example demo server to use a supported web browser to call the WebAuthn APIs to register and use a credential. See the file README.adoc in this directory for details. Navigate to https://localhost:5000 in a supported web browser. """ from __future__ import print_function, absolute_import, unicode_literals from fido2.webauthn import PublicKeyCredentialRpEntity from fido2.client import ClientData from fido2.server import Fido2Server from fido2.ctap2 import AttestationObject, AuthenticatorData from fido2 import cbor from flask import Flask, session, request, redirect, abort import os app = Flask(__name__, static_url_path="") app.secret_key = os.urandom(32) # Used for session. rp = PublicKeyCredentialRpEntity("localhost", "Demo server") server = Fido2Server(rp) # Registered credentials are stored globally, in memory only. Single user # support, state is lost when the server terminates. credentials = [] @app.route("/") def index(): return redirect("/index.html") @app.route("/api/register/begin", methods=["POST"]) def register_begin(): registration_data, state = server.register_begin( { "id": b"user_id", "name": "a_user", "displayName": "A. User", "icon": "https://example.com/image.png", }, credentials, user_verification="discouraged", authenticator_attachment="cross-platform", ) session["state"] = state print("\n\n\n\n") print(registration_data) print("\n\n\n\n") return cbor.encode(registration_data) @app.route("/api/register/complete", methods=["POST"]) def register_complete(): data = cbor.decode(request.get_data()) client_data = ClientData(data["clientDataJSON"]) att_obj = AttestationObject(data["attestationObject"]) print("clientData", client_data) print("AttestationObject:", att_obj) auth_data = server.register_complete(session["state"], client_data, att_obj) credentials.append(auth_data.credential_data) print("REGISTERED CREDENTIAL:", auth_data.credential_data) return cbor.encode({"status": "OK"}) @app.route("/api/authenticate/begin", methods=["POST"]) def authenticate_begin(): if not credentials: abort(404) auth_data, state = server.authenticate_begin(credentials) session["state"] = state return cbor.encode(auth_data) @app.route("/api/authenticate/complete", methods=["POST"]) def authenticate_complete(): if not credentials: abort(404) data = cbor.decode(request.get_data()) credential_id = data["credentialId"] client_data = ClientData(data["clientDataJSON"]) auth_data = AuthenticatorData(data["authenticatorData"]) signature = data["signature"] print("clientData", client_data) print("AuthenticatorData", auth_data) server.authenticate_complete( session.pop("state"), credentials, credential_id, client_data, auth_data, signature, ) print("ASSERTION OK") return cbor.encode({"status": "OK"}) if __name__ == "__main__": print(__doc__) app.run(ssl_context="adhoc", debug=True) fido2-0.8.1/examples/server/static/0000755000175000017500000000000013566742204017017 5ustar daindain00000000000000fido2-0.8.1/examples/server/static/authenticate.html0000644000175000017500000000326213406733061022360 0ustar daindain00000000000000 Fido 2.0 webauthn demo

WebAuthn demo using python-fido2

This demo requires a browser supporting the WebAuthn API!


Authenticate using a credential

Touch your authenticator device now...

Cancel fido2-0.8.1/examples/server/static/cbor.js0000644000175000017500000002761713317145510020306 0ustar daindain00000000000000/* * The MIT License (MIT) * * Copyright (c) 2014-2016 Patrick Gansterer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ (function(global, undefined) { "use strict"; var POW_2_24 = 5.960464477539063e-8, POW_2_32 = 4294967296, POW_2_53 = 9007199254740992; function encode(value) { var data = new ArrayBuffer(256); var dataView = new DataView(data); var lastLength; var offset = 0; function prepareWrite(length) { var newByteLength = data.byteLength; var requiredLength = offset + length; while (newByteLength < requiredLength) newByteLength <<= 1; if (newByteLength !== data.byteLength) { var oldDataView = dataView; data = new ArrayBuffer(newByteLength); dataView = new DataView(data); var uint32count = (offset + 3) >> 2; for (var i = 0; i < uint32count; ++i) dataView.setUint32(i << 2, oldDataView.getUint32(i << 2)); } lastLength = length; return dataView; } function commitWrite() { offset += lastLength; } function writeFloat64(value) { commitWrite(prepareWrite(8).setFloat64(offset, value)); } function writeUint8(value) { commitWrite(prepareWrite(1).setUint8(offset, value)); } function writeUint8Array(value) { var dataView = prepareWrite(value.length); for (var i = 0; i < value.length; ++i) dataView.setUint8(offset + i, value[i]); commitWrite(); } function writeUint16(value) { commitWrite(prepareWrite(2).setUint16(offset, value)); } function writeUint32(value) { commitWrite(prepareWrite(4).setUint32(offset, value)); } function writeUint64(value) { var low = value % POW_2_32; var high = (value - low) / POW_2_32; var dataView = prepareWrite(8); dataView.setUint32(offset, high); dataView.setUint32(offset + 4, low); commitWrite(); } function writeTypeAndLength(type, length) { if (length < 24) { writeUint8(type << 5 | length); } else if (length < 0x100) { writeUint8(type << 5 | 24); writeUint8(length); } else if (length < 0x10000) { writeUint8(type << 5 | 25); writeUint16(length); } else if (length < 0x100000000) { writeUint8(type << 5 | 26); writeUint32(length); } else { writeUint8(type << 5 | 27); writeUint64(length); } } function encodeItem(value) { var i; if (value === false) return writeUint8(0xf4); if (value === true) return writeUint8(0xf5); if (value === null) return writeUint8(0xf6); if (value === undefined) return writeUint8(0xf7); switch (typeof value) { case "number": if (Math.floor(value) === value) { if (0 <= value && value <= POW_2_53) return writeTypeAndLength(0, value); if (-POW_2_53 <= value && value < 0) return writeTypeAndLength(1, -(value + 1)); } writeUint8(0xfb); return writeFloat64(value); case "string": var utf8data = []; for (i = 0; i < value.length; ++i) { var charCode = value.charCodeAt(i); if (charCode < 0x80) { utf8data.push(charCode); } else if (charCode < 0x800) { utf8data.push(0xc0 | charCode >> 6); utf8data.push(0x80 | charCode & 0x3f); } else if (charCode < 0xd800) { utf8data.push(0xe0 | charCode >> 12); utf8data.push(0x80 | (charCode >> 6) & 0x3f); utf8data.push(0x80 | charCode & 0x3f); } else { charCode = (charCode & 0x3ff) << 10; charCode |= value.charCodeAt(++i) & 0x3ff; charCode += 0x10000; utf8data.push(0xf0 | charCode >> 18); utf8data.push(0x80 | (charCode >> 12) & 0x3f); utf8data.push(0x80 | (charCode >> 6) & 0x3f); utf8data.push(0x80 | charCode & 0x3f); } } writeTypeAndLength(3, utf8data.length); return writeUint8Array(utf8data); default: var length; if (Array.isArray(value)) { length = value.length; writeTypeAndLength(4, length); for (i = 0; i < length; ++i) encodeItem(value[i]); } else if (value instanceof Uint8Array) { writeTypeAndLength(2, value.length); writeUint8Array(value); } else { var keys = Object.keys(value); length = keys.length; writeTypeAndLength(5, length); for (i = 0; i < length; ++i) { var key = keys[i]; encodeItem(key); encodeItem(value[key]); } } } } encodeItem(value); if ("slice" in data) return data.slice(0, offset); var ret = new ArrayBuffer(offset); var retView = new DataView(ret); for (var i = 0; i < offset; ++i) retView.setUint8(i, dataView.getUint8(i)); return ret; } function decode(data, tagger, simpleValue) { var dataView = new DataView(data); var offset = 0; if (typeof tagger !== "function") tagger = function(value) { return value; }; if (typeof simpleValue !== "function") simpleValue = function() { return undefined; }; function commitRead(length, value) { offset += length; return value; } function readArrayBuffer(length) { return commitRead(length, new Uint8Array(data, offset, length)); } function readFloat16() { var tempArrayBuffer = new ArrayBuffer(4); var tempDataView = new DataView(tempArrayBuffer); var value = readUint16(); var sign = value & 0x8000; var exponent = value & 0x7c00; var fraction = value & 0x03ff; if (exponent === 0x7c00) exponent = 0xff << 10; else if (exponent !== 0) exponent += (127 - 15) << 10; else if (fraction !== 0) return (sign ? -1 : 1) * fraction * POW_2_24; tempDataView.setUint32(0, sign << 16 | exponent << 13 | fraction << 13); return tempDataView.getFloat32(0); } function readFloat32() { return commitRead(4, dataView.getFloat32(offset)); } function readFloat64() { return commitRead(8, dataView.getFloat64(offset)); } function readUint8() { return commitRead(1, dataView.getUint8(offset)); } function readUint16() { return commitRead(2, dataView.getUint16(offset)); } function readUint32() { return commitRead(4, dataView.getUint32(offset)); } function readUint64() { return readUint32() * POW_2_32 + readUint32(); } function readBreak() { if (dataView.getUint8(offset) !== 0xff) return false; offset += 1; return true; } function readLength(additionalInformation) { if (additionalInformation < 24) return additionalInformation; if (additionalInformation === 24) return readUint8(); if (additionalInformation === 25) return readUint16(); if (additionalInformation === 26) return readUint32(); if (additionalInformation === 27) return readUint64(); if (additionalInformation === 31) return -1; throw "Invalid length encoding"; } function readIndefiniteStringLength(majorType) { var initialByte = readUint8(); if (initialByte === 0xff) return -1; var length = readLength(initialByte & 0x1f); if (length < 0 || (initialByte >> 5) !== majorType) throw "Invalid indefinite length element"; return length; } function appendUtf16Data(utf16data, length) { for (var i = 0; i < length; ++i) { var value = readUint8(); if (value & 0x80) { if (value < 0xe0) { value = (value & 0x1f) << 6 | (readUint8() & 0x3f); length -= 1; } else if (value < 0xf0) { value = (value & 0x0f) << 12 | (readUint8() & 0x3f) << 6 | (readUint8() & 0x3f); length -= 2; } else { value = (value & 0x0f) << 18 | (readUint8() & 0x3f) << 12 | (readUint8() & 0x3f) << 6 | (readUint8() & 0x3f); length -= 3; } } if (value < 0x10000) { utf16data.push(value); } else { value -= 0x10000; utf16data.push(0xd800 | (value >> 10)); utf16data.push(0xdc00 | (value & 0x3ff)); } } } function decodeItem() { var initialByte = readUint8(); var majorType = initialByte >> 5; var additionalInformation = initialByte & 0x1f; var i; var length; if (majorType === 7) { switch (additionalInformation) { case 25: return readFloat16(); case 26: return readFloat32(); case 27: return readFloat64(); } } length = readLength(additionalInformation); if (length < 0 && (majorType < 2 || 6 < majorType)) throw "Invalid length"; switch (majorType) { case 0: return length; case 1: return -1 - length; case 2: if (length < 0) { var elements = []; var fullArrayLength = 0; while ((length = readIndefiniteStringLength(majorType)) >= 0) { fullArrayLength += length; elements.push(readArrayBuffer(length)); } var fullArray = new Uint8Array(fullArrayLength); var fullArrayOffset = 0; for (i = 0; i < elements.length; ++i) { fullArray.set(elements[i], fullArrayOffset); fullArrayOffset += elements[i].length; } return fullArray; } return readArrayBuffer(length); case 3: var utf16data = []; if (length < 0) { while ((length = readIndefiniteStringLength(majorType)) >= 0) appendUtf16Data(utf16data, length); } else appendUtf16Data(utf16data, length); return String.fromCharCode.apply(null, utf16data); case 4: var retArray; if (length < 0) { retArray = []; while (!readBreak()) retArray.push(decodeItem()); } else { retArray = new Array(length); for (i = 0; i < length; ++i) retArray[i] = decodeItem(); } return retArray; case 5: var retObject = {}; for (i = 0; i < length || length < 0 && !readBreak(); ++i) { var key = decodeItem(); retObject[key] = decodeItem(); } return retObject; case 6: return tagger(decodeItem(), length); case 7: switch (length) { case 20: return false; case 21: return true; case 22: return null; case 23: return undefined; default: return simpleValue(length); } } } var ret = decodeItem(); if (offset !== data.byteLength) throw "Remaining bytes"; return ret; } var obj = { encode: encode, decode: decode }; if (typeof define === "function" && define.amd) define("cbor/cbor", obj); else if (typeof module !== "undefined" && module.exports) module.exports = obj; else if (!global.CBOR) global.CBOR = obj; })(this); fido2-0.8.1/examples/server/static/index-u2f.html0000644000175000017500000000140113406733061021474 0ustar daindain00000000000000 Fido 2.0 webauthn demo

WebAuthn demo using python-fido2

This demo requires a browser supporting the WebAuthn API!


Available actions

Register
Authenticate

To allow the testing of authenticating with legacy U2F credentials, you can also register a U2F credential: Register U2F

fido2-0.8.1/examples/server/static/index.html0000644000175000017500000000113413406701512021001 0ustar daindain00000000000000 Fido 2.0 webauthn demo

WebAuthn demo using python-fido2

This demo requires a browser supporting the WebAuthn API!


Available actions

Register
Authenticate
fido2-0.8.1/examples/server/static/register.html0000644000175000017500000000304113406733061021521 0ustar daindain00000000000000 Fido 2.0 webauthn demo

WebAuthn demo using python-fido2

This demo requires a browser supporting the WebAuthn API!


Register a credential

Touch your authenticator device now...

Cancel fido2-0.8.1/examples/server/static/u2f-api.js0000644000175000017500000005062013406733061020615 0ustar daindain00000000000000//Copyright 2014-2015 Google Inc. All rights reserved. //Use of this source code is governed by a BSD-style //license that can be found in the LICENSE file or at //https://developers.google.com/open-source/licenses/bsd /** * @fileoverview The U2F api. */ 'use strict'; /** * Namespace for the U2F api. * @type {Object} */ var u2f = u2f || {}; /** * FIDO U2F Javascript API Version * @number */ var js_api_version; /** * The U2F extension id * @const {string} */ // The Chrome packaged app extension ID. // Uncomment this if you want to deploy a server instance that uses // the package Chrome app and does not require installing the U2F Chrome extension. u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; // The U2F Chrome extension ID. // Uncomment this if you want to deploy a server instance that uses // the U2F Chrome extension to authenticate. // u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; /** * Message types for messsages to/from the extension * @const * @enum {string} */ u2f.MessageTypes = { 'U2F_REGISTER_REQUEST': 'u2f_register_request', 'U2F_REGISTER_RESPONSE': 'u2f_register_response', 'U2F_SIGN_REQUEST': 'u2f_sign_request', 'U2F_SIGN_RESPONSE': 'u2f_sign_response', 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request', 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response' }; /** * Response status codes * @const * @enum {number} */ u2f.ErrorCodes = { 'OK': 0, 'OTHER_ERROR': 1, 'BAD_REQUEST': 2, 'CONFIGURATION_UNSUPPORTED': 3, 'DEVICE_INELIGIBLE': 4, 'TIMEOUT': 5 }; /** * A message for registration requests * @typedef {{ * type: u2f.MessageTypes, * appId: ?string, * timeoutSeconds: ?number, * requestId: ?number * }} */ u2f.U2fRequest; /** * A message for registration responses * @typedef {{ * type: u2f.MessageTypes, * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), * requestId: ?number * }} */ u2f.U2fResponse; /** * An error object for responses * @typedef {{ * errorCode: u2f.ErrorCodes, * errorMessage: ?string * }} */ u2f.Error; /** * Data object for a single sign request. * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}} */ u2f.Transport; /** * Data object for a single sign request. * @typedef {Array} */ u2f.Transports; /** * Data object for a single sign request. * @typedef {{ * version: string, * challenge: string, * keyHandle: string, * appId: string * }} */ u2f.SignRequest; /** * Data object for a sign response. * @typedef {{ * keyHandle: string, * signatureData: string, * clientData: string * }} */ u2f.SignResponse; /** * Data object for a registration request. * @typedef {{ * version: string, * challenge: string * }} */ u2f.RegisterRequest; /** * Data object for a registration response. * @typedef {{ * version: string, * keyHandle: string, * transports: Transports, * appId: string * }} */ u2f.RegisterResponse; /** * Data object for a registered key. * @typedef {{ * version: string, * keyHandle: string, * transports: ?Transports, * appId: ?string * }} */ u2f.RegisteredKey; /** * Data object for a get API register response. * @typedef {{ * js_api_version: number * }} */ u2f.GetJsApiVersionResponse; //Low level MessagePort API support /** * Sets up a MessagePort to the U2F extension using the * available mechanisms. * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback */ u2f.getMessagePort = function(callback) { if (typeof chrome != 'undefined' && chrome.runtime) { // The actual message here does not matter, but we need to get a reply // for the callback to run. Thus, send an empty signature request // in order to get a failure response. var msg = { type: u2f.MessageTypes.U2F_SIGN_REQUEST, signRequests: [] }; chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { if (!chrome.runtime.lastError) { // We are on a whitelisted origin and can talk directly // with the extension. u2f.getChromeRuntimePort_(callback); } else { // chrome.runtime was available, but we couldn't message // the extension directly, use iframe u2f.getIframePort_(callback); } }); } else if (u2f.isAndroidChrome_()) { u2f.getAuthenticatorPort_(callback); } else if (u2f.isIosChrome_()) { u2f.getIosPort_(callback); } else { // chrome.runtime was not available at all, which is normal // when this origin doesn't have access to any extensions. u2f.getIframePort_(callback); } }; /** * Detect chrome running on android based on the browser's useragent. * @private */ u2f.isAndroidChrome_ = function() { var userAgent = navigator.userAgent; return userAgent.indexOf('Chrome') != -1 && userAgent.indexOf('Android') != -1; }; /** * Detect chrome running on iOS based on the browser's platform. * @private */ u2f.isIosChrome_ = function() { return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1; }; /** * Connects directly to the extension via chrome.runtime.connect. * @param {function(u2f.WrappedChromeRuntimePort_)} callback * @private */ u2f.getChromeRuntimePort_ = function(callback) { var port = chrome.runtime.connect(u2f.EXTENSION_ID, {'includeTlsChannelId': true}); setTimeout(function() { callback(new u2f.WrappedChromeRuntimePort_(port)); }, 0); }; /** * Return a 'port' abstraction to the Authenticator app. * @param {function(u2f.WrappedAuthenticatorPort_)} callback * @private */ u2f.getAuthenticatorPort_ = function(callback) { setTimeout(function() { callback(new u2f.WrappedAuthenticatorPort_()); }, 0); }; /** * Return a 'port' abstraction to the iOS client app. * @param {function(u2f.WrappedIosPort_)} callback * @private */ u2f.getIosPort_ = function(callback) { setTimeout(function() { callback(new u2f.WrappedIosPort_()); }, 0); }; /** * A wrapper for chrome.runtime.Port that is compatible with MessagePort. * @param {Port} port * @constructor * @private */ u2f.WrappedChromeRuntimePort_ = function(port) { this.port_ = port; }; /** * Format and return a sign request compliant with the JS API version supported by the extension. * @param {Array} signRequests * @param {number} timeoutSeconds * @param {number} reqId * @return {Object} */ u2f.formatSignRequest_ = function(appId, challenge, registeredKeys, timeoutSeconds, reqId) { if (js_api_version === undefined || js_api_version < 1.1) { // Adapt request to the 1.0 JS API var signRequests = []; for (var i = 0; i < registeredKeys.length; i++) { signRequests[i] = { version: registeredKeys[i].version, challenge: challenge, keyHandle: registeredKeys[i].keyHandle, appId: appId }; } return { type: u2f.MessageTypes.U2F_SIGN_REQUEST, signRequests: signRequests, timeoutSeconds: timeoutSeconds, requestId: reqId }; } // JS 1.1 API return { type: u2f.MessageTypes.U2F_SIGN_REQUEST, appId: appId, challenge: challenge, registeredKeys: registeredKeys, timeoutSeconds: timeoutSeconds, requestId: reqId }; }; /** * Format and return a register request compliant with the JS API version supported by the extension.. * @param {Array} signRequests * @param {Array} signRequests * @param {number} timeoutSeconds * @param {number} reqId * @return {Object} */ u2f.formatRegisterRequest_ = function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) { if (js_api_version === undefined || js_api_version < 1.1) { // Adapt request to the 1.0 JS API for (var i = 0; i < registerRequests.length; i++) { registerRequests[i].appId = appId; } var signRequests = []; for (var i = 0; i < registeredKeys.length; i++) { signRequests[i] = { version: registeredKeys[i].version, challenge: registerRequests[0], keyHandle: registeredKeys[i].keyHandle, appId: appId }; } return { type: u2f.MessageTypes.U2F_REGISTER_REQUEST, signRequests: signRequests, registerRequests: registerRequests, timeoutSeconds: timeoutSeconds, requestId: reqId }; } // JS 1.1 API return { type: u2f.MessageTypes.U2F_REGISTER_REQUEST, appId: appId, registerRequests: registerRequests, registeredKeys: registeredKeys, timeoutSeconds: timeoutSeconds, requestId: reqId }; }; /** * Posts a message on the underlying channel. * @param {Object} message */ u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { this.port_.postMessage(message); }; /** * Emulates the HTML 5 addEventListener interface. Works only for the * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. * @param {string} eventName * @param {function({data: Object})} handler */ u2f.WrappedChromeRuntimePort_.prototype.addEventListener = function(eventName, handler) { var name = eventName.toLowerCase(); if (name == 'message' || name == 'onmessage') { this.port_.onMessage.addListener(function(message) { // Emulate a minimal MessageEvent object handler({'data': message}); }); } else { console.error('WrappedChromeRuntimePort only supports onMessage'); } }; /** * Wrap the Authenticator app with a MessagePort interface. * @constructor * @private */ u2f.WrappedAuthenticatorPort_ = function() { this.requestId_ = -1; this.requestObject_ = null; } /** * Launch the Authenticator intent. * @param {Object} message */ u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { var intentUrl = u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + ';S.request=' + encodeURIComponent(JSON.stringify(message)) + ';end'; document.location = intentUrl; }; /** * Tells what type of port this is. * @return {String} port type */ u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() { return "WrappedAuthenticatorPort_"; }; /** * Emulates the HTML 5 addEventListener interface. * @param {string} eventName * @param {function({data: Object})} handler */ u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) { var name = eventName.toLowerCase(); if (name == 'message') { var self = this; /* Register a callback to that executes when * chrome injects the response. */ window.addEventListener( 'message', self.onRequestUpdate_.bind(self, handler), false); } else { console.error('WrappedAuthenticatorPort only supports message'); } }; /** * Callback invoked when a response is received from the Authenticator. * @param function({data: Object}) callback * @param {Object} message message Object */ u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = function(callback, message) { var messageObject = JSON.parse(message.data); var intentUrl = messageObject['intentURL']; var errorCode = messageObject['errorCode']; var responseObject = null; if (messageObject.hasOwnProperty('data')) { responseObject = /** @type {Object} */ ( JSON.parse(messageObject['data'])); } callback({'data': responseObject}); }; /** * Base URL for intents to Authenticator. * @const * @private */ u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; /** * Wrap the iOS client app with a MessagePort interface. * @constructor * @private */ u2f.WrappedIosPort_ = function() {}; /** * Launch the iOS client app request * @param {Object} message */ u2f.WrappedIosPort_.prototype.postMessage = function(message) { var str = JSON.stringify(message); var url = "u2f://auth?" + encodeURI(str); location.replace(url); }; /** * Tells what type of port this is. * @return {String} port type */ u2f.WrappedIosPort_.prototype.getPortType = function() { return "WrappedIosPort_"; }; /** * Emulates the HTML 5 addEventListener interface. * @param {string} eventName * @param {function({data: Object})} handler */ u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) { var name = eventName.toLowerCase(); if (name !== 'message') { console.error('WrappedIosPort only supports message'); } }; /** * Sets up an embedded trampoline iframe, sourced from the extension. * @param {function(MessagePort)} callback * @private */ u2f.getIframePort_ = function(callback) { // Create the iframe var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; var iframe = document.createElement('iframe'); iframe.src = iframeOrigin + '/u2f-comms.html'; iframe.setAttribute('style', 'display:none'); document.body.appendChild(iframe); var channel = new MessageChannel(); var ready = function(message) { if (message.data == 'ready') { channel.port1.removeEventListener('message', ready); callback(channel.port1); } else { console.error('First event on iframe port was not "ready"'); } }; channel.port1.addEventListener('message', ready); channel.port1.start(); iframe.addEventListener('load', function() { // Deliver the port to the iframe and initialize iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); }); }; //High-level JS API /** * Default extension response timeout in seconds. * @const */ u2f.EXTENSION_TIMEOUT_SEC = 30; /** * A singleton instance for a MessagePort to the extension. * @type {MessagePort|u2f.WrappedChromeRuntimePort_} * @private */ u2f.port_ = null; /** * Callbacks waiting for a port * @type {Array} * @private */ u2f.waitingForPort_ = []; /** * A counter for requestIds. * @type {number} * @private */ u2f.reqCounter_ = 0; /** * A map from requestIds to client callbacks * @type {Object.} * @private */ u2f.callbackMap_ = {}; /** * Creates or retrieves the MessagePort singleton to use. * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback * @private */ u2f.getPortSingleton_ = function(callback) { if (u2f.port_) { callback(u2f.port_); } else { if (u2f.waitingForPort_.length == 0) { u2f.getMessagePort(function(port) { u2f.port_ = port; u2f.port_.addEventListener('message', /** @type {function(Event)} */ (u2f.responseHandler_)); // Careful, here be async callbacks. Maybe. while (u2f.waitingForPort_.length) u2f.waitingForPort_.shift()(u2f.port_); }); } u2f.waitingForPort_.push(callback); } }; /** * Handles response messages from the extension. * @param {MessageEvent.} message * @private */ u2f.responseHandler_ = function(message) { var response = message.data; var reqId = response['requestId']; if (!reqId || !u2f.callbackMap_[reqId]) { console.error('Unknown or missing requestId in response.'); return; } var cb = u2f.callbackMap_[reqId]; delete u2f.callbackMap_[reqId]; cb(response['responseData']); }; /** * Dispatches an array of sign requests to available U2F tokens. * If the JS API version supported by the extension is unknown, it first sends a * message to the extension to find out the supported API version and then it sends * the sign request. * @param {string=} appId * @param {string=} challenge * @param {Array} registeredKeys * @param {function((u2f.Error|u2f.SignResponse))} callback * @param {number=} opt_timeoutSeconds */ u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { if (js_api_version === undefined) { // Send a message to get the extension to JS API version, then send the actual sign request. u2f.getApiVersion( function (response) { js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version']; console.log("Extension JS API Version: ", js_api_version); u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); }); } else { // We know the JS API version. Send the actual sign request in the supported API version. u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); } }; /** * Dispatches an array of sign requests to available U2F tokens. * @param {string=} appId * @param {string=} challenge * @param {Array} registeredKeys * @param {function((u2f.Error|u2f.SignResponse))} callback * @param {number=} opt_timeoutSeconds */ u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { u2f.getPortSingleton_(function(port) { var reqId = ++u2f.reqCounter_; u2f.callbackMap_[reqId] = callback; var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId); port.postMessage(req); }); }; /** * Dispatches register requests to available U2F tokens. An array of sign * requests identifies already registered tokens. * If the JS API version supported by the extension is unknown, it first sends a * message to the extension to find out the supported API version and then it sends * the register request. * @param {string=} appId * @param {Array} registerRequests * @param {Array} registeredKeys * @param {function((u2f.Error|u2f.RegisterResponse))} callback * @param {number=} opt_timeoutSeconds */ u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { if (js_api_version === undefined) { // Send a message to get the extension to JS API version, then send the actual register request. u2f.getApiVersion( function (response) { js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version']; console.log("Extension JS API Version: ", js_api_version); u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds); }); } else { // We know the JS API version. Send the actual register request in the supported API version. u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds); } }; /** * Dispatches register requests to available U2F tokens. An array of sign * requests identifies already registered tokens. * @param {string=} appId * @param {Array} registerRequests * @param {Array} registeredKeys * @param {function((u2f.Error|u2f.RegisterResponse))} callback * @param {number=} opt_timeoutSeconds */ u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { u2f.getPortSingleton_(function(port) { var reqId = ++u2f.reqCounter_; u2f.callbackMap_[reqId] = callback; var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); var req = u2f.formatRegisterRequest_( appId, registeredKeys, registerRequests, timeoutSeconds, reqId); port.postMessage(req); }); }; /** * Dispatches a message to the extension to find out the supported * JS API version. * If the user is on a mobile phone and is thus using Google Authenticator instead * of the Chrome extension, don't send the request and simply return 0. * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback * @param {number=} opt_timeoutSeconds */ u2f.getApiVersion = function(callback, opt_timeoutSeconds) { u2f.getPortSingleton_(function(port) { // If we are using Android Google Authenticator or iOS client app, // do not fire an intent to ask which JS API version to use. if (port.getPortType) { var apiVersion; switch (port.getPortType()) { case 'WrappedIosPort_': case 'WrappedAuthenticatorPort_': apiVersion = 1.1; break; default: apiVersion = 0; break; } callback({ 'js_api_version': apiVersion }); return; } var reqId = ++u2f.reqCounter_; u2f.callbackMap_[reqId] = callback; var req = { type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST, timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), requestId: reqId }; port.postMessage(req); }); }; fido2-0.8.1/examples/server/static/u2f.html0000644000175000017500000000332513406733061020376 0ustar daindain00000000000000 Fido 2.0 webauthn demo

WebAuthn demo using python-fido2

This demo requires a browser supporting the WebAuthn API!


Register a U2F credential

Touch your authenticator device now...

Cancel fido2-0.8.1/examples/u2f_nfc.py0000644000175000017500000000174213544577440016126 0ustar daindain00000000000000from fido2.pcsc import CtapPcscDevice from fido2.utils import sha256 from fido2.ctap1 import CTAP1 import sys dev = next(CtapPcscDevice.list_devices(), None) if not dev: print("No NFC u2f device found") sys.exit(1) chal = sha256(b"AAA") appid = sha256(b"BBB") ctap1 = CTAP1(dev) print("version:", ctap1.get_version()) # True - make extended APDU and send it to key # ISO 7816-3:2006. page 33, 12.1.3 Decoding conventions for command APDUs # ISO 7816-3:2006. page 34, 12.2 Command-response pair transmission by T=0 # False - make group of short (less than 255 bytes length) APDU # and send them to key. ISO 7816-3:2005, page 9, 5.1.1.1 Command chaining dev.use_ext_apdu = False reg = ctap1.register(chal, appid) print("register:", reg) reg.verify(appid, chal) print("Register message verify OK") auth = ctap1.authenticate(chal, appid, reg.key_handle) print("authenticate result: ", auth) res = auth.verify(appid, chal, reg.public_key) print("Authenticate message verify OK") fido2-0.8.1/fido2/0000755000175000017500000000000013566742204013407 5ustar daindain00000000000000fido2-0.8.1/fido2/attestation.py0000644000175000017500000003536313554254405016330 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals from .cose import CoseKey, ES256 from ._tpm import TpmAttestationFormat, TpmPublicFormat from .utils import sha256, websafe_decode from binascii import a2b_hex from cryptography import x509 from cryptography.exceptions import InvalidSignature as _InvalidSignature from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import padding, ec, rsa from cryptography.hazmat.primitives.constant_time import bytes_eq from cryptography.hazmat.primitives import hashes import abc import json class InvalidAttestation(Exception): pass class InvalidData(InvalidAttestation): pass class InvalidSignature(InvalidAttestation): pass class UnsupportedType(InvalidAttestation): def __init__(self, auth_data, fmt=None): super(UnsupportedType, self).__init__( 'Attestation format "{}" is not supported'.format(fmt) if fmt else "This attestation format is not supported!" ) self.auth_data = auth_data self.fmt = fmt class Attestation(abc.ABC): @abc.abstractmethod def verify(self, statement, auth_data, client_data_hash): pass @staticmethod def for_type(fmt): for cls in Attestation.__subclasses__(): if getattr(cls, "FORMAT", None) == fmt: return cls class TypedUnsupportedAttestation(UnsupportedAttestation): def __init__(self): super(TypedUnsupportedAttestation, self).__init__(fmt) return TypedUnsupportedAttestation class UnsupportedAttestation(Attestation): def __init__(self, fmt=None): self.fmt = fmt def verify(self, statement, auth_data, client_data_hash): raise UnsupportedType(auth_data, self.fmt) class NoneAttestation(Attestation): FORMAT = "none" def verify(self, statement, auth_data, client_data_hash): if statement != {}: raise InvalidData("None Attestation requires empty statement.") class FidoU2FAttestation(Attestation): FORMAT = "fido-u2f" 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] FidoU2FAttestation.verify_signature( auth_data.rp_id_hash, client_data_hash, cd.credential_id, pk, statement["x5c"][0], statement["sig"], ) @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() # GS Root R2 (https://pki.goog/) _GSR2_DER = a2b_hex( b"308203ba308202a2a003020102020b0400000000010f8626e60d300d06092a864886f70d0101050500304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d20523231133011060355040a130a476c6f62616c5369676e311330110603550403130a476c6f62616c5369676e301e170d3036313231353038303030305a170d3231313231353038303030305a304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d20523231133011060355040a130a476c6f62616c5369676e311330110603550403130a476c6f62616c5369676e30820122300d06092a864886f70d01010105000382010f003082010a0282010100a6cf240ebe2e6f28994542c4ab3e21549b0bd37f8470fa12b3cbbf875fc67f86d3b2305cd6fdadf17bdce5f86096099210f5d053defb7b7e7388ac52887b4aa6ca49a65ea8a78c5a11bc7a82ebbe8ce9b3ac962507974a992a072fb41e77bf8a0fb5027c1b96b8c5b93a2cbcd612b9eb597de2d006865f5e496ab5395e8834ecbc780c0898846ca8cd4bb4a07d0c794df0b82dcb21cad56c5b7de1a02984a1f9d39449cb24629120bcdd0bd5d9ccf9ea270a2b7391c69d1bacc8cbe8e0a0f42f908b4dfbb0361bf6197a85e06df26113885c9fe0930a51978a5aceafabd5f7aa09aa60bddcd95fdf72a960135e0001c94afa3fa4ea070321028e82ca03c29b8f0203010001a3819c308199300e0603551d0f0101ff040403020106300f0603551d130101ff040530030101ff301d0603551d0e041604149be20757671c1ec06a06de59b49a2ddfdc19862e30360603551d1f042f302d302ba029a0278625687474703a2f2f63726c2e676c6f62616c7369676e2e6e65742f726f6f742d72322e63726c301f0603551d230418301680149be20757671c1ec06a06de59b49a2ddfdc19862e300d06092a864886f70d01010505000382010100998153871c68978691ece04ab8440bab81ac274fd6c1b81c4378b30c9afcea2c3c6e611b4d4b29f59f051d26c1b8e983006245b6a90893b9a9334b189ac2f887884edbdd71341ac154da463fe0d32aab6d5422f53a62cd206fba2989d7dd91eed35ca23ea15b41f5dfe564432de9d539abd2a2dfb78bd0c080191c45c02d8ce8f82da4745649c505b54f15de6e44783987a87ebbf3791891bbf46f9dc1f08c358c5d01fbc36db9ef446d7946317e0afea982c1ffefab6e20c450c95f9d4d9b178c0ce501c9a0416a7353faa550b46e250ffb4c18f4fd52d98e69b1e8110fde88d8fb1d49f7aade95cf2078c26012db25408c6afc7e4238406412f79e81e1932e" # noqa E501 ) class AndroidSafetynetAttestation(Attestation): FORMAT = "android-safetynet" def __init__(self, allow_rooted=False, ca=_GSR2_DER): self.allow_rooted = allow_rooted self._ca = x509.load_der_x509_certificate(ca, default_backend()) 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")) certs = [ x509.load_der_x509_certificate(websafe_decode(x), default_backend()) for x in data["x5c"] ] certs.append(self._ca) cert = certs.pop(0) 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 ) while certs: child = cert cert = certs.pop(0) pub = cert.public_key() 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), ) OID_AAGUID = x509.ObjectIdentifier("1.3.6.1.4.1.45724.1.1.4") def _validate_cert_common(cert): if cert.version != x509.Version.v3: raise InvalidData("Attestation certificate must use version 3!") bc = cert.extensions.get_extension_for_class(x509.BasicConstraints) if bc.value.ca: raise InvalidData("Attestation certificate must have CA=false!") 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" 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()) else: pub_key = CoseKey.parse(auth_data.credential_data.public_key) if pub_key.ALGORITHM != alg: raise InvalidData("Wrong algorithm of public key!") try: pub_key.verify(auth_data + client_data_hash, statement["sig"]) except _InvalidSignature: raise InvalidSignature() OID_AIK_CERTIFICATE = x509.ObjectIdentifier("2.23.133.8.3") 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" 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") cert_info = statement["certInfo"] if x5c: 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()) else: pub_key = CoseKey.parse(auth_data.credential_data.public_key) if pub_key.ALGORITHM != alg: raise InvalidData("Wrong algorithm of 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 digest = hashes.Hash(pub_key._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"]) except _InvalidSignature: raise InvalidSignature("signature of certInfo does not match") fido2-0.8.1/fido2/cbor.py0000644000175000017500000001060713544577440014716 0ustar daindain00000000000000# 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. """ import struct import six def dump_int(data, mt=0): if data < 0: mt = 1 data = -1 - data mt = mt << 5 if data <= 23: args = (">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): return b"\xf5" if data else b"\xf4" def dump_list(data): return dump_int(len(data), mt=4) + b"".join([encode(x) for x in data]) def _sort_keys(entry): key = entry[0] return six.indexbytes(key, 0), len(key), key def dump_dict(data): 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): return dump_int(len(data), mt=2) + data def dump_text(data): data_bytes = data.encode("utf8") return dump_int(len(data_bytes), mt=3) + data_bytes _SERIALIZERS = [ (bool, dump_bool), (six.integer_types, dump_int), (dict, dump_dict), (list, dump_list), (six.text_type, dump_text), (six.binary_type, dump_bytes), ] def encode(data): for k, v in _SERIALIZERS: if isinstance(data, k): return v(data) raise ValueError("Unsupported value: {}".format(data)) def load_int(ai, data): if ai < 24: return ai, data elif ai == 24: return six.indexbytes(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, data): val, rest = load_int(ai, data) return -1 - val, rest def load_bool(ai, data): return ai == 21, data def load_bytes(ai, data): l, data = load_int(ai, data) return data[:l], data[l:] def load_text(ai, data): enc, rest = load_bytes(ai, data) return enc.decode("utf8"), rest def load_array(ai, data): 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, data): 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): fb = six.indexbytes(data, 0) return _DESERIALIZERS[fb >> 5](fb & 0b11111, data[1:]) def decode(data): value, rest = decode_from(data) if rest != b"": raise ValueError("Extraneous data") return value fido2-0.8.1/fido2/client.py0000644000175000017500000006067613566741225015260 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals, division from .hid import STATUS from .ctap import CtapError from .ctap1 import CTAP1, APDU, ApduError from .ctap2 import CTAP2, PinProtocolV1, AttestationObject, AssertionResponse, Info from .webauthn import ( PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, AuthenticatorSelectionCriteria, UserVerificationRequirement, ) from .cose import ES256 from .rpid import verify_rp_id, verify_app_id from .utils import sha256, hmac_sha256, websafe_decode, websafe_encode from enum import Enum, IntEnum, unique from threading import Timer, Event import json import six import platform class ClientData(bytes): def __init__(self, _): super(ClientData, self).__init__() self.data = json.loads(self.decode()) def get(self, key): return self.data[key] @property def challenge(self): return websafe_decode(self.get("challenge")) @property def b64(self): return websafe_encode(self) @property def hash(self): return sha256(self) @classmethod def build(cls, **kwargs): return cls(json.dumps(kwargs).encode()) @classmethod def from_b64(cls, data): return cls(websafe_decode(data)) def __repr__(self): return self.decode() def __str__(self): return self.decode() 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 += ". Caused by {}".format(self.cause) return r def _ctap2client_err(e): 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.UNSUPPORTED_EXTENSION, 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.PIN_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 ce(e) 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() @unique class U2F_TYPE(six.text_type, Enum): REGISTER = "navigator.id.finishEnrollment" SIGN = "navigator.id.getAssertion" class U2fClient(object): """U2F-like client implementation. The client allows registration and authentication of U2F credentials against an Authenticator using CTAP 1. Prefer using Fido2Client if possible. :param device: CtapDevice to use. :param str origin: The origin to use. :param verify: Function to verify an APP ID for a given origin. """ def __init__(self, device, origin, verify=verify_app_id): self.poll_delay = 0.25 self.ctap = CTAP1(device) self.origin = origin self._verify = verify def _verify_app_id(self, app_id): try: if self._verify(app_id, self.origin): return except Exception: pass # Fall through to ClientError raise ClientError.ERR.BAD_REQUEST() def register( self, app_id, register_requests, registered_keys, event=None, on_keepalive=None ): self._verify_app_id(app_id) version = self.ctap.get_version() dummy_param = b"\0" * 32 for key in registered_keys: if key["version"] != version: continue key_app_id = key.get("appId", app_id) app_param = sha256(key_app_id.encode()) self._verify_app_id(key_app_id) key_handle = websafe_decode(key["keyHandle"]) try: self.ctap.authenticate(dummy_param, app_param, key_handle, True) raise ClientError.ERR.DEVICE_INELIGIBLE() # Bad response except ApduError as e: if e.code == APDU.USE_NOT_SATISFIED: raise ClientError.ERR.DEVICE_INELIGIBLE() except CtapError as e: raise _ctap2client_err(e) for request in register_requests: if request["version"] == version: challenge = request["challenge"] break else: raise ClientError.ERR.DEVICE_INELIGIBLE() client_data = ClientData.build( typ=U2F_TYPE.REGISTER, challenge=challenge, origin=self.origin ) app_param = sha256(app_id.encode()) reg_data = _call_polling( self.poll_delay, event, on_keepalive, self.ctap.register, client_data.hash, app_param, ) return {"registrationData": reg_data.b64, "clientData": client_data.b64} def sign(self, app_id, challenge, registered_keys, event=None, on_keepalive=None): client_data = ClientData.build( typ=U2F_TYPE.SIGN, challenge=challenge, origin=self.origin ) version = self.ctap.get_version() for key in registered_keys: if key["version"] == version: key_app_id = key.get("appId", app_id) self._verify_app_id(key_app_id) key_handle = websafe_decode(key["keyHandle"]) app_param = sha256(key_app_id.encode()) try: signature_data = _call_polling( self.poll_delay, event, on_keepalive, self.ctap.authenticate, client_data.hash, app_param, key_handle, ) break except ClientError: pass # Ignore and try next key else: raise ClientError.ERR.DEVICE_INELIGIBLE() return { "clientData": client_data.b64, "signatureData": signature_data.b64, "keyHandle": key["keyHandle"], } @unique class WEBAUTHN_TYPE(six.text_type, Enum): MAKE_CREDENTIAL = "webauthn.create" GET_ASSERTION = "webauthn.get" class _BaseClient(object): def __init__(self, origin, verify): self.origin = origin self._verify = verify def _verify_rp_id(self, rp_id): try: if self._verify(rp_id, self.origin): return except Exception: pass # Fall through to ClientError raise ClientError.ERR.BAD_REQUEST() def _build_client_data(self, typ, challenge, extensions={}): return ClientData.build( type=typ, origin=self.origin, challenge=websafe_encode(challenge), clientExtensions=extensions, ) _CTAP1_INFO = Info.create(["U2F_V2"]) class Fido2Client(_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, origin, verify=verify_rp_id): super(Fido2Client, self).__init__(origin, verify) self.ctap1_poll_delay = 0.25 try: self.ctap2 = CTAP2(device) self.info = self.ctap2.get_info() if PinProtocolV1.VERSION in self.info.pin_protocols: self.pin_protocol = PinProtocolV1(self.ctap2) else: self.pin_protocol = None self._do_make_credential = self._ctap2_make_credential self._do_get_assertion = self._ctap2_get_assertion except ValueError: self.ctap1 = CTAP1(device) self.info = _CTAP1_INFO self._do_make_credential = self._ctap1_make_credential self._do_get_assertion = self._ctap1_get_assertion def _get_ctap_uv(self, uv_requirement, pin_provided): pin_supported = "clientPin" in self.info.options pin_set = self.info.options.get("clientPin", False) if pin_provided: if not pin_set: raise ClientError.ERR.BAD_REQUEST("PIN provided, but not set/supported") else: return False # If PIN is provided, internal uv is not used uv_supported = "uv" in self.info.options uv_set = self.info.options.get("uv", False) if uv_requirement == UserVerificationRequirement.REQUIRED: if not uv_set: raise ClientError.ERR.CONFIGURATION_UNSUPPORTED( "User verification not configured/supported" ) return True elif uv_requirement == UserVerificationRequirement.PREFERRED: if not uv_set and (uv_supported or pin_supported): raise ClientError.ERR.CONFIGURATION_UNSUPPORTED( "User verification supported but not configured" ) return uv_set return False def make_credential(self, options, **kwargs): """Creates a credential. :param options: PublicKeyCredentialCreationOptions data. :param pin: (optional) Used if PIN verification is required. :param threading.Event event: (optional) Signal to abort the operation. :param on_keepalive: (optional) function to call with CTAP status updates. """ options = PublicKeyCredentialCreationOptions._wrap(options) pin = kwargs.get("pin") event = kwargs.get("event", Event()) if options.timeout: timer = Timer(options.timeout / 1000, event.set) timer.daemon = True timer.start() self._verify_rp_id(options.rp.id) client_data = self._build_client_data( WEBAUTHN_TYPE.MAKE_CREDENTIAL, options.challenge ) selection = options.authenticator_selection or AuthenticatorSelectionCriteria() try: return ( self._do_make_credential( client_data, options.rp, options.user, options.pub_key_cred_params, options.exclude_credentials, options.extensions, selection.require_resident_key, self._get_ctap_uv(selection.user_verification, pin is not None), pin, event, kwargs.get("on_keepalive"), ), client_data, ) except CtapError as e: raise _ctap2client_err(e) finally: if options.timeout: timer.cancel() def _ctap2_make_credential( self, client_data, rp, user, key_params, exclude_list, extensions, rk, uv, pin, event, on_keepalive, ): pin_auth = None pin_protocol = None if pin: pin_protocol = self.pin_protocol.VERSION pin_token = self.pin_protocol.get_pin_token(pin) pin_auth = hmac_sha256(pin_token, client_data.hash)[:16] elif self.info.options.get("clientPin") and not uv: raise ClientError.ERR.BAD_REQUEST("PIN required but not provided") if not (rk or uv): options = None else: options = {} if rk: options["rk"] = True if uv: options["uv"] = True 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") return self.ctap2.make_credential( client_data.hash, rp, user, key_params, exclude_list, extensions, options, pin_auth, pin_protocol, event, on_keepalive, ) def _ctap1_make_credential( self, client_data, rp, user, key_params, exclude_list, extensions, rk, uv, pin, event, on_keepalive, ): if rk or uv or ES256.ALGORITHM not in [p.alg for p in key_params]: 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.ctap1_poll_delay, event, on_keepalive, self.ctap1.register, dummy_param, dummy_param, ) raise ClientError.ERR.DEVICE_INELIGIBLE() return AttestationObject.from_ctap1( app_param, _call_polling( self.ctap1_poll_delay, event, on_keepalive, self.ctap1.register, client_data.hash, app_param, ), ) def get_assertion(self, options, **kwargs): """Get an assertion. :param options: PublicKeyCredentialRequestOptions data. :param pin: (optional) Used if PIN verification is required. :param threading.Event event: (optional) Signal to abort the operation. :param on_keepalive: (optional) Not implemented. """ options = PublicKeyCredentialRequestOptions._wrap(options) pin = kwargs.get("pin") event = kwargs.get("event", Event()) if options.timeout: timer = Timer(options.timeout / 1000, event.set) timer.daemon = True timer.start() self._verify_rp_id(options.rp_id) client_data = self._build_client_data( WEBAUTHN_TYPE.GET_ASSERTION, options.challenge ) try: return ( self._do_get_assertion( client_data, options.rp_id, options.allow_credentials, options.extensions, self._get_ctap_uv(options.user_verification, pin is not None), pin, event, kwargs.get("on_keepalive"), ), client_data, ) except CtapError as e: raise _ctap2client_err(e) finally: if options.timeout: timer.cancel() def _ctap2_get_assertion( self, client_data, rp_id, allow_list, extensions, uv, pin, event, on_keepalive ): pin_auth = None pin_protocol = None if pin: pin_protocol = self.pin_protocol.VERSION pin_token = self.pin_protocol.get_pin_token(pin) pin_auth = hmac_sha256(pin_token, client_data.hash)[:16] elif self.info.options.get("clientPin") and not uv: raise ClientError.ERR.BAD_REQUEST("PIN required but not provided") if uv: options = {"uv": True} else: options = None 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") return self.ctap2.get_assertions( rp_id, client_data.hash, allow_list, extensions, options, pin_auth, pin_protocol, event, on_keepalive, ) def _ctap1_get_assertion( self, client_data, rp_id, allow_list, extensions, uv, pin, event, on_keepalive ): if uv 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.ctap1_poll_delay, event, on_keepalive, self.ctap1.authenticate, client_param, app_param, cred["id"], ) return [AssertionResponse.from_ctap1(app_param, cred, auth_resp)] 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() _WIN_INFO = Info.create(["U2F_V2", "FIDO_2_0"]) if platform.system().lower() == "windows": try: from .win_api import ( WinAPI, WebAuthNAuthenticatorAttachment, WebAuthNUserVerificationRequirement, WebAuthNAttestationConvoyancePreference, ) except Exception: # TODO: Make this less generic pass class WindowsClient(_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, verify=verify_rp_id, handle=None): super(WindowsClient, self).__init__(origin, verify) self.api = WinAPI(handle) @property def info(self): return _WIN_INFO @staticmethod def is_available(): 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._wrap(options) self._verify_rp_id(options.rp.id) client_data = self._build_client_data( WEBAUTHN_TYPE.MAKE_CREDENTIAL, 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) return AttestationObject(result), client_data 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._wrap(options) self._verify_rp_id(options.rp_id) client_data = self._build_client_data( WEBAUTHN_TYPE.GET_ASSERTION, 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 ( [AssertionResponse.create(credential, auth_data, signature, user)], client_data, ) fido2-0.8.1/fido2/cose.py0000644000175000017500000001633513563266532014724 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals from .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 try: from cryptography.hazmat.primitives.asymmetric import ed25519 except ImportError: # EdDSA requires Cryptography >= 2.6. ed25519 = None class CoseKey(dict): """A COSE formatted public key. :param _: The COSE key paramters. :cvar ALGORITHM: COSE algorithm identifier. """ ALGORITHM = None def verify(self, message, signature): """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, public_key): """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): """Get a subclass of CoseKey corresponding to an algorithm identifier. :param alg: The COSE identifier of the algorithm. :return: A CoseKey. """ if alg == EdDSA.ALGORITHM and ed25519 is None: # EdDSA requires Cryptography >= 2.6. return UnsupportedKey for cls in CoseKey.__subclasses__(): if cls.ALGORITHM == alg: return cls return UnsupportedKey @staticmethod def for_name(name): """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): """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(): """Get a list of all supported algorithm identifiers""" if ed25519: algs = (ES256, EdDSA, PS256, RS256) else: algs = (ES256, PS256, RS256) return [cls.ALGORITHM for cls in algs] 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 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() 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)}) fido2-0.8.1/fido2/ctap.py0000644000175000017500000001061113544577440014713 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import from enum import IntEnum, unique import abc @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. """ @abc.abstractmethod def call(self, cmd, data=b"", event=None, on_keepalive=None): """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): """Close the device, releasing any held resources.""" @classmethod @abc.abstractmethod def list_devices(cls): """Generates instances of cls for discoverable devices.""" class CtapError(Exception): @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 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_OPERATION_PENDING = 0x2A 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 PIN_REQUIRED = 0x36 PIN_POLICY_VIOLATION = 0x37 PIN_TOKEN_EXPIRED = 0x38 REQUEST_TOO_LARGE = 0x39 ACTION_TIMEOUT = 0x3A UP_REQUIRED = 0x3B OTHER = 0x7F SPEC_LAST = 0xDF EXTENSION_FIRST = 0xE0 EXTENSION_LAST = 0xEF VENDOR_FIRST = 0xF0 VENDOR_LAST = 0xFF def __str__(self): return "0x%02X - %s" % (self.value, self.name) def __init__(self, code): try: code = CtapError.ERR(code) message = "CTAP error: %s" % code except ValueError: message = "CTAP error: 0x%02X" % code self.code = code super(CtapError, self).__init__(message) fido2-0.8.1/fido2/ctap1.py0000644000175000017500000002245613554302612014772 0ustar daindain00000000000000# 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 .hid import CTAPHID from .utils import websafe_encode, websafe_decode, bytes2int, ByteBuffer from .cose import ES256 from .attestation import FidoU2FAttestation from enum import IntEnum, unique from binascii import b2a_hex import struct import six @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, data=b""): self.code = code self.data = data def __repr__(self): return "APDU error: 0x{:04X} {:d} bytes of data".format( self.code, len(self.data) ) 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. """ def __init__(self, _): super(RegistrationData, self).__init__() if six.indexbytes(self, 0) != 0x05: raise ValueError("Reserved byte != 0x05") self.public_key = self[1:66] kh_len = six.indexbytes(self, 66) self.key_handle = self[67 : 67 + kh_len] cert_offs = 67 + kh_len cert_len = six.indexbytes(self, cert_offs + 1) if cert_len > 0x80: n_bytes = cert_len - 0x80 cert_len = ( bytes2int(self[cert_offs + 2 : cert_offs + 2 + n_bytes]) + n_bytes ) cert_len += 2 self.certificate = self[cert_offs : cert_offs + cert_len] self.signature = self[cert_offs + cert_len :] @property def b64(self): """Websafe base64 encoded string of the RegistrationData.""" return websafe_encode(self) def verify(self, app_param, client_param): """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, ) def __repr__(self): return ( "RegistrationData(public_key: h'%s', key_handle: h'%s', " "certificate: h'%s', signature: h'%s')" ) % tuple( b2a_hex(x).decode() for x in ( self.public_key, self.key_handle, self.certificate, self.signature, ) ) def __str__(self): return "%r" % self @classmethod def from_b64(cls, data): """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)) 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. """ def __init__(self, _): super(SignatureData, self).__init__() reader = ByteBuffer(self) self.user_presence = reader.unpack("B") self.counter = reader.unpack(">I") self.signature = reader.read() @property def b64(self): """str: Websafe base64 encoded string of the SignatureData.""" return websafe_encode(self) def verify(self, app_param, client_param, public_key): """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) def __repr__(self): return ( "SignatureData(user_presence: 0x%02x, counter: %d, " "signature: h'%s'" ) % (self.user_presence, self.counter, b2a_hex(self.signature)) def __str__(self): return "%r" % self @classmethod def from_b64(cls, data): """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(object): """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): self.device = device def send_apdu(self, cla=0, ins=0, p1=0, p2=0, data=b""): """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 """ size = len(data) size_h = size >> 16 & 0xFF size_l = size & 0xFFFF apdu = struct.pack(">BBBBBH", cla, ins, p1, p2, size_h, size_l) + 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): """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, app_param): """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, app_param, key_handle, check_only=False): """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) fido2-0.8.1/fido2/ctap2.py0000644000175000017500000011436313565714376015012 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals from . import cbor from .ctap import CtapError from .cose import CoseKey, ES256 from .hid import CTAPHID, CAPABILITY from .utils import ByteBuffer, sha256, hmac_sha256, bytes2int, int2bytes from .attestation import FidoU2FAttestation from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from binascii import b2a_hex from enum import IntEnum, unique import struct import six import re def args(*params): """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) def hexstr(bs): """Formats a byte string as a human readable hex string. :param bs: The bytes to format. :return: A readable string representation of the input. """ return "h'%s'" % b2a_hex(bs).decode() class Info(bytes): """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_protocols: The PIN 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. """ @unique class KEY(IntEnum): VERSIONS = 0x01 EXTENSIONS = 0x02 AAGUID = 0x03 OPTIONS = 0x04 MAX_MSG_SIZE = 0x05 PIN_PROTOCOLS = 0x06 MAX_CREDS_IN_LIST = 0x07 MAX_CRED_ID_LENGTH = 0x08 TRANSPORTS = 0x09 ALGORITHMS = 0x0A @classmethod def get(cls, key): try: return cls(key) except ValueError: return key def __init__(self, _): super(Info, self).__init__() data = dict((Info.KEY.get(k), v) for (k, v) in cbor.decode(self).items()) self.versions = data[Info.KEY.VERSIONS] self.extensions = data.get(Info.KEY.EXTENSIONS, []) self.aaguid = data[Info.KEY.AAGUID] self.options = data.get(Info.KEY.OPTIONS, {}) self.max_msg_size = data.get(Info.KEY.MAX_MSG_SIZE, 1024) self.pin_protocols = data.get(Info.KEY.PIN_PROTOCOLS, []) self.max_creds_in_list = data.get(Info.KEY.MAX_CREDS_IN_LIST) self.max_cred_id_length = data.get(Info.KEY.MAX_CRED_ID_LENGTH) self.transports = data.get(Info.KEY.TRANSPORTS, []) self.algorithms = data.get(Info.KEY.ALGORITHMS) self.data = data def __repr__(self): r = "Info(versions: %r" % self.versions if self.extensions: r += ", extensions: %r" % self.extensions r += ", aaguid: %s" % hexstr(self.aaguid) if self.options: r += ", options: %r" % self.options r += ", max_message_size: %d" % self.max_msg_size if self.pin_protocols: r += ", pin_protocols: %r" % self.pin_protocols if self.max_creds_in_list: r += ", max_credential_count_in_list: %d" % self.max_creds_in_list if self.max_cred_id_length: r += ", max_credential_id_length: %d" % self.max_cred_id_length if self.transports: r += ", transports: %r" % self.transports if self.algorithms: r += ", algorithms: %r" % self.algorithms return r + ")" def __str__(self): return self.__repr__() @classmethod def create( cls, versions, extensions=None, aaguid=b"\0" * 16, options=None, max_msg_size=None, pin_protocols=None, max_creds_in_list=None, max_cred_id_length=None, transports=None, algorithms=None, ): """Create an Info by providing its components. See class docstring for parameter descriptions. """ return cls( cbor.encode( args( versions, extensions, aaguid, options, max_msg_size, pin_protocols, max_creds_in_list, max_cred_id_length, transports, algorithms, ) ) ) class AttestedCredentialData(bytes): """Binary encoding of the attested credential data. :param _: The binary representation of the attested credential data. :ivar aaguid: The AAGUID of the authenticator. :ivar credential_id: The binary ID of the credential. :ivar public_key: The public key of the credential. """ def __init__(self, _): super(AttestedCredentialData, self).__init__() parsed = AttestedCredentialData.parse(self) self.aaguid = parsed[0] self.credential_id = parsed[1] self.public_key = parsed[2] if parsed[3]: raise ValueError("Wrong length") def __repr__(self): return ( "AttestedCredentialData(aaguid: %s, credential_id: %s, " "public_key: %s" ) % (hexstr(self.aaguid), hexstr(self.credential_id), self.public_key) def __str__(self): return self.__repr__() @staticmethod def parse(data): """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 = 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, credential_id, public_key): """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): """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. """ parts = cls.parse(data) return cls.create(*parts[:-1]), parts[-1] @classmethod def from_ctap1(cls, key_handle, public_key): """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( b"\0" * 16, key_handle, ES256.from_ctap1(public_key) # AAGUID ) 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. """ @unique class FLAG(IntEnum): """Authenticator data flags See https://www.w3.org/TR/webauthn/#sec-authenticator-data for details """ USER_PRESENT = 0x01 USER_VERIFIED = 0x04 ATTESTED = 0x40 EXTENSION_DATA = 0x80 def __init__(self, _): super(AuthenticatorData, self).__init__() reader = ByteBuffer(self) self.rp_id_hash = reader.read(32) self.flags = reader.unpack("B") self.counter = reader.unpack(">I") rest = reader.read() if self.flags & AuthenticatorData.FLAG.ATTESTED: self.credential_data, rest = AttestedCredentialData.unpack_from(rest) else: self.credential_data = None if self.flags & AuthenticatorData.FLAG.EXTENSION_DATA: self.extensions, rest = cbor.decode_from(rest) else: self.extensions = None if rest: raise ValueError("Wrong length") @classmethod def create(cls, rp_id_hash, flags, counter, credential_data=b"", extensions=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): """Return true if the User Present flag is set. :return: True if User Present is set, False otherwise. :rtype: bool """ return bool(self.flags & AuthenticatorData.FLAG.USER_PRESENT) def is_user_verified(self): """Return true if the User Verified flag is set. :return: True if User Verified is set, False otherwise. :rtype: bool """ return bool(self.flags & AuthenticatorData.FLAG.USER_VERIFIED) def is_attested(self): """Return true if the Attested credential data flag is set. :return: True if Attested credential data is set, False otherwise. :rtype: bool """ return bool(self.flags & AuthenticatorData.FLAG.ATTESTED) def has_extension_data(self): """Return true if the Extenstion data flag is set. :return: True if Extenstion data is set, False otherwise. :rtype: bool """ return bool(self.flags & AuthenticatorData.FLAG.EXTENSION_DATA) def __repr__(self): r = "AuthenticatorData(rp_id_hash: %s, flags: 0x%02x, counter: %d" % ( hexstr(self.rp_id_hash), self.flags, self.counter, ) if self.credential_data: r += ", credential_data: %s" % self.credential_data if self.extensions: r += ", extensions: %s" % self.extensions return r + ")" def __str__(self): return self.__repr__() class AttestationObject(bytes): """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_statement: The attestation statement. :type att_statement: Dict[str, Any] :ivar data: The AttestationObject members, in the form of a dict. :type data: Dict[AttestationObject.KEY, Any] """ @unique class KEY(IntEnum): FMT = 1 AUTH_DATA = 2 ATT_STMT = 3 @classmethod def for_key(cls, key): """Get an AttestationObject.KEY by number or by name, using the numeric ID or the Webauthn key string. :param key: The numeric key value, or the string name of a member. :type key: Union[str, int] :return: The KEY corresponding to the input. :rtype: AttestationObject.KEY """ if isinstance(key, int): return cls(key) name = re.sub("([a-z])([A-Z])", r"\1_\2", key).upper() return getattr(cls, name) @property def string_key(self): """Get the string used for this key in the Webauthn specification. :return: The Webauthn string used for a key. :rtype: str """ value = "".join(w.capitalize() for w in self.name.split("_")) return value[0].lower() + value[1:] def __init__(self, _): super(AttestationObject, self).__init__() data = dict( (AttestationObject.KEY.for_key(k), v) for (k, v) in cbor.decode(self).items() ) self.fmt = data[AttestationObject.KEY.FMT] self.auth_data = AuthenticatorData(data[AttestationObject.KEY.AUTH_DATA]) data[AttestationObject.KEY.AUTH_DATA] = self.auth_data self.att_statement = data[AttestationObject.KEY.ATT_STMT] self.data = data def __repr__(self): return "AttestationObject(fmt: %r, auth_data: %r, att_statement: %r)" % ( self.fmt, self.auth_data, self.att_statement, ) def __str__(self): return self.__repr__() @classmethod def create(cls, fmt, auth_data, att_stmt): """Create an AttestationObject instance. :param fmt: The type of attestation used. :type fmt: str :param auth_data: Binary representation of the authenticator data. :type auth_data: bytes :param att_stmt: The attestation statement. :type att_stmt: dict :return: The attestation object. :rtype: AttestationObject """ return cls(cbor.encode(args(fmt, auth_data, att_stmt))) @classmethod def from_ctap1(cls, app_param, registration): """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( FidoU2FAttestation.FORMAT, AuthenticatorData.create( app_param, 0x41, 0, AttestedCredentialData.from_ctap1( registration.key_handle, registration.public_key ), ), { # att_statement "x5c": [registration.certificate], "sig": registration.signature, }, ) def with_int_keys(self): """Get a copy of this AttestationObject, using CTAP2 integer values as map keys in the CBOR representation. :return: The attestation object, using int keys. :rtype: AttestationObject """ return AttestationObject(cbor.encode(self.data)) def with_string_keys(self): """Get a copy of this AttestationObject, using Webauthn string values as map keys in the CBOR representation. :return: The attestation object, using str keys. :rtype: AttestationObject """ return AttestationObject( cbor.encode(dict((k.string_key, v) for k, v in self.data.items())) ) class AssertionResponse(bytes): """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). """ @unique class KEY(IntEnum): CREDENTIAL = 1 AUTH_DATA = 2 SIGNATURE = 3 USER = 4 N_CREDS = 5 def __init__(self, _): super(AssertionResponse, self).__init__() data = dict( (AssertionResponse.KEY(k), v) for (k, v) in cbor.decode(self).items() ) self.credential = data.get(AssertionResponse.KEY.CREDENTIAL) self.auth_data = AuthenticatorData(data[AssertionResponse.KEY.AUTH_DATA]) self.signature = data[AssertionResponse.KEY.SIGNATURE] self.user = data.get(AssertionResponse.KEY.USER) self.number_of_credentials = data.get(AssertionResponse.KEY.N_CREDS) self.data = data def __repr__(self): r = "AssertionResponse(credential: %r, auth_data: %r, signature: %s" % ( self.credential, self.auth_data, hexstr(self.signature), ) if self.user: r += ", user: %s" % self.user if self.number_of_credentials is not None: r += ", number_of_credentials: %d" % self.number_of_credentials return r + ")" def __str__(self): return self.__repr__() def verify(self, client_param, public_key): """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 create(cls, credential, auth_data, signature, user=None, n_creds=None): """Create an AssertionResponse instance. :param credential: The credential used for the response. :param auth_data: The binary encoded authenticator data. :param signature: The digital signature of the response. :param user: The user data of the credential, if any. :param n_creds: The number of responses available. :return: The assertion response. """ return cls(cbor.encode(args(credential, auth_data, signature, user, n_creds))) @classmethod def from_ctap1(cls, app_param, credential, authentication): """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.create( credential, AuthenticatorData.create( app_param, authentication.user_presence & 0x01, authentication.counter ), authentication.signature, ) class CTAP2(object): """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 # 0x41 is the command byte for credmgmt preview CREDENTIAL_MGMT = 0x41 def __init__(self, device, strict_cbor=True): if not device.capabilities & CAPABILITY.CBOR: raise ValueError("Device does not support CTAP2.") self.device = device self._strict_cbor = strict_cbor def send_cbor( self, cmd, data=None, event=None, parse=cbor.decode, on_keepalive=None ): """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 parse: Function used to parse the binary response data, defaults to parsing the CBOR. :param on_keepalive: Optional function called when keep-alive is sent by the authenticator. :return: The result of calling the parse function on the response data (defaults to the CBOR decoded value). """ 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 = six.indexbytes(response, 0) if status != 0x00: raise CtapError(status) if len(response) == 1: return None enc = response[1:] if self._strict_cbor: expected = cbor.encode(cbor.decode(enc)) if expected != enc: enc_h = b2a_hex(enc) exp_h = b2a_hex(expected) raise ValueError( "Non-canonical CBOR from Authenticator.\n" "Got: {}\n".format(enc_h) + "Expected: {}".format(exp_h) ) return parse(enc) def make_credential( self, client_data_hash, rp, user, key_params, exclude_list=None, extensions=None, options=None, pin_auth=None, pin_protocol=None, event=None, on_keepalive=None, ): """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_auth: Optional PIN auth parameter. :param pin_protocol: The version of PIN 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. """ return self.send_cbor( CTAP2.CMD.MAKE_CREDENTIAL, args( client_data_hash, rp, user, key_params, exclude_list, extensions, options, pin_auth, pin_protocol, ), event, AttestationObject, on_keepalive, ) def get_assertion( self, rp_id, client_data_hash, allow_list=None, extensions=None, options=None, pin_auth=None, pin_protocol=None, event=None, on_keepalive=None, ): """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_auth: Optional PIN auth parameter. :param pin_protocol: The version of PIN 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. """ return self.send_cbor( CTAP2.CMD.GET_ASSERTION, args( rp_id, client_data_hash, allow_list, extensions, options, pin_auth, pin_protocol, ), event, AssertionResponse, on_keepalive, ) def get_info(self): """CTAP2 getInfo command. :return: Information about the authenticator. """ return self.send_cbor(CTAP2.CMD.GET_INFO, parse=Info) def client_pin( self, pin_protocol, sub_cmd, key_agreement=None, pin_auth=None, new_pin_enc=None, pin_hash_enc=None, ): """CTAP2 clientPin command, used for various PIN operations. :param pin_protocol: The PIN protocol version to use. :param sub_cmd: A clientPin sub command. :param key_agreement: The keyAgreement parameter. :param pin_auth: The pinAuth parameter. :param new_pin_enc: The newPinEnc parameter. :param pin_hash_enc: The pinHashEnc parameter. :return: The response of the command, decoded. """ return self.send_cbor( CTAP2.CMD.CLIENT_PIN, args( pin_protocol, sub_cmd, key_agreement, pin_auth, new_pin_enc, pin_hash_enc, ), ) def reset(self, event=None, on_keepalive=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) def get_next_assertion(self): """CTAP2 getNextAssertion command. :return: The next available assertion response. """ return self.send_cbor(CTAP2.CMD.GET_NEXT_ASSERTION, parse=AssertionResponse) def credential_mgmt( self, sub_cmd, sub_cmd_params=None, pin_protocol=None, pin_auth=None ): """CTAP2 credentialManagement command, used to manage resident credentials. :param sub_cmd: A credentialManagement sub command. :param sub_cmd_params: Sub command specific parameters. :param pin_protocol: PIN protocol version used. :pin_auth: """ return self.send_cbor( CTAP2.CMD.CREDENTIAL_MGMT, args(sub_cmd, sub_cmd_params, pin_protocol, pin_auth), ) def get_assertions(self, *args, **kwargs): """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 _pad_pin(pin): if not isinstance(pin, six.string_types): raise ValueError("PIN of wrong type, expecting %s" % six.string_types) if len(pin) < 4: raise ValueError("PIN must be >= 4 characters") pin = pin.encode("utf8").ljust(64, b"\0") pin += b"\0" * (-(len(pin) - 16) % 16) if len(pin) > 255: raise ValueError("PIN must be <= 255 bytes") return pin class PinProtocolV1(object): """Implementation of the CTAP2 PIN protocol v1. :param ctap: An instance of a CTAP2 object. :cvar VERSION: The version number of the PIV protocol. :cvar IV: An all-zero IV used for some cryptographic operations. """ VERSION = 1 IV = b"\x00" * 16 @unique class CMD(IntEnum): GET_RETRIES = 0x01 GET_KEY_AGREEMENT = 0x02 SET_PIN = 0x03 CHANGE_PIN = 0x04 GET_PIN_TOKEN = 0x05 @unique class RESULT(IntEnum): KEY_AGREEMENT = 0x01 PIN_TOKEN = 0x02 RETRIES = 0x03 def __init__(self, ctap): self.ctap = ctap def get_shared_secret(self): 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), } resp = self.ctap.client_pin( PinProtocolV1.VERSION, PinProtocolV1.CMD.GET_KEY_AGREEMENT ) pk = resp[PinProtocolV1.RESULT.KEY_AGREEMENT] x = bytes2int(pk[-2]) y = bytes2int(pk[-3]) pk = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()).public_key(be) shared_secret = sha256(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 get_pin_token(self, pin): """Get a PIN token from the authenticator. :param pin: The PIN of the authenticator. :return: A PIN token. """ key_agreement, shared_secret = self.get_shared_secret() cipher = self._get_cipher(shared_secret) pin_hash = sha256(pin.encode())[:16] enc = cipher.encryptor() pin_hash_enc = enc.update(pin_hash) + enc.finalize() resp = self.ctap.client_pin( PinProtocolV1.VERSION, PinProtocolV1.CMD.GET_PIN_TOKEN, key_agreement=key_agreement, pin_hash_enc=pin_hash_enc, ) dec = cipher.decryptor() return dec.update(resp[PinProtocolV1.RESULT.PIN_TOKEN]) + dec.finalize() def get_pin_retries(self): """Get the number of PIN retries remaining. :return: The number or PIN attempts until the authenticator is locked. """ resp = self.ctap.client_pin( PinProtocolV1.VERSION, PinProtocolV1.CMD.GET_RETRIES ) return resp[PinProtocolV1.RESULT.RETRIES] def set_pin(self, pin): """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. """ pin = _pad_pin(pin) key_agreement, shared_secret = self.get_shared_secret() cipher = self._get_cipher(shared_secret) enc = cipher.encryptor() pin_enc = enc.update(pin) + enc.finalize() pin_auth = hmac_sha256(shared_secret, pin_enc)[:16] self.ctap.client_pin( PinProtocolV1.VERSION, PinProtocolV1.CMD.SET_PIN, key_agreement=key_agreement, new_pin_enc=pin_enc, pin_auth=pin_auth, ) def change_pin(self, old_pin, new_pin): """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. """ new_pin = _pad_pin(new_pin) key_agreement, shared_secret = self.get_shared_secret() cipher = self._get_cipher(shared_secret) pin_hash = sha256(old_pin.encode())[:16] enc = cipher.encryptor() pin_hash_enc = enc.update(pin_hash) + enc.finalize() enc = cipher.encryptor() new_pin_enc = enc.update(new_pin) + enc.finalize() pin_auth = hmac_sha256(shared_secret, new_pin_enc + pin_hash_enc)[:16] self.ctap.client_pin( PinProtocolV1.VERSION, PinProtocolV1.CMD.CHANGE_PIN, key_agreement=key_agreement, pin_hash_enc=pin_hash_enc, new_pin_enc=new_pin_enc, pin_auth=pin_auth, ) class CredentialManagement(object): """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_protocol: The PIN protocol version used. :param pin_token: A valid pin_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 @unique class SUB_PARAMETER(IntEnum): RP_ID_HASH = 0x01 CREDENTIAL_ID = 0x02 @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 def __init__(self, ctap, pin_protocol, pin_token): self.ctap = ctap self.pin_protocol = pin_protocol self.pin_token = pin_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_protocol"] = self.pin_protocol kwargs["pin_auth"] = hmac_sha256(self.pin_token, msg)[:16] return self.ctap.credential_mgmt(**kwargs) def get_metadata(self): """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): """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): """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): """Convenience method to enumerate all RPs. See enumerate_rps_begin and enumerate_rps_next for details. """ first = self.enumerate_rps_begin() 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): """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.SUB_PARAMETER.RP_ID_HASH: rp_id_hash}, ) def enumerate_creds_next(self): """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): """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): """Delete a resident credential. :param cred_id: The ID of the credential to delete. """ return self._call( CredentialManagement.CMD.DELETE_CREDENTIAL, {CredentialManagement.SUB_PARAMETER.CREDENTIAL_ID: cred_id}, ) fido2-0.8.1/fido2/extensions.py0000644000175000017500000001036113544577440016165 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals from .ctap2 import PinProtocolV1 from .utils import hmac_sha256 import abc class Extension(abc.ABC): """ Base class for CTAP2 extensions. """ NAME = None def results_for(self, auth_data): """ Get the parsed extension results from an AuthenticatorData object. """ data = auth_data.extensions.get(self.NAME) if auth_data.is_attested(): return self.create_result(data) else: return self.get_result(data) def create_dict(self, *args, **kwargs): """ Return extension dict for use with calls to make_credential. """ return {self.NAME: self.create_data(*args, **kwargs)} def get_dict(self, *args, **kwargs): """ Return extension dict for use with calls to get_assertion. """ return {self.NAME: self.get_data(*args, **kwargs)} @abc.abstractmethod def create_data(self, *args, **kwargs): """ Return extension data value for use with calls to make_credential. """ @abc.abstractmethod def create_result(self, data): """ Process and return extension result from call to make_credential. """ @abc.abstractmethod def get_data(self, *args, **kwargs): """ Return extension data value for use with calls to get_assertion. """ @abc.abstractmethod def get_result(self, data): """ Process and return extension result from call to get_assertion. """ class HmacSecretExtension(Extension): """ Implements the hmac-secret CTAP2 extension. """ NAME = "hmac-secret" SALT_LEN = 32 def __init__(self, ctap): self._pin_protocol = PinProtocolV1(ctap) def create_data(self): return True def create_result(self, data): if data is not True: raise ValueError("hmac-secret extension not supported") def get_data(self, salt1, salt2=b""): if len(salt1) != self.SALT_LEN: raise ValueError("Wrong length for salt1") if salt2 and len(salt2) != self.SALT_LEN: raise ValueError("Wrong length for salt2") key_agreement, shared_secret = self._pin_protocol.get_shared_secret() self._agreement = key_agreement self._secret = shared_secret enc = self._pin_protocol._get_cipher(shared_secret).encryptor() salt_enc = enc.update(salt1) + enc.update(salt2) + enc.finalize() return { 1: key_agreement, 2: salt_enc, 3: hmac_sha256(shared_secret, salt_enc)[:16], } def get_result(self, data): dec = self._pin_protocol._get_cipher(self._secret).decryptor() salt = dec.update(data) + dec.finalize() return ( salt[: HmacSecretExtension.SALT_LEN], salt[HmacSecretExtension.SALT_LEN :], ) fido2-0.8.1/fido2/hid.py0000644000175000017500000000714613554254377014543 0ustar daindain00000000000000from __future__ import absolute_import from .ctap import CtapDevice, CtapError, STATUS from ._pyu2f import hidtransport from enum import IntEnum, unique from threading import Event import struct @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(IntEnum): WINK = 0x01 LOCK = 0x02 # Not used CBOR = 0x04 NMSG = 0x08 def supported(self, flags): return bool(flags & self) TYPE_INIT = 0x80 class _SingleEvent(object): def __init__(self): self.flag = False def is_set(self): if not self.flag: self.flag = True return False return True class CtapHidDevice(CtapDevice): """ CtapDevice implementation using the HID transport. :cvar descriptor: Device descriptor. """ def __init__(self, descriptor, dev): self.descriptor = descriptor self._dev = dev def __repr__(self): return "CtapHidDevice(%s)" % self.descriptor["path"] @property def version(self): """CTAP HID protocol version. :rtype: int """ return self._dev.u2fhid_version @property def device_version(self): """Device version number.""" return self._dev.device_version @property def capabilities(self): """Capabilities supported by the device.""" return self._dev.capabilities def call(self, cmd, data=b"", event=None, on_keepalive=None): event = event or Event() self._dev.InternalSend(TYPE_INIT | cmd, bytearray(data)) last_ka = None while not event.is_set(): status, resp = self._dev.InternalRecv() status ^= TYPE_INIT if status == cmd: return bytes(resp) elif status == CTAPHID.ERROR: raise CtapError(resp[0]) elif status == CTAPHID.KEEPALIVE: 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) continue else: raise CtapError(CtapError.ERR.INVALID_COMMAND) # Cancel the request. self._dev.InternalSend(TYPE_INIT | CTAPHID.CANCEL, bytearray()) self._dev.InternalRecv() raise CtapError(CtapError.ERR.KEEPALIVE_CANCEL) def wink(self): """Causes the authenticator to blink.""" self.call(CTAPHID.WINK) def ping(self, msg=b"Hello FIDO"): """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=10): """Locks the channel.""" self.call(CTAPHID.LOCK, struct.pack(">B", lock_time)) def close(self): del self._dev del self.descriptor @classmethod def list_devices(cls, selector=hidtransport.HidUsageSelector): for d in hidtransport.hid.Enumerate(): if selector(d): try: dev = hidtransport.hid.Open(d["path"]) yield cls(d, hidtransport.UsbHidTransport(dev)) except OSError: # Insufficient permissions to access device pass fido2-0.8.1/fido2/nfc.py0000644000175000017500000001372213544577440014540 0ustar daindain00000000000000# 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 absolute_import, unicode_literals from .ctap import CtapDevice, CtapError, STATUS from .hid import CAPABILITY, CTAPHID from .pcsc import PCSCDevice from smartcard.Exceptions import CardConnectionException from threading import Event import struct import six AID_FIDO = b"\xa0\x00\x00\x06\x47\x2f\x00\x01" SW_SUCCESS = (0x90, 0x00) SW_UPDATE = (0x91, 0x00) SW1_MORE_DATA = 0x61 class CardSelectException(Exception): """can't select u2f/fido2 application on the card""" pass class CtapNfcDevice(CtapDevice): """ CtapDevice implementation using the pcsc NFC transport. """ def __init__(self, dev): self._dev = dev self._dev.connect() self._capabilities = 0 result, sw1, sw2 = self._dev.select_applet(AID_FIDO) if (sw1, sw2) != SW_SUCCESS: raise CardSelectException("Select error") if result == b"U2F_V2": self._capabilities |= CAPABILITY.NMSG try: # Probe for CTAP2 by calling GET_INFO self.call(CTAPHID.CBOR, b"\x04") self._capabilities |= CAPABILITY.CBOR except CtapError: pass @property def pcsc_device(self): return self._dev def __repr__(self): return "CtapNfcDevice(%s)" % self._dev.reader.name @property def version(self): """CTAP NFC protocol version. :rtype: int """ return 2 if self._capabilities & CAPABILITY.CBOR else 1 @property def capabilities(self): """Capabilities supported by the device.""" return self._capabilities def _chain_apdus(self, cla, ins, p1, p2, data=b""): while len(data) > 250: to_send, data = data[:250], data[250:] header = struct.pack("!BBBBB", 0x90, ins, p1, p2, len(to_send)) resp, sw1, sw2 = self._dev.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._dev.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._dev.apdu_exchange(apdu) resp += lres return resp, sw1, sw2 def _call_apdu(self, apdu): if len(apdu) >= 7 and six.indexbytes(apdu, 4) == 0: # Extended APDU data_len = struct.unpack("!H", apdu[5:7])[0] data = apdu[7 : 7 + data_len] else: # Short APDU data_len = six.indexbytes(apdu, 4) data = apdu[5 : 5 + data_len] (cla, ins, p1, p2) = six.iterbytes(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=b"", event=None, on_keepalive=None): 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 = six.indexbytes(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, b"") 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, data=b"", event=None, on_keepalive=None): if cmd == CTAPHID.MSG: return self._call_apdu(data) elif cmd == CTAPHID.CBOR: return self._call_cbor(data, event, on_keepalive) else: raise CtapError(CtapError.ERR.INVALID_COMMAND) @classmethod # selector='CL' def list_devices(cls, selector="", pcsc_device=PCSCDevice): """ Returns list of readers in the system. Iterator. :param selector: :param pcsc_device: device to work with. PCSCDevice by default. :return: iterator. next reader """ for d in pcsc_device.list_devices(selector): try: yield cls(d) except CardConnectionException: pass fido2-0.8.1/fido2/pcsc.py0000644000175000017500000001755613544577440014733 0ustar daindain00000000000000# 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 absolute_import, unicode_literals from .ctap import CtapDevice, CtapError, STATUS from .hid import CAPABILITY, CTAPHID from smartcard import System from smartcard.pcsc.PCSCExceptions import ListReadersException from smartcard.pcsc.PCSCContext import PCSCContext from binascii import b2a_hex from threading import Event import struct import six 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, name): self._capabilities = 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 self._capabilities == 0: raise ValueError("Unsupported device") def __repr__(self): return "CtapPcscDevice(%s)" % self._name @property def version(self): """CTAPHID protocol version. :rtype: int """ return 2 if self._capabilities & CAPABILITY.CBOR else 1 @property def capabilities(self): """Capabilities supported by the device.""" return self._capabilities def get_atr(self): """Get the ATR/ATS of the connected card.""" return self._conn.getATR() def apdu_exchange(self, apdu, protocol=None): """Exchange data with smart card. :param apdu: byte string. data to exchange with card :return: byte string. response from card """ logger.debug("apdu %s", b2a_hex(apdu)) resp, sw1, sw2 = self._conn.transmit(list(six.iterbytes(apdu)), protocol) response = bytes(bytearray(resp)) logger.debug("response [0x%04X] %s", sw1 << 8 + sw2, b2a_hex(response)) return response, sw1, sw2 def control_exchange(self, control_code, control_data=b""): """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.debug("control %s", b2a_hex(control_data)) response = self._conn.control(control_code, list(six.iterbytes(control_data))) response = bytes(bytearray(response)) logger.debug("response %s", b2a_hex(response)) return response def _select(self): 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 |= 0x08 def _chain_apdus(self, cla, ins, p1, p2, data=b""): 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): if len(apdu) >= 7 and six.indexbytes(apdu, 4) == 0: # Extended APDU data_len = struct.unpack("!H", apdu[5:7])[0] data = apdu[7 : 7 + data_len] else: # Short APDU data_len = six.indexbytes(apdu, 4) data = apdu[5 : 5 + data_len] (cla, ins, p1, p2) = six.iterbytes(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=b"", event=None, on_keepalive=None): 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 = six.indexbytes(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, data=b"", event=None, on_keepalive=None): 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): self._conn.disconnect() @classmethod def list_devices(cls, name=""): 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() fido2-0.8.1/fido2/public_suffix_list.dat0000644000175000017500000064555113565752352020022 0ustar daindain00000000000000// 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 : https://en.wikipedia.org/wiki/.ac 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://en.wikipedia.org/wiki/.ae // see also: "Domain Name Eligibility Policy" at http://www.aeda.ae/eng/aepolicy.php 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 freight.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/nic-argentina/normativa-vigente ar com.ar edu.ar gob.ar gov.ar int.ar mil.ar musica.ar net.ar org.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 // 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 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 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://en.wikipedia.org/wiki/.bj bj asso.bj barreau.bj gouv.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 arq.br art.br ato.br b.br barueri.br belem.br bhz.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 cri.br cuiaba.br curitiba.br def.br ecn.br eco.br edu.br emp.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 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 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 ribeirao.br rio.br riobranco.br riopreto.br salvador.br sampa.br santamaria.br santoandre.br saobernardo.br saogonca.br sjc.br slg.br slz.br sorocaba.br srv.br taxi.br tc.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://en.wikipedia.org/wiki/.cl cl gov.cl gob.cl co.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 // 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 cy ac.cy biz.cy com.cy ekloges.cy gov.cy ltd.cy name.cy net.cy org.cy parliament.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 : https://en.wikipedia.org/wiki/.dz dz com.dz org.dz net.dz gov.dz edu.dz asso.dz pol.dz art.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 : https://en.wikipedia.org/wiki/.fj *.fj // fk : https://en.wikipedia.org/wiki/.fk *.fk // fm : https://en.wikipedia.org/wiki/.fm fm // fo : https://en.wikipedia.org/wiki/.fo fo // fr : http://www.afnic.fr/ // domaines descriptifs : https://www.afnic.fr/medias/documents/Cadre_legal/Afnic_Naming_Policy_12122016_VEN.pdf fr asso.fr com.fr gouv.fr nom.fr prd.fr tm.fr // domaines sectoriels : https://www.afnic.fr/en/products-and-services/the-fr-tld/sector-based-fr-domains-4.html 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 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 : http://www.gt/politicas_de_registro.html 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 // 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/ il ac.il co.il gov.il idf.il k12.il muni.il net.il org.il // 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 co.in firm.in net.in org.in gen.in ind.in nic.in ac.in edu.in res.in gov.in mil.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.html // 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 edu.ky gov.ky com.ky org.ky net.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 : http://www.nic.lk/seclevpr.html 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 : http://about.museum/naming/ // http://index.museum/ museum academy.museum agriculture.museum air.museum airguard.museum alabama.museum alaska.museum amber.museum ambulance.museum american.museum americana.museum americanantiques.museum americanart.museum amsterdam.museum and.museum annefrank.museum anthro.museum anthropology.museum antiques.museum aquarium.museum arboretum.museum archaeological.museum archaeology.museum architecture.museum art.museum artanddesign.museum artcenter.museum artdeco.museum arteducation.museum artgallery.museum arts.museum artsandcrafts.museum asmatart.museum assassination.museum assisi.museum association.museum astronomy.museum atlanta.museum austin.museum australia.museum automotive.museum aviation.museum axis.museum badajoz.museum baghdad.museum bahn.museum bale.museum baltimore.museum barcelona.museum baseball.museum basel.museum baths.museum bauern.museum beauxarts.museum beeldengeluid.museum bellevue.museum bergbau.museum berkeley.museum berlin.museum bern.museum bible.museum bilbao.museum bill.museum birdart.museum birthplace.museum bonn.museum boston.museum botanical.museum botanicalgarden.museum botanicgarden.museum botany.museum brandywinevalley.museum brasil.museum bristol.museum british.museum britishcolumbia.museum broadcast.museum brunel.museum brussel.museum brussels.museum bruxelles.museum building.museum burghof.museum bus.museum bushey.museum cadaques.museum california.museum cambridge.museum can.museum canada.museum capebreton.museum carrier.museum cartoonart.museum casadelamoneda.museum castle.museum castres.museum celtic.museum center.museum chattanooga.museum cheltenham.museum chesapeakebay.museum chicago.museum children.museum childrens.museum childrensgarden.museum chiropractic.museum chocolate.museum christiansburg.museum cincinnati.museum cinema.museum circus.museum civilisation.museum civilization.museum civilwar.museum clinton.museum clock.museum coal.museum coastaldefence.museum cody.museum coldwar.museum collection.museum colonialwilliamsburg.museum coloradoplateau.museum columbia.museum columbus.museum communication.museum communications.museum community.museum computer.museum computerhistory.museum comunicações.museum contemporary.museum contemporaryart.museum convent.museum copenhagen.museum corporation.museum correios-e-telecomunicações.museum corvette.museum costume.museum countryestate.museum county.museum crafts.museum cranbrook.museum creation.museum cultural.museum culturalcenter.museum culture.museum cyber.museum cymru.museum dali.museum dallas.museum database.museum ddr.museum decorativearts.museum delaware.museum delmenhorst.museum denmark.museum depot.museum design.museum detroit.museum dinosaur.museum discovery.museum dolls.museum donostia.museum durham.museum eastafrica.museum eastcoast.museum education.museum educational.museum egyptian.museum eisenbahn.museum elburg.museum elvendrell.museum embroidery.museum encyclopedic.museum england.museum entomology.museum environment.museum environmentalconservation.museum epilepsy.museum essex.museum estate.museum ethnology.museum exeter.museum exhibition.museum family.museum farm.museum farmequipment.museum farmers.museum farmstead.museum field.museum figueres.museum filatelia.museum film.museum fineart.museum finearts.museum finland.museum flanders.museum florida.museum force.museum fortmissoula.museum fortworth.museum foundation.museum francaise.museum frankfurt.museum franziskaner.museum freemasonry.museum freiburg.museum fribourg.museum frog.museum fundacio.museum furniture.museum gallery.museum garden.museum gateway.museum geelvinck.museum gemological.museum geology.museum georgia.museum giessen.museum glas.museum glass.museum gorge.museum grandrapids.museum graz.museum guernsey.museum halloffame.museum hamburg.museum handson.museum harvestcelebration.museum hawaii.museum health.museum heimatunduhren.museum hellas.museum helsinki.museum hembygdsforbund.museum heritage.museum histoire.museum historical.museum historicalsociety.museum historichouses.museum historisch.museum historisches.museum history.museum historyofscience.museum horology.museum house.museum humanities.museum illustration.museum imageandsound.museum indian.museum indiana.museum indianapolis.museum indianmarket.museum intelligence.museum interactive.museum iraq.museum iron.museum isleofman.museum jamison.museum jefferson.museum jerusalem.museum jewelry.museum jewish.museum jewishart.museum jfk.museum journalism.museum judaica.museum judygarland.museum juedisches.museum juif.museum karate.museum karikatur.museum kids.museum koebenhavn.museum koeln.museum kunst.museum kunstsammlung.museum kunstunddesign.museum labor.museum labour.museum lajolla.museum lancashire.museum landes.museum lans.museum läns.museum larsson.museum lewismiller.museum lincoln.museum linz.museum living.museum livinghistory.museum localhistory.museum london.museum losangeles.museum louvre.museum loyalist.museum lucerne.museum luxembourg.museum luzern.museum mad.museum madrid.museum mallorca.museum manchester.museum mansion.museum mansions.museum manx.museum marburg.museum maritime.museum maritimo.museum maryland.museum marylhurst.museum media.museum medical.museum medizinhistorisches.museum meeres.museum memorial.museum mesaverde.museum michigan.museum midatlantic.museum military.museum mill.museum miners.museum mining.museum minnesota.museum missile.museum missoula.museum modern.museum moma.museum money.museum monmouth.museum monticello.museum montreal.museum moscow.museum motorcycle.museum muenchen.museum muenster.museum mulhouse.museum muncie.museum museet.museum museumcenter.museum museumvereniging.museum music.museum national.museum nationalfirearms.museum nationalheritage.museum nativeamerican.museum naturalhistory.museum naturalhistorymuseum.museum naturalsciences.museum nature.museum naturhistorisches.museum natuurwetenschappen.museum naumburg.museum naval.museum nebraska.museum neues.museum newhampshire.museum newjersey.museum newmexico.museum newport.museum newspaper.museum newyork.museum niepce.museum norfolk.museum north.museum nrw.museum nyc.museum nyny.museum oceanographic.museum oceanographique.museum omaha.museum online.museum ontario.museum openair.museum oregon.museum oregontrail.museum otago.museum oxford.museum pacific.museum paderborn.museum palace.museum paleo.museum palmsprings.museum panama.museum paris.museum pasadena.museum pharmacy.museum philadelphia.museum philadelphiaarea.museum philately.museum phoenix.museum photography.museum pilots.museum pittsburgh.museum planetarium.museum plantation.museum plants.museum plaza.museum portal.museum portland.museum portlligat.museum posts-and-telecommunications.museum preservation.museum presidio.museum press.museum project.museum public.museum pubol.museum quebec.museum railroad.museum railway.museum research.museum resistance.museum riodejaneiro.museum rochester.museum rockart.museum roma.museum russia.museum saintlouis.museum salem.museum salvadordali.museum salzburg.museum sandiego.museum sanfrancisco.museum santabarbara.museum santacruz.museum santafe.museum saskatchewan.museum satx.museum savannahga.museum schlesisches.museum schoenbrunn.museum schokoladen.museum school.museum schweiz.museum science.museum scienceandhistory.museum scienceandindustry.museum sciencecenter.museum sciencecenters.museum science-fiction.museum sciencehistory.museum sciences.museum sciencesnaturelles.museum scotland.museum seaport.museum settlement.museum settlers.museum shell.museum sherbrooke.museum sibenik.museum silk.museum ski.museum skole.museum society.museum sologne.museum soundandvision.museum southcarolina.museum southwest.museum space.museum spy.museum square.museum stadt.museum stalbans.museum starnberg.museum state.museum stateofdelaware.museum station.museum steam.museum steiermark.museum stjohn.museum stockholm.museum stpetersburg.museum stuttgart.museum suisse.museum surgeonshall.museum surrey.museum svizzera.museum sweden.museum sydney.museum tank.museum tcm.museum technology.museum telekommunikation.museum television.museum texas.museum textile.museum theater.museum time.museum timekeeping.museum topology.museum torino.museum touch.museum town.museum transport.museum tree.museum trolley.museum trust.museum trustee.museum uhren.museum ulm.museum undersea.museum university.museum usa.museum usantiques.museum usarts.museum uscountryestate.museum usculture.museum usdecorativearts.museum usgarden.museum ushistory.museum ushuaia.museum uslivinghistory.museum utah.museum uvic.museum valley.museum vantaa.museum versailles.museum viking.museum village.museum virginia.museum virtual.museum virtuel.museum vlaanderen.museum volkenkunde.museum wales.museum wallonie.museum war.museum washingtondc.museum watchandclock.museum watch-and-clock.museum western.museum westfalen.museum whaling.museum wildlife.museum williamsburg.museum windmill.museum workshop.museum york.museum yorkshire.museum yosemite.museum youth.museum zoological.museum zoology.museum ירושלים.museum иком.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.net.my/ my com.my net.my org.my gov.my edu.my mil.my name.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 : http://www.norid.no/regelverk/index.en.html // The Norwegian registry has declined to notify us of updates. The web pages // referenced below are the official source of the data. There is also an // announce mailing list: // https://postlister.uninett.no/sympa/info/norid-diskusjon no // Norid generic domains : http://www.norid.no/regelverk/vedlegg-c.en.html fhs.no vgs.no fylkesbibl.no folkebibl.no museum.no idrett.no priv.no // Non-Norid generic domains : http://www.norid.no/regelverk/vedlegg-d.en.html mil.no stat.no dep.no kommune.no herad.no // no geographical names : http://www.norid.no/regelverk/vedlegg-b.en.html // 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 ic.gov.pl is.gov.pl us.gov.pl kmpsp.gov.pl kppsp.gov.pl kwpsp.gov.pl psp.gov.pl wskr.gov.pl kwp.gov.pl mw.gov.pl ug.gov.pl um.gov.pl umig.gov.pl ugim.gov.pl upow.gov.pl uw.gov.pl starostwo.gov.pl pa.gov.pl po.gov.pl psse.gov.pl pup.gov.pl rzgw.gov.pl sa.gov.pl so.gov.pl sr.gov.pl wsa.gov.pl sko.gov.pl uzs.gov.pl wiih.gov.pl winb.gov.pl pinb.gov.pl wios.gov.pl witd.gov.pl wzmiuw.gov.pl piw.gov.pl wiw.gov.pl griw.gov.pl wif.gov.pl oum.gov.pl sdn.gov.pl zp.gov.pl uppo.gov.pl mup.gov.pl wuoz.gov.pl konsulat.gov.pl oirm.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 : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.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 : http://online.dns.pt/dns/start_dns 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 : http://www.afnic.re/obtenir/chartes/nommage-re/annexe-descriptifs 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/en/domains/domens_ru/reserved/ ru ac.ru edu.ru gov.ru int.ru mil.ru test.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://www.nic.sh/registrar.html 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 net.ss org.ss // st : http://www.nic.st/html/policyrules/ st co.st com.st consulado.st edu.st embaixada.st gov.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://en.wikipedia.org/wiki/.tf 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 : https://en.wikipedia.org/wiki/.tn // http://whois.ati.tn/ tn com.tn ens.tn fin.tn gov.tn ind.tn intl.tn nat.tn net.tn org.tn info.tn perso.tn tourism.tn edunet.tn rnrt.tn rns.tn rnu.tn mincom.tn agrinet.tn defense.tn turen.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 dominic.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 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 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 ve arts.ve co.ve com.ve e12.ve edu.ve firm.ve gob.ve gov.ve info.ve int.ve mil.ve net.ve org.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.dot.vn/vnnic/vnnic/domainregistration.jsp vn com.vn net.vn org.vn edu.vn gov.vn int.vn ac.vn biz.vn info.vn name.vn pro.vn health.vn // vu : https://en.wikipedia.org/wiki/.vu // http://www.vunic.vu/ vu com.vu edu.vu net.vu org.vu // wf : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.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 : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.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--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 ею // 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--fzc2c9e2c ("Lanka", Sinhalese-Sinhala) : LK // http://nic.lk ලංකා // xn--xkc2al3hye2a ("Ilangai", Tamil) : LK // http://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 // http://www.cctld.ru/en/docs/rulesrf.php рф // 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 // 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 2019-11-20T17:10:44Z // 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 // abarth : 2015-07-30 Fiat Chrysler Automobiles N.V. abarth // 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 Minds + Machines Group Limited 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 // adac : 2015-07-16 Allgemeiner Deutscher Automobil-Club e.V. (ADAC) adac // 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 // afamilycompany : 2015-07-23 Johnson Shareholdings, Inc. afamilycompany // 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 // aigo : 2015-08-06 aigo Digital Technology Co,Ltd. aigo // 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 // alfaromeo : 2015-07-31 Fiat Chrysler Automobiles N.V. alfaromeo // 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 // 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 QIHOO 360 TECHNOLOGY 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 Afilias 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 Uniregistry, Corp. audio // auspost : 2015-08-13 Australian Postal Corporation auspost // author : 2014-12-18 Amazon Registry Services, Inc. author // auto : 2014-11-13 Cars Registry Limited auto // autos : 2014-01-09 DERAutos, LLC autos // avianca : 2015-01-08 Aerovias del Continente Americano S.A. Avianca avianca // aws : 2015-06-25 Amazon Registry Services, Inc. aws // axa : 2013-12-19 AXA SA 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 L'Oréal beauty // beer : 2014-01-09 Minds + Machines Group Limited 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 Afilias 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 Afilias Limited bio // black : 2014-01-16 Afilias Limited black // blackfriday : 2014-01-16 Uniregistry, Corp. 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 Afilias 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 DERBoats, 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 Boston TLD Management, LLC boston // bot : 2014-12-18 Amazon Registry Services, Inc. bot // boutique : 2013-11-14 Binky Moon, LLC boutique // box : 2015-11-12 .BOX 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 Dotbroker Registry Limited broker // brother : 2015-01-29 Brother Industries, Ltd. brother // brussels : 2014-02-06 DNS.be vzw brussels // budapest : 2013-11-21 Minds + Machines Group Limited budapest // bugatti : 2015-07-23 Bugatti International SA bugatti // 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 AC Webconnecting Holding B.V. cam // camera : 2013-08-27 Binky Moon, LLC camera // camp : 2013-11-07 Binky Moon, LLC camp // cancerresearch : 2014-05-15 Australian Cancer Research Foundation cancerresearch // 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 Cars Registry Limited 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 Cars Registry Limited cars // casa : 2013-11-21 Minds + Machines Group Limited casa // case : 2015-09-03 CNH Industrial N.V. case // caseih : 2015-09-03 CNH Industrial N.V. caseih // 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 // ceb : 2015-04-09 The Corporate Executive Board Company ceb // 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 DotCFD Registry Limited cfd // chanel : 2015-04-09 Chanel International B.V. chanel // channel : 2014-05-08 Charleston Road Registry Inc. channel // charity : 2018-04-11 Binky Moon, LLC 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 Uniregistry, Corp. 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 Uniregistry, Corp. 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 .CLUB DOMAINS, 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 Minds + Machines Group Limited cooking // cookingchannel : 2015-07-02 Lifestyle Domain Holdings, Inc. cookingchannel // cool : 2013-11-14 Binky Moon, LLC cool // corsica : 2014-09-25 Collectivité de Corse corsica // country : 2013-12-19 DotCountry LLC country // coupon : 2015-02-26 Amazon Registry Services, Inc. coupon // coupons : 2015-03-26 Binky Moon, LLC coupons // courses : 2014-12-04 OPEN UNIVERSITIES AUSTRALIA PTY LTD 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 CUNA Performance Resources, 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 // csc : 2014-09-25 Alliance-One Services, Inc. csc // cuisinella : 2014-04-03 SCHMIDT GROUPE S.A.S. cuisinella // cymru : 2014-05-08 Nominet UK cymru // cyou : 2015-01-22 Beijing Gamease Age Digital Technology Co., Ltd. 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 Minds + Machines Group Limited 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 Top Level Design, 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 Uniregistry, Corp. 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 // duck : 2015-07-23 Johnson Shareholdings, Inc. duck // dunlop : 2015-07-02 The Goodyear Tire & Rubber Company dunlop // dupont : 2015-06-25 E. I. du Pont de Nemours and Company 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 Co., Ltd. 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 // esurance : 2015-07-23 Esurance Insurance Company esurance // 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 Minds + Machines Group Limited 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 // fiat : 2015-07-31 Fiat Chrysler Automobiles N.V. fiat // 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 Minds + Machines Group Limited fishing // fit : 2014-11-07 Minds + Machines Group Limited fit // fitness : 2014-03-06 Binky Moon, LLC fitness // flickr : 2015-04-02 Yahoo! Domain Services 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 Uniregistry, Corp. 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 // foodnetwork : 2015-07-02 Lifestyle Domain Holdings, Inc. foodnetwork // football : 2014-12-18 Binky Moon, LLC football // ford : 2014-11-13 Ford Motor Company ford // forex : 2014-12-11 Dotforex Registry Limited forex // forsale : 2014-05-22 Dog Beach, LLC forsale // forum : 2015-04-02 Fegistry, LLC forum // foundation : 2013-12-05 Binky Moon, LLC 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 // fujixerox : 2015-07-23 Xerox DNHC LLC fujixerox // fun : 2016-01-14 DotSpace Inc. 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 Uniregistry, Corp. game // games : 2015-05-28 Dog Beach, LLC games // gap : 2015-07-31 The Gap, Inc. gap // garden : 2014-06-26 Minds + Machines Group Limited garden // gay : 2019-05-23 Top Level Design, 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 COMBELL NV 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 Dog Beach, LLC gives // giving : 2014-11-13 Giving Limited giving // glade : 2015-07-23 Johnson Shareholdings, Inc. glade // glass : 2013-11-07 Binky Moon, LLC glass // gle : 2014-07-24 Charleston Road Registry Inc. gle // global : 2014-04-17 Dot Global Domain Registry 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 Afilias 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 Uniregistry, Corp. guitars // guru : 2013-08-27 Binky Moon, LLC guru // hair : 2015-12-03 L'Oréal 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 DotHealth, LLC health // healthcare : 2014-06-12 Binky Moon, LLC healthcare // help : 2014-06-26 Uniregistry, Corp. 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 // hgtv : 2015-07-02 Lifestyle Domain Holdings, Inc. hgtv // hiphop : 2014-03-06 Uniregistry, Corp. hiphop // hisamitsu : 2015-07-16 Hisamitsu Pharmaceutical Co.,Inc. hisamitsu // hitachi : 2014-10-31 Hitachi, Ltd. hitachi // hiv : 2014-03-13 Uniregistry, Corp. 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 DERHomes, LLC homes // homesense : 2015-07-16 The TJX Companies, Inc. homesense // honda : 2014-12-18 Honda Motor Co., Ltd. honda // horse : 2013-11-21 Minds + Machines Group Limited horse // hospital : 2016-10-20 Binky Moon, LLC hospital // host : 2014-04-17 DotHost Inc. host // hosting : 2014-05-29 Uniregistry, Corp. 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 Top Level Design, 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 // intel : 2015-08-06 Intel Corporation intel // 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 // iveco : 2015-09-03 CNH Industrial N.V. iveco // jaguar : 2014-11-13 Jaguar Land Rover Ltd jaguar // java : 2014-06-19 Oracle Corporation java // jcb : 2014-11-20 JCB Co., Ltd. jcb // jcp : 2015-04-23 JCP Media, Inc. jcp // 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 Uniregistry, Corp. 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 // kim : 2013-09-23 Afilias 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 // lancia : 2015-07-31 Fiat Chrysler Automobiles N.V. lancia // lancome : 2015-07-23 L'Oréal lancome // 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 ECOM-LAC Federaciòn de Latinoamèrica y el Caribe para Internet y el Comercio Electrònico lat // latino : 2015-07-30 Dish DBS Corporation latino // latrobe : 2014-06-16 La Trobe University latrobe // law : 2015-01-22 LW TLD Limited law // lawyer : 2014-03-20 Dog Beach, LLC lawyer // lds : 2014-03-20 IRI Domain Management, LLC ("Applicant") 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 Afilias Limited lgbt // liaison : 2014-10-02 Liaison Technologies, Incorporated liaison // 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 // linde : 2014-12-04 Linde Aktiengesellschaft linde // link : 2013-11-14 Uniregistry, Corp. 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 // lixil : 2015-03-19 LIXIL Group Corporation lixil // llc : 2017-12-14 Afilias Limited llc // llp : 2019-08-26 Dot Registry LLC 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 // loft : 2015-07-30 Annco, Inc. loft // lol : 2015-01-30 Uniregistry, Corp. lol // london : 2013-11-14 Dot London Domains Limited london // lotte : 2014-11-07 Lotte Holdings Co., Ltd. lotte // lotto : 2014-04-10 Afilias 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 // lupin : 2014-11-07 LUPIN LIMITED lupin // luxe : 2014-01-09 Minds + Machines Group Limited luxe // luxury : 2013-10-17 Luxury Partners, LLC luxury // macys : 2015-07-31 Macys, Inc. macys // 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 L'Oréal 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 Dotmarkets Registry Limited markets // marriott : 2014-10-09 Marriott Worldwide Corporation marriott // marshalls : 2015-07-16 The TJX Companies, Inc. marshalls // maserati : 2015-07-31 Fiat Chrysler Automobiles N.V. maserati // 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 // metlife : 2015-05-07 MetLife Services and Solutions, LLC metlife // miami : 2013-12-19 Minds + Machines Group Limited 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 Co., Ltd. moe // moi : 2014-12-18 Amazon Registry Services, Inc. moi // mom : 2015-04-16 Uniregistry, Corp. 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 ("Applicant") 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 DERMotorcycles, LLC motorcycles // mov : 2014-01-30 Charleston Road Registry Inc. mov // movie : 2015-02-05 Binky Moon, LLC movie // movistar : 2014-10-16 Telefónica S.A. movistar // 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 // mutual : 2015-04-02 Northwestern Mutual MU TLD Registry, LLC mutual // nab : 2015-08-20 National Australia Bank Limited nab // nadex : 2014-12-11 Nadex Domains, Inc. nadex // nagoya : 2013-10-24 GMO Registry, Inc. nagoya // nationwide : 2015-07-23 Nationwide Mutual Insurance Company nationwide // 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 Registry Services, LLC neustar // new : 2014-01-30 Charleston Road Registry Inc. new // newholland : 2015-09-03 CNH Industrial N.V. newholland // 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 Symantec Corporation 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 Top Level Spectrum, Inc. observer // off : 2015-07-23 Johnson Shareholdings, Inc. off // office : 2015-03-12 Microsoft Corporation office // okinawa : 2013-12-05 BRregistry, Inc. okinawa // olayan : 2015-05-14 Crescent Holding GmbH olayan // olayangroup : 2015-05-14 Crescent Holding GmbH 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 I-Registry Ltd. onl // online : 2015-01-15 DotOnline Inc. online // onyourside : 2015-07-23 Nationwide Mutual Insurance Company onyourside // 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 Afilias 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 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 Afilias 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 Uniregistry, Corp. 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 Uniregistry, Corp. 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 Afilias 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 Afilias 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 DotPress Inc. 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 Afilias Limited promo // properties : 2013-12-05 Binky Moon, LLC properties // property : 2014-05-22 Uniregistry, Corp. 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 dotCOOL, Inc. qpon // quebec : 2013-12-19 PointQuébec Inc quebec // quest : 2015-03-26 XYZ.COM LLC quest // qvc : 2015-07-30 QVC, Inc. qvc // racing : 2014-12-04 Premier Registry Limited racing // radio : 2016-07-21 European Broadcasting Union (EBU) radio // raid : 2015-07-23 Johnson Shareholdings, Inc. raid // 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 Fegistry, LLC realty // recipes : 2013-10-17 Binky Moon, LLC recipes // red : 2013-11-07 Afilias 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 I-Registry Ltd. rich // richardli : 2015-05-14 Pacific Century Asset Management (HK) Limited richardli // ricoh : 2014-11-20 Ricoh Company, Ltd. ricoh // rightathome : 2015-07-23 Johnson Shareholdings, Inc. rightathome // 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 // rmit : 2015-11-19 Royal Melbourne Institute of Technology rmit // rocher : 2014-12-18 Ferrero Trading Lux S.A. rocher // rocks : 2013-11-14 Dog Beach, LLC rocks // rodeo : 2013-12-19 Minds + Machines Group Limited 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 regiodot GmbH & Co. KG 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 SPECIAL BROADCASTING SERVICE CORPORATION 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 // scjohnson : 2015-07-23 Johnson Shareholdings, Inc. scjohnson // scor : 2014-10-31 SCOR SE scor // 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 // ses : 2015-07-23 SES ses // 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 Uniregistry, Corp. 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 Afilias 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 QIHOO 360 TECHNOLOGY CO. LTD. shouji // show : 2015-03-05 Binky Moon, LLC show // showtime : 2015-08-06 CBS Domains Inc. showtime // shriram : 2014-01-23 Shriram Capital Ltd. shriram // 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 DotSite Inc. site // ski : 2015-04-09 Afilias Limited ski // skin : 2015-01-15 L'Oréal 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 des Chemins de fer Francais S N C F 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 DotSpace Inc. space // sport : 2017-11-16 Global Association of International Sports Federations (GAISF) sport // spot : 2015-02-26 Amazon Registry Services, Inc. spot // spreadbetting : 2014-12-11 Dotspreadbetting Registry Limited spreadbetting // 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 DotStore Inc. store // stream : 2016-01-08 dot Stream Limited stream // studio : 2015-02-11 Dog Beach, LLC studio // study : 2014-12-11 OPEN UNIVERSITIES AUSTRALIA PTY LTD 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 Minds + Machines Group Limited 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 // swiftcover : 2015-07-23 Swiftcover Insurance Services Limited swiftcover // swiss : 2014-10-16 Swiss Confederation swiss // sydney : 2014-09-18 State of New South Wales, Department of Premier and Cabinet sydney // symantec : 2014-12-04 Symantec Corporation symantec // 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 Uniregistry, Corp. 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 Personals TLD Inc. tech // technology : 2013-09-13 Binky Moon, LLC technology // telefonica : 2014-10-16 Telefónica S.A. telefonica // 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 Accent Media Limited 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 Total SA 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 Dottrading Registry Limited trading // training : 2013-11-07 Binky Moon, LLC training // travel : Dog Beach, LLC travel // travelchannel : 2015-07-02 Lifestyle Domain Holdings, Inc. travelchannel // travelers : 2015-03-26 Travelers TLD, LLC travelers // travelersinsurance : 2015-03-26 Travelers TLD, LLC travelersinsurance // trust : 2014-10-16 NCC Group Inc. 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 DotSite Inc. 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 Minds + Machines Group Limited 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 // vistaprint : 2014-09-18 Vistaprint Limited vistaprint // 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 Minds + Machines Group Limited 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 Richemont DNS Inc. 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 DotWebsite Inc. website // wed : 2013-10-01 Atgron, Inc. wed // wedding : 2014-04-24 Minds + Machines Group Limited 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 Top Level Design, 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 Minds + Machines Group Limited 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 QIHOO 360 TECHNOLOGY 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--3oq18vl8pn36a : 2015-07-02 Volkswagen (China) Investment Co., Ltd. 大众汽车 // 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 Suhub Electronic Establishment موقع // 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 Afilias 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--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--estv75g : 2015-02-19 Industrial and Commercial Bank of China Limited 工行 // 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 Minds + Machines Group Limited 购物 // 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--jlq61u9w7b : 2015-01-08 Nokia Corporation 诺基亚 // xn--jvr189m : 2015-02-26 Amazon Registry Services, Inc. 食品 // xn--kcrx77d1x4a : 2014-11-07 Koninklijke Philips N.V. 飞利浦 // xn--kpu716f : 2014-12-22 Richemont DNS Inc. 手表 // 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 Crescent Holding GmbH العليان // 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 Internet DotTrademark Organisation Limited 招聘 // xn--p1acf : 2013-12-12 Rusnames Limited рус // xn--pbt977c : 2014-12-22 Richemont DNS Inc. 珠宝 // 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 Network 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 DERYachts, LLC yachts // yahoo : 2015-04-02 Yahoo! Domain Services Inc. yahoo // yamaxun : 2014-12-18 Amazon Registry Services, Inc. yamaxun // yandex : 2014-04-10 YANDEX, LLC yandex // yodobashi : 2014-11-20 YODOBASHI CAMERA CO.,LTD. yodobashi // yoga : 2014-05-29 Minds + Machines Group Limited 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 QIHOO 360 TECHNOLOGY 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 // Agnat sp. z o.o. : https://domena.pl // Submitted by Przemyslaw Plewa beep.pl // 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 // Altervista: https://www.altervista.org // Submitted by Carlo Cannas altervista.org // alwaysdata : https://www.alwaysdata.com // Submitted by Cyril alwaysdata.net // Amazon CloudFront : https://aws.amazon.com/cloudfront/ // Submitted by Donavan Miller cloudfront.net // Amazon Elastic Compute Cloud : https://aws.amazon.com/ec2/ // Submitted by Luke Wells *.compute.amazonaws.com *.compute-1.amazonaws.com *.compute.amazonaws.com.cn us-east-1.amazonaws.com // Amazon Elastic Beanstalk : https://aws.amazon.com/elasticbeanstalk/ // Submitted by Luke Wells 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 // Amazon Elastic Load Balancing : https://aws.amazon.com/elasticloadbalancing/ // Submitted by Luke Wells *.elb.amazonaws.com *.elb.amazonaws.com.cn // Amazon S3 : https://aws.amazon.com/s3/ // Submitted by Luke Wells 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-gov-west-1.amazonaws.com s3-us-east-2.amazonaws.com s3-us-west-1.amazonaws.com s3-us-west-2.amazonaws.com s3.ap-northeast-2.amazonaws.com s3.ap-south-1.amazonaws.com s3.cn-north-1.amazonaws.com.cn s3.ca-central-1.amazonaws.com s3.eu-central-1.amazonaws.com s3.eu-west-2.amazonaws.com s3.eu-west-3.amazonaws.com s3.us-east-2.amazonaws.com s3.dualstack.ap-northeast-1.amazonaws.com s3.dualstack.ap-northeast-2.amazonaws.com s3.dualstack.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.dualstack.eu-central-1.amazonaws.com s3.dualstack.eu-west-1.amazonaws.com s3.dualstack.eu-west-2.amazonaws.com s3.dualstack.eu-west-3.amazonaws.com s3.dualstack.sa-east-1.amazonaws.com s3.dualstack.us-east-1.amazonaws.com s3.dualstack.us-east-2.amazonaws.com s3-website-us-east-1.amazonaws.com s3-website-us-west-1.amazonaws.com s3-website-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.ap-northeast-2.amazonaws.com s3-website.ap-south-1.amazonaws.com s3-website.ca-central-1.amazonaws.com s3-website.eu-central-1.amazonaws.com s3-website.eu-west-2.amazonaws.com s3-website.eu-west-3.amazonaws.com s3-website.us-east-2.amazonaws.com // Amune : https://amune.org/ // Submitted by Team Amune t3l3p0rt.net tele.amune.org // Apigee : https://apigee.com/ // Submitted by Apigee Security Team apigee.io // 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 // AVM : https://avm.de // Submitted by Andreas Weise myfritz.net // AW AdvisorWebsites.com Software Inc : https://advisorwebsites.com // Submitted by James Kennedy *.awdev.ca *.advisor.ws // 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 // Banzai Cloud // Submitted by Gabor Kozma app.banzaicloud.io // BetaInABox // Submitted by Adrian betainabox.com // BinaryLane : http://www.binarylane.com // Submitted by Nathan O'Sullivan bnr.la // Blackbaud, Inc. : https://www.blackbaud.com // Submitted by Paul Crowder blackbaudcdn.net // Boomla : https://boomla.com // Submitted by Tibor Halter boomla.net // 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 // 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 // callidomus : https://www.callidomus.com/ // Submitted by Marcus Popp mycd.eu // Carrd : https://carrd.co // Submitted by AJ carrd.co crd.co uwu.ai // CentralNic : http://www.centralnic.com/names/domains // Submitted by registry ae.org ar.com br.com cn.com com.de com.se de.com eu.com gb.com gb.net hu.com hu.net jp.net jpn.com kr.com mex.com no.com qc.com ru.com sa.com se.net uk.com uk.net us.com uy.com za.bz za.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 // 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 // c.la : http://www.c.la/ c.la // certmgr.org : https://certmgr.org // Submitted by B. Blechschmidt certmgr.org // Citrix : https://citrix.com // Submitted by Alex Stoddard xenapponazure.com // Civilized Discourse Construction Kit, Inc. : https://www.discourse.org/ // Submitted by Rishabh Nambiar discourse.group // ClearVox : http://www.clearvox.nl/ // Submitted by Leon Rowland virtueeldomein.nl // Clever Cloud : https://www.clever-cloud.com/ // Submitted by Quentin Adam cleverapps.io // Clerk : https://www.clerk.dev // Submitted by Colin Sidoti *.lcl.dev *.stg.dev // 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 Philip Langdale cloudera.site // Cloudflare, Inc. : https://www.cloudflare.com/ // Submitted by Jake Riesterer trycloudflare.com 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 // Cloudeity Inc : https://cloudeity.com // Submitted by Stefan Dimitrov cloudeity.net // CNPY : https://cnpy.gdn // Submitted by Angelo Gladding cnpy.gdn // CoDNS B.V. co.nl co.no // Combell.com : https://www.combell.com // Submitted by Thomas Wouters webhosting.be hosting-cluster.nl // 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 // cyon GmbH : https://www.cyon.ch/ // Submitted by Dominic Luechinger cyon.link cyon.site // 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 // dapps.earth : https://dapps.earth/ // Submitted by Daniil Burdakov *.dapps.earth *.bzz.dapps.earth // Debian : https://www.debian.org/ // Submitted by Peter Palfrader / Debian Sysadmin Team debian.net // deSEC : https://desec.io/ // Submitted by Peter Thomassen dedyn.io // 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 // 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 // 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 // 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 // Enalean SAS: https://www.enalean.com // Submitted by Thomas Cottier mytuleap.com // ECG Robotics, Inc: https://ecgrobotics.org // Submitted by onred.one staging.onred.one // Enonic : http://enonic.com/ // Submitted by Erik Kaareng-Sunde enonic.io customer.enonic.io // 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 // 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 // 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 // Fastly Inc. : http://www.fastly.com/ // Submitted by Fastly Security 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 // FASTVPS EESTI OU : https://fastvps.ru/ // Submitted by Likhachev Vasiliy fastpanel.direct fastvps-server.com // Featherhead : https://featherhead.xyz/ // Submitted by Simon Menke fhapp.xyz // Fedora : https://fedoraproject.org/ // submitted by Patrick Uiterwijk fedorainfracloud.org fedorapeople.org cloud.fedoraproject.org app.os.fedoraproject.org app.os.stg.fedoraproject.org // Fermax : https://fermax.com/ // submitted by Koen Van Isterdael mydobiss.com // 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 // Flynn : https://flynn.io // Submitted by Jonathan Rudenberg flynnhub.com flynnhosting.net // 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 // 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/operations/operating-servicegovuk-subdomains // Submitted by David Illsley service.gov.uk // Gehirn Inc. : https://www.gehirn.co.jp/ // Submitted by Kohei YOSHIDA gehirn.ne.jp usercontent.jp // Gentlent, Limited : https://www.gentlent.com // Submitted by Tom Klein lab.ms // GitHub, Inc. // Submitted by Patrick Toomey github.io githubusercontent.com // GitLab, Inc. // Submitted by Alex Hanselka gitlab.io // Glitch, Inc : https://glitch.com // Submitted by Mads Hartmann glitch.me // GMO Pepabo, Inc. : https://pepabo.com/ // Submitted by dojineko lolipop.io // GOV.UK Platform as a Service : https://www.cloud.service.gov.uk/ // Submitted by Tom Whitwell cloudapps.digital london.cloudapps.digital // UKHomeOffice : https://www.gov.uk/government/organisations/home-office // Submitted by Jon Shanks homeoffice.gov.uk // GlobeHosting, Inc. // Submitted by Zoltan Egresi ro.im shop.ro // 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 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 cloudfunctions.net cloud.goog codespot.com googleapis.com googlecode.com pagespeedmobilizer.com publishproxy.com withgoogle.com withyoutube.com // Hakaran group: http://hakaran.cz // Submited 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 // 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 myravendb.com ravendb.community ravendb.me development.run ravendb.run // HOSTBIP REGISTRY : https://www.hostbip.com/ // Submitted by Atanunu Igbunuroghene bpl.biz orx.biz ng.city biz.gl ng.ink col.ng firm.ng gen.ng ltd.ng ng.school sch.so // 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 // 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 // IPiFony Systems, Inc. : https://www.ipifony.com/ // Submitted by Matthew Hardeman ipifony.net // IServ GmbH : https://iserv.eu // Submitted by Kim-Alexander Brodowski mein-iserv.de test-iserv.de iserv.dev // I-O DATA DEVICE, INC. : http://www.iodata.com/ // Submitted by Yuji Minagawa iobb.net // Jino : https://www.jino.ru // Submitted by Sergey Ulyashin myjino.ru *.hosting.myjino.ru *.landing.myjino.ru *.spectrum.myjino.ru *.vps.myjino.ru // 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 // 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 // .KRD : http://nic.krd/data/krd/Registration%20Policy.pdf co.krd edu.krd // 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 // Linki Tools UG : https://linki.tools // Submitted by Paulo Matos linkitools.space // 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 // LiquidNet Ltd : http://www.liquidnetlimited.com/ // Submitted by Victor Velchev we.bs // Log'in Line : https://www.loginline.com/ // Submitted by Rémi Mach loginline.app loginline.dev loginline.io loginline.services loginline.site // 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 uklugs.org 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.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 // Memset hosting : https://www.memset.com // Submitted by Tom Whitwell miniserver.com memset.net // 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 Justin Luk azurecontainer.io azurewebsites.net azure-mobile.net cloudapp.net // 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 // Nabu Casa : https://www.nabucasa.com // Submitted by Paulus Schoutsen ui.nabu.casa // Names.of.London : https://names.of.london/ // Submitted by James Stevens or pony.club of.fashion on.fashion of.football in.london of.london for.men and.mom for.mom for.one for.sale of.work to.work // NCTU.ME : https://nctu.me/ // Submitted by Tocknicsu nctu.me // Netlify : https://www.netlify.com // Submitted by Jessica Parsons bitballoon.com netlify.com // Neustar Inc. // Submitted by Trung Tran 4u.com // ngrok : https://ngrok.com/ // Submitted by Alan Shreve ngrok.io // 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 // 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 // Nodum B.V. : https://nodum.io/ // Submitted by Wietse Wind nodum.co nodum.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 // NymNom : https://nymnom.com/ // Submitted by Dave McCormack nom.ae nom.af nom.ai nom.al nym.by nym.bz nom.cl nym.ec nom.gd nom.ge nom.gl nym.gr nom.gt nym.gy nym.hk nom.hn nym.ie nom.im nom.ke nym.kz nym.la nym.lc nom.li nym.li nym.lt nym.lu nym.me nom.mk nym.mn nym.mx nom.nu nym.nz nym.pe nym.pt nom.pw nom.qa nym.ro nom.rs nom.si nym.sk nom.st nym.su nym.sx nom.tj nym.tw nom.ug nom.uy nom.vc nom.vg // Octopodal Solutions, LLC. : https://ulterius.io/ // Submitted by Andrew Sampson cya.gg // Omnibond Systems, LLC. : https://www.omnibond.com // Submitted by Cole Estep cloudycluster.net // One Fold Media : http://www.onefoldmedia.com/ // Submitted by Eddie Jones nid.io // OpenCraft GmbH : http://opencraft.com/ // Submitted by Sven Marnach opencraft.hosting // Opera Software, A.S.A. // Submitted by Yngve Pettersen operaunite.com // OutSystems // Submitted by Duarte Santos outsystemscloud.com // OwnProvider GmbH: http://www.ownprovider.com // Submitted by Jan Moennich ownprovider.com own.pm // 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 // .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 // 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 *.platform.sh *.platformsh.site // Port53 : https://port53.io/ // Submitted by Maximilian Schieder dyn53.io // Positive Codes Technology Company : http://co.bn/faq.html // Submitted by Zulfais co.bn // 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 // Qualifio : https://qualifio.com/ // Submitted by Xavier De Cock qualifioapp.com // 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 // 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 // Rancher Labs, Inc : https://rancher.com // Submitted by Vincent Fiduccia *.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 Mason Clayton 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 ptplus.fit wellbeingzone.co.uk // Rochester Institute of Technology : http://www.rit.edu/ // Submitted by Jennifer Herting git-pages.rit.edu // 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 // 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 // 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 // Service Online LLC : http://drs.ua/ // Submitted by Serhii Bulakh biz.ua co.ua pp.ua // ShiftEdit : https://shiftedit.net/ // Submitted by Adam Jimenez shiftedit.io // Shopblocks : http://www.shopblocks.com/ // Submitted by Alex Bowers myshopblocks.com // Shopit : https://www.shopitcommerce.com/ // Submitted by Craig McMahon shopitsite.com // 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 // Stackhero : https://www.stackhero.io // Submitted by Adrien Gillon stackhero-network.com // staticland : https://static.land // Submitted by Seth Vincent static.land dev.static.land sites.static.land // 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 // 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 // Swisscom Application Cloud: https://developer.swisscom.com // Submitted by Matthias.Winzeler applicationcloud.io scapp.io // 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 diskstation.me dscloud.biz dscloud.me dscloud.mobi dsmynas.com dsmynas.net dsmynas.org familyds.com familyds.net familyds.org i234.me myds.me synology.me vpnplus.to direct.quickconnect.to // TAIFUN Software AG : http://taifun-software.de // Submitted by Bjoern Henke taifun-dns.de // TASK geographical domains (www.task.gda.pl/uslugi/dns) gda.pl gdansk.pl gdynia.pl med.pl sopot.pl // Teckids e.V. : https://www.teckids.org // Submitted by Dominik George edugit.org // Telebit : https://telebit.cloud // Submitted by AJ ONeal telebit.app telebit.io *.telebit.xyz // The Gwiddle Foundation : https://gwiddlefoundation.org.uk // Submitted by Joshua Bayfield gwiddle.co.uk // Thingdust AG : https://thingdust.com/ // Submitted by Adrian Imboden thingdustdata.com cust.dev.thingdust.io cust.disrec.thingdust.io cust.prod.thingdust.io cust.testing.thingdust.io // Tlon.io : https://tlon.io // Submitted by Mark Staarink arvo.network azimuth.network // TownNews.com : http://www.townnews.com // Submitted by Dustin Ward 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 // 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 // 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 // United Gameserver GmbH : https://united-gameserver.de // Submitted by Stefan Schwarz virtualuser.de virtual-user.de // .US // Submitted by Ed Moore lib.de.us // VeryPositive SIA : http://very.lv // Submitted by Danko Aleksejevs 2038.io // 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 // 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 // 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 // Wikimedia Labs : https://wikitech.wikimedia.org // Submitted by Yuvi Panda wmflabs.org // 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 nohost.me noho.st // ZaNiC : http://www.za.net/ // Submitted by registry za.net za.org // Zeit, Inc. : https://zeit.domains/ // Submitted by Olli Vanhoja now.sh // 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 site.builder.nu enterprisecloud.nu // ===END PRIVATE DOMAINS=== fido2-0.8.1/fido2/rpid.py0000644000175000017500000000627213544577440014732 0ustar daindain00000000000000# 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 absolute_import, unicode_literals import os import six from six.moves.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, origin): """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 isinstance(rp_id, six.binary_type): rp_id = rp_id.decode() if not rp_id: return False if isinstance(origin, six.binary_type): origin = origin.decode() url = urlparse(origin) if url.scheme != "https": return False host = url.hostname if host == rp_id: return True if host.endswith("." + rp_id) and rp_id not in suffixes: return True return False def verify_app_id(app_id, origin): """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. """ if isinstance(app_id, six.binary_type): app_id = app_id.decode() url = urlparse(app_id) if url.scheme != "https": return False return verify_rp_id(url.hostname, origin) fido2-0.8.1/fido2/server.py0000644000175000017500000003364013565742600015274 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals from .rpid import verify_rp_id, verify_app_id from .cose import CoseKey from .ctap2 import AttestedCredentialData from .client import WEBAUTHN_TYPE from .attestation import Attestation, FidoU2FAttestation, UnsupportedAttestation from .utils import websafe_encode, websafe_decode from .webauthn import ( AttestationConveyancePreference, PublicKeyCredentialRpEntity, AuthenticatorSelectionCriteria, PublicKeyCredentialDescriptor, PublicKeyCredentialType, PublicKeyCredentialParameters, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, UserVerificationRequirement, ) import os from cryptography.hazmat.primitives import constant_time from cryptography.exceptions import InvalidSignature def _verify_origin_for_rp(rp_id): return lambda o: verify_rp_id(rp_id, o) def _default_attestations(): return [ cls() for cls in Attestation.__subclasses__() if getattr(cls, "FORMAT", "none") != "none" ] def _validata_challenge(challenge): 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, transports=None): """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): if creds is None: return None return [ to_descriptor(c) if isinstance(c, AttestedCredentialData) else PublicKeyCredentialDescriptor._wrap(c) for c in creds ] class Fido2Server(object): """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 attestation_types: (optional) List of `Attestation` subclasses to use to verify attestation. By default, all available subclasses of `Attestation` will be used, excluding the NoneAttestation format. This parameter is ignored if `attestation` is set to `none`. """ def __init__( self, rp, attestation=None, verify_origin=None, attestation_types=None ): self.rp = PublicKeyCredentialRpEntity._wrap(rp) self._verify = verify_origin or _verify_origin_for_rp(self.rp.id) self.timeout = 30000 self.attestation = AttestationConveyancePreference._wrap(attestation) self.allowed_algorithms = [ PublicKeyCredentialParameters("public-key", alg) for alg in CoseKey.supported_algorithms() ] self._attestation_types = attestation_types or _default_attestations() def register_begin( self, user, credentials=None, resident_key=None, user_verification=None, authenticator_attachment=None, challenge=None, ): """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: True to request a resident credential. :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) state = self._make_internal_state(challenge, user_verification) return ( { "publicKey": PublicKeyCredentialCreationOptions( self.rp, user, challenge, self.allowed_algorithms, self.timeout, _wrap_credentials(credentials), AuthenticatorSelectionCriteria( authenticator_attachment, resident_key, user_verification ) if any((authenticator_attachment, resident_key, user_verification)) else None, self.attestation, ) }, state, ) def register_complete(self, state, client_data, attestation_object): """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""" if client_data.get("type") != WEBAUTHN_TYPE.MAKE_CREDENTIAL: raise ValueError("Incorrect type in ClientData.") if not self._verify(client_data.get("origin")): raise ValueError("Invalid origin in ClientData.") 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): att_verifier = 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. att_verifier.verify( attestation_object.att_statement, attestation_object.auth_data, client_data.hash, ) # We simply ignore attestation if self.attestation == 'none', as not all # clients strip the attestation. return attestation_object.auth_data def authenticate_begin( self, credentials=None, user_verification=None, challenge=None ): """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) state = self._make_internal_state(challenge, user_verification) return ( { "publicKey": PublicKeyCredentialRequestOptions( challenge, self.timeout, self.rp.id, _wrap_credentials(credentials), user_verification, ) }, state, ) def authenticate_complete( self, state, credentials, credential_id, client_data, auth_data, signature ): """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.""" if client_data.get("type") != WEBAUTHN_TYPE.GET_ASSERTION: raise ValueError("Incorrect type in ClientData.") if not self._verify(client_data.get("origin")): raise ValueError("Invalid origin in ClientData.") 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.") return cred raise ValueError("Unknown credential ID.") @staticmethod def _make_internal_state(challenge, user_verification): return { "challenge": websafe_encode(challenge), "user_verification": user_verification, } 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, rp, verify_u2f_origin=None, *args, **kwargs): super(U2FFido2Server, self).__init__(rp, *args, **kwargs) kwargs["attestation_types"] = [FidoU2FAttestation()] 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( PublicKeyCredentialRpEntity(app_id, self.rp.name), *args, **kwargs ) def register_begin(self, *args, **kwargs): req, state = super(U2FFido2Server, self).register_begin(*args, **kwargs) req["publicKey"].setdefault("extensions", {})["appidExclude"] = self._app_id return req, state def authenticate_begin(self, *args, **kwargs): req, state = super(U2FFido2Server, self).authenticate_begin(*args, **kwargs) req["publicKey"].setdefault("extensions", {})["appid"] = self._app_id return req, state def authenticate_complete(self, *args, **kwargs): try: return super(U2FFido2Server, self).authenticate_complete(*args, **kwargs) except ValueError: return self._app_id_server.authenticate_complete(*args, **kwargs) fido2-0.8.1/fido2/utils.py0000644000175000017500000001074313564737350015132 0ustar daindain00000000000000# 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 base64 import urlsafe_b64decode, urlsafe_b64encode from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hmac, hashes from binascii import b2a_hex from io import BytesIO import six import struct __all__ = [ "websafe_encode", "websafe_decode", "sha256", "hmac_sha256", "bytes2int", "int2bytes", ] def sha256(data): """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, data): """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): """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(b2a_hex(value), 16) def int2bytes(value, minlen=-1): """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(bytearray(reversed(ba))) def websafe_decode(data): """Decodes a websafe-base64 encoded string (bytes or str). 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, six.text_type): data = data.encode("ascii") data += b"=" * (-len(data) % 4) return urlsafe_b64decode(data) def websafe_encode(data): """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): """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=-1): """Like BytesIO.read(), but checks the number of bytes read and raises an error if fewer bytes were read than expected. """ data = super(ByteBuffer, self).read(size) if size > 0 and len(data) != size: raise ValueError( "Not enough data to read (need: %d, had: %d)." % (size, len(data)) ) return data fido2-0.8.1/fido2/webauthn.py0000644000175000017500000001633513565742600015605 0ustar daindain00000000000000# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals from .utils import sha256 from enum import Enum, unique import six import re """ 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. """ class _StringEnum(six.text_type, Enum): @classmethod def _wrap(cls, value): if value is None: return None return cls(value) @unique class AttestationConveyancePreference(_StringEnum): NONE = "none" INDIRECT = "indirect" DIRECT = "direct" @unique class UserVerificationRequirement(_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" INTERNAL = "internal" @unique class PublicKeyCredentialType(_StringEnum): PUBLIC_KEY = "public-key" def _snake2camel(name): parts = name.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) def _camel2snake(name): s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() class _DataObject(dict): """Base class for WebAuthn data types, acting both as dict and providing attribute access to values. """ def __init__(self, **data): keys = {k: _snake2camel(k) for k in data.keys()} super(_DataObject, self).__init__( {keys[k]: v for k, v in data.items() if v is not None} ) super(_DataObject, self).__setattr__("_keys", keys) def __getattr__(self, name): if name in self._keys: return self.get(self._keys[name]) raise AttributeError( "'{}' object has no attribute '{}'".format(type(self).__name__, name) ) def __setattr__(self, name, value): if name in self._keys: self[self._keys[name]] = value else: raise AttributeError( "'{}' object has no attribute '{}'".format(type(self).__name__, name) ) def __repr__(self): return "{}({!r})".format(self.__class__.__name__, dict(self)) @classmethod def _wrap(cls, data): if data is None: return None if isinstance(data, cls): return data return cls(**{_camel2snake(k): v for k, v in data.items()}) @classmethod def _wrap_list(cls, datas): return [cls._wrap(x) for x in datas] if datas is not None else None class PublicKeyCredentialRpEntity(_DataObject): def __init__(self, id, name, icon=None): super(PublicKeyCredentialRpEntity, self).__init__(id=id, name=name, icon=icon) @property def id_hash(self): """Return SHA256 hash of the identifier.""" return sha256(self.id.encode("utf8")) class PublicKeyCredentialUserEntity(_DataObject): def __init__(self, id, name, icon=None, display_name=None): super(PublicKeyCredentialUserEntity, self).__init__( id=id, name=name, icon=icon, display_name=display_name ) class PublicKeyCredentialParameters(_DataObject): def __init__(self, type, alg): super(PublicKeyCredentialParameters, self).__init__( type=PublicKeyCredentialType(type), alg=alg ) class PublicKeyCredentialDescriptor(_DataObject): def __init__(self, type, id, transports=None): super(PublicKeyCredentialDescriptor, self).__init__( type=PublicKeyCredentialType(type), id=id, transports=transports, # Note: Type is str as in current WebAuthn draft! ) class AuthenticatorSelectionCriteria(_DataObject): def __init__( self, authenticator_attachment=None, require_resident_key=None, user_verification=None, ): super(AuthenticatorSelectionCriteria, self).__init__( authenticator_attachment=AuthenticatorAttachment._wrap( authenticator_attachment ), require_resident_key=require_resident_key, user_verification=UserVerificationRequirement._wrap(user_verification), ) class PublicKeyCredentialCreationOptions(_DataObject): def __init__( self, rp, user, challenge, pub_key_cred_params, timeout=None, exclude_credentials=None, authenticator_selection=None, attestation=None, extensions=None, ): super(PublicKeyCredentialCreationOptions, self).__init__( rp=PublicKeyCredentialRpEntity._wrap(rp), user=PublicKeyCredentialUserEntity._wrap(user), challenge=challenge, pub_key_cred_params=PublicKeyCredentialParameters._wrap_list( pub_key_cred_params ), timeout=timeout, exclude_credentials=PublicKeyCredentialDescriptor._wrap_list( exclude_credentials ), authenticator_selection=AuthenticatorSelectionCriteria._wrap( authenticator_selection ), attestation=AttestationConveyancePreference._wrap(attestation), extensions=extensions, ) class PublicKeyCredentialRequestOptions(_DataObject): def __init__( self, challenge, timeout=None, rp_id=None, allow_credentials=None, user_verification=None, extensions=None, ): super(PublicKeyCredentialRequestOptions, self).__init__( challenge=challenge, timeout=timeout, rp_id=rp_id, allow_credentials=PublicKeyCredentialDescriptor._wrap_list( allow_credentials ), user_verification=UserVerificationRequirement._wrap(user_verification), extensions=extensions, ) fido2-0.8.1/fido2/win_api.py0000644000175000017500000006216613564737350015426 0ustar daindain00000000000000# 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 absolute_import, unicode_literals from enum import IntEnum, unique from ctypes.wintypes import BOOL, DWORD, LONG, LPCWSTR, HWND from threading import Thread import ctypes PBYTE = ctypes.POINTER(ctypes.c_ubyte) # Different from wintypes.PBYTE, which is signed PCWSTR = ctypes.c_wchar_p class BytesProperty(object): """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)), ] def __init__( self, timeout, attachment, user_verification_requirement, credentials, cancellationId, ): 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) 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), ] auth_data = BytesProperty("AuthenticatorData") signature = BytesProperty("Signature") user_id = BytesProperty("UserId") 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)), ] def __init__( self, timeout, require_resident_key, attachment, user_verification_requirement, attestation_convoyence, credentials, cancellationId, ): 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) 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), ] auth_data = BytesProperty("AuthenticatorData") attestation = BytesProperty("Attestation") attestation_object = BytesProperty("AttestationObject") credential_id = BytesProperty("CredentialId") def __del__(self): WEBAUTHN.WebAuthNFreeCredentialAttestation(ctypes.byref(self)) @unique class WebAuthNUserVerificationRequirement(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 @classmethod def from_string(cls, value): return getattr(cls, value.upper().replace("-", "_")) @unique class WebAuthNAttestationConvoyancePreference(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 @classmethod def from_string(cls, value): return getattr(cls, value.upper().replace("-", "_")) @unique class WebAuthNAuthenticatorAttachment(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 @classmethod def from_string(cls, value): return getattr(cls, value.upper().replace("-", "_")) @unique class WebAuthNCTAPTransport(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 @classmethod def from_string(cls, value): return getattr(cls, value.upper().replace("-", "_")) WEBAUTHN = ctypes.windll.webauthn 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 = ctypes.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 = ctypes.HRESULT WEBAUTHN.WebAuthNAuthenticatorGetAssertion.argtypes = [ HWND, LPCWSTR, ctypes.POINTER(WebAuthNClientData), ctypes.POINTER(WebAuthNGetAssertionOptions), ctypes.POINTER(ctypes.POINTER(WebAuthNAssertion)), ] WEBAUTHN.WebAuthNAuthenticatorGetAssertion.restype = ctypes.HRESULT WEBAUTHN.WebAuthNFreeCredentialAttestation.argtypes = [ ctypes.POINTER(WebAuthNCredentialAttestation) ] WEBAUTHN.WebAuthNFreeAssertion.argtypes = [ctypes.POINTER(WebAuthNAssertion)] WEBAUTHN.WebAuthNGetCancellationId.argtypes = [ctypes.POINTER(GUID)] WEBAUTHN.WebAuthNGetCancellationId.restype = ctypes.HRESULT WEBAUTHN.WebAuthNCancelCurrentOperation.argtypes = [ctypes.POINTER(GUID)] WEBAUTHN.WebAuthNCancelCurrentOperation.restype = ctypes.HRESULT WEBAUTHN.WebAuthNGetErrorName.argtypes = [ctypes.HRESULT] WEBAUTHN.WebAuthNGetErrorName.restype = PCWSTR WEBAUTHN_STRUCT_VERSIONS = { 1: { "WebAuthNRpEntityInformation": 1, "WebAuthNUserEntityInformation": 1, "WebAuthNClientData": 1, "WebAuthNCoseCredentialParameter": 1, "WebAuthNCredential": 1, "WebAuthNCredentialEx": 1, "WebAuthNMakeCredentialOptions": 3, "WebAuthNGetAssertionOptions": 4, "WEBAUTHN_COMMON_ATTESTATION": 1, "WebAuthNCredentialAttestation": 3, "WebAuthNAssertion": 1, }, 2: {}, } def get_version(class_name): """Get version of struct. :param str class_name: Struct class name. :returns: Version of Struct to use. :rtype: int """ if class_name in WEBAUTHN_STRUCT_VERSIONS[WEBAUTHN_API_VERSION]: return WEBAUTHN_STRUCT_VERSIONS[WEBAUTHN_API_VERSION][class_name] return WEBAUTHN_STRUCT_VERSIONS[1][class_name] class CancelThread(Thread): def __init__(self, event): super(CancelThread, self).__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(object): """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 ctypes.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, ) ), 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, ) ), ctypes.byref(assertion_pointer), ) if event: t.complete() obj = assertion_pointer.contents return obj.Credential.descriptor, obj.auth_data, obj.signature, obj.user_id fido2-0.8.1/fido2/_pyu2f/0000755000175000017500000000000013566742204014613 5ustar daindain00000000000000fido2-0.8.1/fido2/_pyu2f/base.py0000644000175000017500000000522613544600444016077 0ustar daindain00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # 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. """Implement base classes for hid package. This module provides the base classes implemented by the platform-specific modules. It includes a base class for all implementations built on interacting with file-like objects. """ class HidDevice(object): """Base class for all HID devices in this package.""" @staticmethod def Enumerate(): """Enumerates all the hid devices. This function enumerates all the hid device and provides metadata for helping the client select one. Returns: A list of dictionaries of metadata. Each implementation is required to provide at least: vendor_id, product_id, product_string, usage, usage_page, and path. """ pass def __init__(self, path): """Initialize the device at path.""" pass def GetInReportDataLength(self): """Returns the max input report data length in bytes. Returns the max input report data length in bytes. This excludes the report id. """ pass def GetOutReportDataLength(self): """Returns the max output report data length in bytes. Returns the max output report data length in bytes. This excludes the report id. """ pass def Write(self, packet): """Writes packet to device. Writes the packet to the device. Args: packet: An array of integers to write to the device. Excludes the report ID. Must be equal to GetOutReportLength(). """ pass def Read(self): """Reads packet from device. Reads the packet from the device. Returns: An array of integers read from the device. Excludes the report ID. The length is equal to GetInReportDataLength(). """ pass class DeviceDescriptor(object): """Descriptor for basic attributes of the device.""" usage_page = None usage = None vendor_id = None product_id = None product_string = None path = None internal_max_in_report_len = 0 internal_max_out_report_len = 0 def ToPublicDict(self): out = {} for k, v in self.__dict__.items(): if not k.startswith('internal_'): out[k] = v return out fido2-0.8.1/fido2/_pyu2f/freebsd.py0000644000175000017500000000352513544600444016577 0ustar daindain00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # 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. """Implements raw HID interface on FreeBSD using sysctl and device files.""" from __future__ import absolute_import import os import uhid_freebsd from . import linux class FreeBSDHidDevice(linux.LinuxHidDevice): """Implementation of HID device for FreeBSD. """ @staticmethod def Enumerate(): for dev in uhid_freebsd.enumerate(): desc = linux.base.DeviceDescriptor() desc.path = dev["path"] desc.vendor_id = dev["vendor_id"] desc.product_id = dev["product_id"] desc.product_string = dev["product_desc"] fd = os.open(desc.path, os.O_RDONLY) linux.ParseReportDescriptor( uhid_freebsd.get_report_data(fd, 3), desc) os.close(fd) yield desc.ToPublicDict() def __init__(self, path): linux.base.HidDevice.__init__(self, path) self.dev = os.open(path, os.O_RDWR) self.desc = linux.base.DeviceDescriptor() self.desc.path = path linux.ParseReportDescriptor( uhid_freebsd.get_report_data(self.dev, 3), self.desc) def Write(self, packet): """See base class.""" out = bytes(bytearray([0]*64 + packet)) # 64 zero bytes (report ID) os.write(self.dev, out) fido2-0.8.1/fido2/_pyu2f/hidtransport.py0000644000175000017500000002645313544600444017713 0ustar daindain00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # 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. """HID Transport for U2F. This module imports the U2F HID Transport protocol as well as methods for discovering devices implementing this protocol. """ from __future__ import absolute_import import logging import os import struct import time import six from . import hid def HidUsageSelector(device): if device['usage_page'] == 0xf1d0 and device['usage'] == 0x01: return True return False def DiscoverLocalHIDU2FDevices(selector=HidUsageSelector): for d in hid.Enumerate(): if selector(d): try: dev = hid.Open(d['path']) yield UsbHidTransport(dev) except OSError: # Insufficient permissions to access device pass class UsbHidTransport(object): """Implements the U2FHID transport protocol. This class implements the U2FHID transport protocol from the FIDO U2F specs. This protocol manages fragmenting longer messages over a short hid frame (usually 64 bytes). It exposes an APDU channel through the MSG command as well as a series of other commands for configuring and interacting with the device. """ U2FHID_PING = 0x81 U2FHID_MSG = 0x83 U2FHID_WINK = 0x88 U2FHID_PROMPT = 0x87 U2FHID_INIT = 0x86 U2FHID_LOCK = 0x84 U2FHID_ERROR = 0xbf U2FHID_SYNC = 0xbc CTAPHID_KEEPALIVE = 0xbb U2FHID_BROADCAST_CID = bytearray([0xff, 0xff, 0xff, 0xff]) ERR_CHANNEL_BUSY = bytearray([0x06]) class InitPacket(object): """Represent an initial U2FHID packet. Represent an initial U2FHID packet. This packet contains metadata necessary to interpret the entire packet stream associated with a particular exchange (read or write). Attributes: packet_size: The size of the hid report (packet) used. Usually 64. cid: The channel id for the connection to the device. size: The size of the entire message to be sent (including all continuation packets) payload: The portion of the message to put into the init packet. This must be smaller than packet_size - 7 (the overhead for an init packet). """ def __init__(self, packet_size, cid, cmd, size, payload): self.packet_size = packet_size if len(cid) != 4 or cmd > 255 or size >= 2**16: raise OSError('Invalid packet') if len(payload) > self.packet_size - 7: raise OSError('Invalid packet') self.cid = cid # byte array self.cmd = cmd # number self.size = size # number (full size of message) self.payload = payload # byte array (for first packet) def ToWireFormat(self): """Serializes the packet.""" ret = bytearray(64) ret[0:4] = self.cid ret[4] = self.cmd struct.pack_into('>H', ret, 5, self.size) ret[7:7 + len(self.payload)] = self.payload return list(six.iterbytes(bytes(ret))) @staticmethod def FromWireFormat(packet_size, data): """Derializes the packet. Deserializes the packet from wire format. Args: packet_size: The size of all packets (usually 64) data: List of ints or bytearray containing the data from the wire. Returns: InitPacket object for specified data Raises: OSError: if the data isn't a valid InitPacket """ ba = bytearray(data) if len(ba) != packet_size: raise OSError('Invalid packet') cid = ba[0:4] cmd = ba[4] size = struct.unpack('>H', bytes(ba[5:7]))[0] payload = ba[7:7 + size] # might truncate at packet_size return UsbHidTransport.InitPacket(packet_size, cid, cmd, size, payload) class ContPacket(object): """Represents a continutation U2FHID packet. Represents a continutation U2FHID packet. These packets follow the intial packet and contains the remaining data in a particular message. Attributes: packet_size: The size of the hid report (packet) used. Usually 64. cid: The channel id for the connection to the device. seq: The sequence number for this continuation packet. The first continuation packet is 0 and it increases from there. payload: The payload to put into this continuation packet. This must be less than packet_size - 5 (the overhead of the continuation packet is 5). """ def __init__(self, packet_size, cid, seq, payload): self.packet_size = packet_size self.cid = cid self.seq = seq self.payload = payload if len(payload) > self.packet_size - 5: raise OSError('Invalid packet') if seq > 127: raise OSError('Invalid packet') def ToWireFormat(self): """Serializes the packet.""" ret = bytearray(self.packet_size) ret[0:4] = self.cid ret[4] = self.seq ret[5:5 + len(self.payload)] = self.payload return [int(x) for x in ret] @staticmethod def FromWireFormat(packet_size, data): """Derializes the packet. Deserializes the packet from wire format. Args: packet_size: The size of all packets (usually 64) data: List of ints or bytearray containing the data from the wire. Returns: InitPacket object for specified data Raises: OSError: if the data isn't a valid ContPacket """ ba = bytearray(data) if len(ba) != packet_size: raise OSError('Invalid packet') cid = ba[0:4] seq = ba[4] # We don't know the size limit a priori here without seeing the init # packet, so truncation needs to be done in the higher level protocol # handling code, unlike the degenerate case of a 1 packet message in an # init packet, where the size is known. payload = ba[5:] return UsbHidTransport.ContPacket(packet_size, cid, seq, payload) def __init__(self, hid_device, read_timeout_secs=3.0): self.hid_device = hid_device in_size = hid_device.GetInReportDataLength() out_size = hid_device.GetOutReportDataLength() if in_size != out_size: raise OSError( 'unsupported device with different in/out packet sizes.') if in_size == 0: raise OSError('unable to determine packet size') self.packet_size = in_size self.read_timeout_secs = read_timeout_secs self.logger = logging.getLogger('_pyu2f.hidtransport') self.InternalInit() def SendMsgBytes(self, msg): r = self.InternalExchange(UsbHidTransport.U2FHID_MSG, msg) return r def SendBlink(self, length): return self.InternalExchange(UsbHidTransport.U2FHID_PROMPT, bytearray([length])) def SendWink(self): return self.InternalExchange(UsbHidTransport.U2FHID_WINK, bytearray([])) def SendPing(self, data): return self.InternalExchange(UsbHidTransport.U2FHID_PING, data) def InternalInit(self): """Initializes the device and obtains channel id.""" self.cid = UsbHidTransport.U2FHID_BROADCAST_CID nonce = bytearray(os.urandom(8)) r = self.InternalExchange(UsbHidTransport.U2FHID_INIT, nonce) if len(r) < 17: raise OSError('unexpected init reply len') if r[0:8] != nonce: raise OSError('nonce mismatch') self.cid = bytearray(r[8:12]) self.u2fhid_version = r[12] self.device_version = tuple(r[13:16]) self.capabilities = r[16] def InternalExchange(self, cmd, payload_in): """Sends and receives a message from the device.""" # make a copy because we destroy it below self.logger.debug('payload: ' + str(list(payload_in))) payload = bytearray() payload[:] = payload_in for _ in range(2): self.InternalSend(cmd, payload) ret_cmd, ret_payload = self.InternalRecv() if ret_cmd == UsbHidTransport.U2FHID_ERROR: if ret_payload == UsbHidTransport.ERR_CHANNEL_BUSY: time.sleep(0.5) continue raise OSError('Device error: %d' % int(ret_payload[0])) elif ret_cmd != cmd: raise OSError('Command mismatch!') return ret_payload raise OSError('Device Busy. Please retry') def InternalSend(self, cmd, payload): """Sends a message to the device, including fragmenting it.""" length_to_send = len(payload) max_payload = self.packet_size - 7 first_frame = payload[0:max_payload] first_packet = UsbHidTransport.InitPacket(self.packet_size, self.cid, cmd, len(payload), first_frame) del payload[0:max_payload] length_to_send -= len(first_frame) self.InternalSendPacket(first_packet) seq = 0 while length_to_send > 0: max_payload = self.packet_size - 5 next_frame = payload[0:max_payload] del payload[0:max_payload] length_to_send -= len(next_frame) next_packet = UsbHidTransport.ContPacket(self.packet_size, self.cid, seq, next_frame) self.InternalSendPacket(next_packet) seq += 1 def InternalSendPacket(self, packet): wire = packet.ToWireFormat() self.logger.debug('sending packet: ' + str(wire)) self.hid_device.Write(wire) def InternalReadFrame(self): # TODO(gdasher): Figure out timeouts. Today, this implementation # blocks forever at the HID level waiting for a response to a report. # This may not be reasonable behavior (though in practice in seems to be # OK on the set of devices and machines tested so far). frame = self.hid_device.Read() self.logger.debug('recv: ' + str(frame)) return frame def InternalRecv(self): """Receives a message from the device, including defragmenting it.""" first_packet_read = False while not first_packet_read: first_read = self.InternalReadFrame() first_packet = UsbHidTransport.InitPacket.FromWireFormat( self.packet_size, first_read) first_packet_read = self.cid == first_packet.cid data = first_packet.payload to_read = first_packet.size - len(first_packet.payload) seq = 0 while to_read > 0: next_read = self.InternalReadFrame() next_packet = UsbHidTransport.ContPacket.FromWireFormat(self.packet_size, next_read) if self.cid != next_packet.cid: # Skip over packets that are for communication with other clients. # HID is broadcast, so we see potentially all communication from the # device. For well-behaved devices, these should be BUSY messages # sent to other clients of the device because at this point we're # in mid-message transit. continue if seq != next_packet.seq: raise OSError('Packets received out of order') # This packet for us at this point, so debit it against our # balance of bytes to read. to_read -= len(next_packet.payload) data.extend(next_packet.payload) seq += 1 # truncate incomplete frames data = data[0:first_packet.size] return (first_packet.cmd, data) fido2-0.8.1/fido2/_pyu2f/linux.py0000644000175000017500000001553213544600444016325 0ustar daindain00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # 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. """Implements raw HID interface on Linux using SysFS and device files.""" from __future__ import absolute_import import os import struct import six from . import base REPORT_DESCRIPTOR_KEY_MASK = 0xfc LONG_ITEM_ENCODING = 0xfe OUTPUT_ITEM = 0x90 INPUT_ITEM = 0x80 COLLECTION_ITEM = 0xa0 REPORT_COUNT = 0x94 REPORT_SIZE = 0x74 USAGE_PAGE = 0x04 USAGE = 0x08 def GetValueLength(rd, pos): """Get value length for a key in rd. For a key at position pos in the Report Descriptor rd, return the length of the associated value. This supports both short and long format values. Args: rd: Report Descriptor pos: The position of the key in rd. Returns: (key_size, data_len) where key_size is the number of bytes occupied by the key and data_len is the length of the value associated by the key. """ key = six.indexbytes(rd, pos) if key == LONG_ITEM_ENCODING: # If the key is tagged as a long item (0xfe), then the format is # [key (1 byte)] [data len (1 byte)] [item tag (1 byte)] [data (n # bytes)]. # Thus, the entire key record is 3 bytes long. if pos + 1 < len(rd): return (3, rd[pos + 1]) else: raise OSError('Malformed report descriptor') else: # If the key is tagged as a short item, then the item tag and data len are # packed into one byte. The format is thus: # [tag (high 4 bits)] [type (2 bits)] [size code (2 bits)] [data (n bytes)]. # The size code specifies 1,2, or 4 bytes (0x03 means 4 bytes). code = key & 0x03 if code <= 0x02: return (1, code) elif code == 0x03: return (1, 4) raise OSError('Cannot happen') def ReadLsbBytes(rd, offset, value_size): """Reads value_size bytes from rd at offset, least signifcant byte first.""" encoding = None if value_size == 1: encoding = '= pos + 1 + value_length: report_count = ReadLsbBytes(rd, pos + 1, value_length) elif key & REPORT_DESCRIPTOR_KEY_MASK == REPORT_SIZE: if len(rd) >= pos + 1 + value_length: report_size = ReadLsbBytes(rd, pos + 1, value_length) elif key & REPORT_DESCRIPTOR_KEY_MASK == USAGE_PAGE: if len(rd) >= pos + 1 + value_length: usage_page = ReadLsbBytes(rd, pos + 1, value_length) elif key & REPORT_DESCRIPTOR_KEY_MASK == USAGE: if len(rd) >= pos + 1 + value_length: usage = ReadLsbBytes(rd, pos + 1, value_length) pos += value_length + key_size return desc def ParseUevent(uevent, desc): lines = uevent.split(b'\n') for line in lines: line = line.strip() if not line: continue k, v = line.split(b'=') if k == b'HID_NAME': desc.product_string = v.decode('utf8') elif k == b'HID_ID': _, vid, pid = v.split(b':') desc.vendor_id = int(vid, 16) desc.product_id = int(pid, 16) class LinuxHidDevice(base.HidDevice): """Implementation of HID device for linux. Implementation of HID device interface for linux that uses block devices to interact with the device and sysfs to enumerate/discover device metadata. """ @staticmethod def Enumerate(): for hidraw in os.listdir('/sys/class/hidraw'): rd_path = ( os.path.join( '/sys/class/hidraw', hidraw, 'device/report_descriptor')) uevent_path = os.path.join('/sys/class/hidraw', hidraw, 'device/uevent') rd_file = open(rd_path, 'rb') uevent_file = open(uevent_path, 'rb') desc = base.DeviceDescriptor() desc.path = os.path.join('/dev/', hidraw) ParseReportDescriptor(rd_file.read(), desc) ParseUevent(uevent_file.read(), desc) rd_file.close() uevent_file.close() yield desc.ToPublicDict() def __init__(self, path): base.HidDevice.__init__(self, path) self.dev = os.open(path, os.O_RDWR) self.desc = base.DeviceDescriptor() self.desc.path = path rd_file = open(os.path.join('/sys/class/hidraw', os.path.basename(path), 'device/report_descriptor'), 'rb') ParseReportDescriptor(rd_file.read(), self.desc) rd_file.close() def GetInReportDataLength(self): """See base class.""" return self.desc.internal_max_in_report_len def GetOutReportDataLength(self): """See base class.""" return self.desc.internal_max_out_report_len def Write(self, packet): """See base class.""" out = bytes(bytearray([0] + packet)) # Prepend the zero-byte (report ID) os.write(self.dev, out) def Read(self): """See base class.""" raw_in = os.read(self.dev, self.GetInReportDataLength()) decoded_in = list(six.iterbytes(raw_in)) return decoded_in fido2-0.8.1/fido2/_pyu2f/macos.py0000644000175000017500000003713513544600444016273 0ustar daindain00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # 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. """Implements HID device interface on MacOS using IOKit and HIDManager.""" from __future__ import absolute_import import ctypes import ctypes.util import logging from six.moves.queue import Queue, Empty import sys import threading from . import base logger = logging.getLogger('_pyu2f.macos') # Constants DEVICE_PATH_BUFFER_SIZE = 512 DEVICE_STRING_PROPERTY_BUFFER_SIZE = 512 HID_DEVICE_PROPERTY_VENDOR_ID = b'VendorID' HID_DEVICE_PROPERTY_PRODUCT_ID = b'ProductID' HID_DEVICE_PROPERTY_PRODUCT = b'Product' HID_DEVICE_PROPERTY_PRIMARY_USAGE = b'PrimaryUsage' HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE = b'PrimaryUsagePage' HID_DEVICE_PROPERTY_MAX_INPUT_REPORT_SIZE = b'MaxInputReportSize' HID_DEVICE_PROPERTY_MAX_OUTPUT_REPORT_SIZE = b'MaxOutputReportSize' HID_DEVICE_PROPERTY_REPORT_ID = b'ReportID' # Declare C types class _CFType(ctypes.Structure): pass class _CFString(_CFType): pass class _CFSet(_CFType): pass class _IOHIDManager(_CFType): pass class _IOHIDDevice(_CFType): pass class _CFRunLoop(_CFType): pass class _CFAllocator(_CFType): pass # Linter isn't consistent about valid class names. Disabling some of the errors CF_SET_REF = ctypes.POINTER(_CFSet) CF_STRING_REF = ctypes.POINTER(_CFString) CF_TYPE_REF = ctypes.POINTER(_CFType) CF_RUN_LOOP_REF = ctypes.POINTER(_CFRunLoop) CF_RUN_LOOP_RUN_RESULT = ctypes.c_int32 CF_ALLOCATOR_REF = ctypes.POINTER(_CFAllocator) CF_TYPE_ID = ctypes.c_ulong # pylint: disable=invalid-name CF_INDEX = ctypes.c_long # pylint: disable=invalid-name CF_TIME_INTERVAL = ctypes.c_double # pylint: disable=invalid-name IO_RETURN = ctypes.c_uint IO_HID_REPORT_TYPE = ctypes.c_uint IO_OBJECT_T = ctypes.c_uint MACH_PORT_T = ctypes.c_uint IO_STRING_T = ctypes.c_char_p # pylint: disable=invalid-name IO_SERVICE_T = IO_OBJECT_T IO_REGISTRY_ENTRY_T = IO_OBJECT_T IO_HID_MANAGER_REF = ctypes.POINTER(_IOHIDManager) IO_HID_DEVICE_REF = ctypes.POINTER(_IOHIDDevice) IO_HID_REPORT_CALLBACK = ctypes.CFUNCTYPE(None, ctypes.py_object, IO_RETURN, ctypes.c_void_p, IO_HID_REPORT_TYPE, ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint8), CF_INDEX) IO_HID_CALLBACK = ctypes.CFUNCTYPE(None, ctypes.py_object, IO_RETURN, ctypes.c_void_p) # Define C constants K_CF_NUMBER_SINT32_TYPE = 3 K_CF_STRING_ENCODING_UTF8 = 0x08000100 K_CF_ALLOCATOR_DEFAULT = None K_IO_SERVICE_PLANE = b'IOService' K_IO_MASTER_PORT_DEFAULT = 0 K_IO_HID_REPORT_TYPE_OUTPUT = 1 K_IO_RETURN_SUCCESS = 0 K_CF_RUN_LOOP_RUN_STOPPED = 2 K_CF_RUN_LOOP_RUN_TIMED_OUT = 3 K_CF_RUN_LOOP_RUN_HANDLED_SOURCE = 4 iokit = None cf = None # Only use iokit and cf if we're on macos, this way we can still run tests # on other platforms if we properly mock if sys.platform.startswith('darwin'): # Load relevant libraries iokit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('IOKit')) cf = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation')) # Exported constants K_CF_RUNLOOP_DEFAULT_MODE = CF_STRING_REF.in_dll(cf, 'kCFRunLoopDefaultMode') # Declare C function prototypes cf.CFSetGetValues.restype = None cf.CFSetGetValues.argtypes = [CF_SET_REF, ctypes.POINTER(ctypes.c_void_p)] cf.CFStringCreateWithCString.restype = CF_STRING_REF cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32] cf.CFStringGetCString.restype = ctypes.c_int cf.CFStringGetCString.argtypes = [CF_STRING_REF, ctypes.c_char_p, CF_INDEX, ctypes.c_uint32] cf.CFStringGetLength.restype = CF_INDEX cf.CFStringGetLength.argtypes = [CF_STRING_REF] cf.CFGetTypeID.restype = CF_TYPE_ID cf.CFGetTypeID.argtypes = [CF_TYPE_REF] cf.CFNumberGetTypeID.restype = CF_TYPE_ID cf.CFNumberGetValue.restype = ctypes.c_int cf.CFRelease.restype = None cf.CFRelease.argtypes = [CF_TYPE_REF] cf.CFRunLoopGetCurrent.restype = CF_RUN_LOOP_REF cf.CFRunLoopGetCurrent.argTypes = [] cf.CFRunLoopRunInMode.restype = CF_RUN_LOOP_RUN_RESULT cf.CFRunLoopRunInMode.argtypes = [CF_STRING_REF, CF_TIME_INTERVAL, ctypes.c_bool] iokit.IOObjectRelease.argtypes = [IO_OBJECT_T] iokit.IOHIDManagerCreate.restype = IO_HID_MANAGER_REF iokit.IOHIDManagerCopyDevices.restype = CF_SET_REF iokit.IOHIDManagerCopyDevices.argtypes = [IO_HID_MANAGER_REF] iokit.IOHIDManagerSetDeviceMatching.restype = None iokit.IOHIDManagerSetDeviceMatching.argtypes = [IO_HID_MANAGER_REF, CF_TYPE_REF] iokit.IOHIDDeviceGetProperty.restype = CF_TYPE_REF iokit.IOHIDDeviceGetProperty.argtypes = [IO_HID_DEVICE_REF, CF_STRING_REF] iokit.IOHIDDeviceRegisterRemovalCallback.restype = None iokit.IOHIDDeviceRegisterRemovalCallback.argtypes = [ IO_HID_DEVICE_REF, IO_HID_CALLBACK, ctypes.py_object] iokit.IOHIDDeviceRegisterInputReportCallback.restype = None iokit.IOHIDDeviceRegisterInputReportCallback.argtypes = [ IO_HID_DEVICE_REF, ctypes.POINTER(ctypes.c_uint8), CF_INDEX, IO_HID_REPORT_CALLBACK, ctypes.py_object] iokit.IORegistryEntryFromPath.restype = IO_REGISTRY_ENTRY_T iokit.IORegistryEntryFromPath.argtypes = [MACH_PORT_T, IO_STRING_T] iokit.IOHIDDeviceCreate.restype = IO_HID_DEVICE_REF iokit.IOHIDDeviceCreate.argtypes = [CF_ALLOCATOR_REF, IO_SERVICE_T] iokit.IOHIDDeviceScheduleWithRunLoop.restype = None iokit.IOHIDDeviceScheduleWithRunLoop.argtypes = [IO_HID_DEVICE_REF, CF_RUN_LOOP_REF, CF_STRING_REF] iokit.IOHIDDeviceUnscheduleFromRunLoop.restype = None iokit.IOHIDDeviceUnscheduleFromRunLoop.argtypes = [IO_HID_DEVICE_REF, CF_RUN_LOOP_REF, CF_STRING_REF] iokit.IOHIDDeviceSetReport.restype = IO_RETURN iokit.IOHIDDeviceSetReport.argtypes = [IO_HID_DEVICE_REF, IO_HID_REPORT_TYPE, CF_INDEX, ctypes.POINTER(ctypes.c_uint8), CF_INDEX] else: logger.warn('Not running on MacOS') def CFStr(s): """Builds a CFString from a python string. Args: s: source string Returns: CFStringRef representation of the source string Resulting CFString must be CFReleased when no longer needed. """ return cf.CFStringCreateWithCString(None, s, 0) def GetDeviceIntProperty(dev_ref, key): """Reads int property from the HID device.""" cf_key = CFStr(key) type_ref = iokit.IOHIDDeviceGetProperty(dev_ref, cf_key) cf.CFRelease(cf_key) if not type_ref: return None if cf.CFGetTypeID(type_ref) != cf.CFNumberGetTypeID(): raise OSError('Expected number type, got {}'.format( cf.CFGetTypeID(type_ref))) out = ctypes.c_int32() ret = cf.CFNumberGetValue(type_ref, K_CF_NUMBER_SINT32_TYPE, ctypes.byref(out)) if not ret: return None return out.value def GetDeviceStringProperty(dev_ref, key): """Reads string property from the HID device.""" cf_key = CFStr(key) type_ref = iokit.IOHIDDeviceGetProperty(dev_ref, cf_key) cf.CFRelease(cf_key) if not type_ref: return None if cf.CFGetTypeID(type_ref) != cf.CFStringGetTypeID(): raise OSError('Expected string type, got {}'.format( cf.CFGetTypeID(type_ref))) type_ref = ctypes.cast(type_ref, CF_STRING_REF) out = ctypes.create_string_buffer(DEVICE_STRING_PROPERTY_BUFFER_SIZE) ret = cf.CFStringGetCString(type_ref, out, DEVICE_STRING_PROPERTY_BUFFER_SIZE, K_CF_STRING_ENCODING_UTF8) if not ret: return None return out.value.decode('utf8') def GetDevicePath(device_handle): """Obtains the unique path for the device. Args: device_handle: reference to the device Returns: A unique path for the device, obtained from the IO Registry """ # Obtain device path from IO Registry io_service_obj = iokit.IOHIDDeviceGetService(device_handle) str_buffer = ctypes.create_string_buffer(DEVICE_PATH_BUFFER_SIZE) iokit.IORegistryEntryGetPath(io_service_obj, K_IO_SERVICE_PLANE, str_buffer) return str_buffer.value def HidReadCallback(read_queue, result, sender, report_type, report_id, report, report_length): """Handles incoming IN report from HID device.""" del result, sender, report_type, report_id # Unused by the callback function incoming_bytes = [report[i] for i in range(report_length)] read_queue.put(incoming_bytes) # C wrapper around ReadCallback() # Declared in this scope so it doesn't get GC-ed REGISTERED_READ_CALLBACK = IO_HID_REPORT_CALLBACK(HidReadCallback) def HidRemovalCallback(hid_device, result, sender): del result, sender cf.CFRunLoopStop(hid_device.run_loop_ref) REMOVAL_CALLBACK = IO_HID_CALLBACK(HidRemovalCallback) def DeviceReadThread(hid_device): """Binds a device to the thread's run loop, then starts the run loop. Args: hid_device: The MacOsHidDevice object The HID manager requires a run loop to handle Report reads. This thread function serves that purpose. """ # Schedule device events with run loop hid_device.run_loop_ref = cf.CFRunLoopGetCurrent() if not hid_device.run_loop_ref: logger.error('Failed to get current run loop') return iokit.IOHIDDeviceScheduleWithRunLoop(hid_device.device_handle, hid_device.run_loop_ref, K_CF_RUNLOOP_DEFAULT_MODE) iokit.IOHIDDeviceRegisterRemovalCallback( hid_device.device_handle, REMOVAL_CALLBACK, ctypes.py_object(hid_device)) # Run the run loop run_loop_run_result = cf.CFRunLoopRunInMode( K_CF_RUNLOOP_DEFAULT_MODE, 4, # Timeout in seconds True) # Return after source handled # log any unexpected run loop exit if run_loop_run_result != K_CF_RUN_LOOP_RUN_HANDLED_SOURCE: logger.error('Unexpected run loop exit code: %d', run_loop_run_result) # Unschedule from run loop iokit.IOHIDDeviceUnscheduleFromRunLoop(hid_device.device_handle, hid_device.run_loop_ref, K_CF_RUNLOOP_DEFAULT_MODE) class MacOsHidDevice(base.HidDevice): """Implementation of HID device for MacOS. Uses IOKit HID Manager to interact with the device. """ @staticmethod def Enumerate(): """See base class.""" # Init a HID manager hid_mgr = iokit.IOHIDManagerCreate(None, None) if not hid_mgr: raise OSError('Unable to obtain HID manager reference') iokit.IOHIDManagerSetDeviceMatching(hid_mgr, None) # Get devices from HID manager device_set_ref = iokit.IOHIDManagerCopyDevices(hid_mgr) if not device_set_ref: raise OSError('Failed to obtain devices from HID manager') num = iokit.CFSetGetCount(device_set_ref) devices = (IO_HID_DEVICE_REF * num)() iokit.CFSetGetValues(device_set_ref, devices) # Retrieve and build descriptor dictionaries for each device descriptors = [] for dev in devices: d = base.DeviceDescriptor() d.vendor_id = GetDeviceIntProperty(dev, HID_DEVICE_PROPERTY_VENDOR_ID) d.product_id = GetDeviceIntProperty(dev, HID_DEVICE_PROPERTY_PRODUCT_ID) d.product_string = GetDeviceStringProperty(dev, HID_DEVICE_PROPERTY_PRODUCT) d.usage = GetDeviceIntProperty(dev, HID_DEVICE_PROPERTY_PRIMARY_USAGE) d.usage_page = GetDeviceIntProperty( dev, HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE) d.report_id = GetDeviceIntProperty(dev, HID_DEVICE_PROPERTY_REPORT_ID) d.path = GetDevicePath(dev) descriptors.append(d.ToPublicDict()) # Clean up CF objects cf.CFRelease(device_set_ref) cf.CFRelease(hid_mgr) return descriptors def __init__(self, path): # Resolve the path to device handle device_entry = iokit.IORegistryEntryFromPath(K_IO_MASTER_PORT_DEFAULT, path) if not device_entry: raise OSError('Device path does not match any HID device on ' 'the system') self.device_handle = iokit.IOHIDDeviceCreate(K_CF_ALLOCATOR_DEFAULT, device_entry) if not self.device_handle: raise OSError('Failed to obtain device handle from registry ' 'entry') iokit.IOObjectRelease(device_entry) self.device_path = path # Open device result = iokit.IOHIDDeviceOpen(self.device_handle, 0) if result != K_IO_RETURN_SUCCESS: raise OSError('Failed to open device for communication: {}' .format(result)) # Create read queue self.read_queue = Queue() # Create and start read thread self.run_loop_ref = None # Read max report sizes for in/out self.internal_max_in_report_len = GetDeviceIntProperty( self.device_handle, HID_DEVICE_PROPERTY_MAX_INPUT_REPORT_SIZE) if not self.internal_max_in_report_len: raise OSError('Unable to obtain max in report size') self.internal_max_out_report_len = GetDeviceIntProperty( self.device_handle, HID_DEVICE_PROPERTY_MAX_OUTPUT_REPORT_SIZE) if not self.internal_max_out_report_len: raise OSError('Unable to obtain max out report size') # Register read callback self.in_report_buffer = (ctypes.c_uint8 * self.internal_max_in_report_len)() iokit.IOHIDDeviceRegisterInputReportCallback( self.device_handle, self.in_report_buffer, self.internal_max_in_report_len, REGISTERED_READ_CALLBACK, ctypes.py_object(self.read_queue)) def GetInReportDataLength(self): """See base class.""" return self.internal_max_in_report_len def GetOutReportDataLength(self): """See base class.""" return self.internal_max_out_report_len def Write(self, packet): """See base class.""" report_id = 0 out_report_buffer = (ctypes.c_uint8 * self.internal_max_out_report_len)() out_report_buffer[:] = packet[:] result = iokit.IOHIDDeviceSetReport(self.device_handle, K_IO_HID_REPORT_TYPE_OUTPUT, report_id, out_report_buffer, self.internal_max_out_report_len) # Non-zero status indicates failure if result != K_IO_RETURN_SUCCESS: raise OSError('Failed to write report to device') def Read(self): """See base class.""" read_thread = threading.Thread(target=DeviceReadThread, args=(self,)) read_thread.start() read_thread.join() try: return self.read_queue.get(False) except Empty: raise OSError('Failed reading a response') def __del__(self): # Unregister the callback iokit.IOHIDDeviceRegisterInputReportCallback( self.device_handle, self.in_report_buffer, self.internal_max_in_report_len, ctypes.cast(0, IO_HID_REPORT_CALLBACK), None) fido2-0.8.1/fido2/_pyu2f/windows.py0000644000175000017500000003036713544600444016663 0ustar daindain00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # 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. """Implements raw HID device communication on Windows.""" from __future__ import absolute_import import ctypes from ctypes import wintypes import platform from . import base # Load relevant DLLs hid = ctypes.windll.Hid setupapi = ctypes.windll.SetupAPI kernel32 = ctypes.windll.Kernel32 # Various structs that are used in the Windows APIs we call class GUID(ctypes.Structure): _fields_ = [('Data1', ctypes.c_ulong), ('Data2', ctypes.c_ushort), ('Data3', ctypes.c_ushort), ('Data4', ctypes.c_ubyte * 8)] # On Windows, SetupAPI.h packs structures differently in 64bit and # 32bit mode. In 64bit mode, thestructures are packed on 8 byte # boundaries, while in 32bit mode, they are packed on 1 byte boundaries. # This is important to get right for some API calls that fill out these # structures. if platform.architecture()[0] == '64bit': SETUPAPI_PACK = 8 elif platform.architecture()[0] == '32bit': SETUPAPI_PACK = 1 else: raise OSError('Unknown architecture: %s' % platform.architecture()[0]) class DeviceInterfaceData(ctypes.Structure): _fields_ = [('cbSize', wintypes.DWORD), ('InterfaceClassGuid', GUID), ('Flags', wintypes.DWORD), ('Reserved', ctypes.POINTER(ctypes.c_ulong))] _pack_ = SETUPAPI_PACK class DeviceInterfaceDetailData(ctypes.Structure): _fields_ = [('cbSize', wintypes.DWORD), ('DevicePath', ctypes.c_byte * 1)] _pack_ = SETUPAPI_PACK class HidAttributes(ctypes.Structure): _fields_ = [('Size', ctypes.c_ulong), ('VendorID', ctypes.c_ushort), ('ProductID', ctypes.c_ushort), ('VersionNumber', ctypes.c_ushort)] class HidCapabilities(ctypes.Structure): _fields_ = [('Usage', ctypes.c_ushort), ('UsagePage', ctypes.c_ushort), ('InputReportByteLength', ctypes.c_ushort), ('OutputReportByteLength', ctypes.c_ushort), ('FeatureReportByteLength', ctypes.c_ushort), ('Reserved', ctypes.c_ushort * 17), ('NotUsed', ctypes.c_ushort * 10)] # Various void* aliases for readability. HDEVINFO = ctypes.c_void_p HANDLE = ctypes.c_void_p PHIDP_PREPARSED_DATA = ctypes.c_void_p # pylint: disable=invalid-name # This is a HANDLE. INVALID_HANDLE_VALUE = 0xffffffff # Status codes NTSTATUS = ctypes.c_long HIDP_STATUS_SUCCESS = 0x00110000 FILE_SHARE_READ = 0x00000001 FILE_SHARE_WRITE = 0x00000002 OPEN_EXISTING = 0x03 ERROR_ACCESS_DENIED = 0x05 # CreateFile Flags GENERIC_WRITE = 0x40000000 GENERIC_READ = 0x80000000 # Function signatures hid.HidD_GetHidGuid.restype = None hid.HidD_GetHidGuid.argtypes = [ctypes.POINTER(GUID)] hid.HidD_GetAttributes.restype = wintypes.BOOLEAN hid.HidD_GetAttributes.argtypes = [HANDLE, ctypes.POINTER(HidAttributes)] hid.HidD_GetPreparsedData.restype = wintypes.BOOLEAN hid.HidD_GetPreparsedData.argtypes = [HANDLE, ctypes.POINTER(PHIDP_PREPARSED_DATA)] hid.HidD_FreePreparsedData.restype = wintypes.BOOLEAN hid.HidD_FreePreparsedData.argtypes = [PHIDP_PREPARSED_DATA] hid.HidD_GetProductString.restype = wintypes.BOOLEAN hid.HidD_GetProductString.argtypes = [HANDLE, ctypes.c_void_p, ctypes.c_ulong] hid.HidP_GetCaps.restype = NTSTATUS hid.HidP_GetCaps.argtypes = [PHIDP_PREPARSED_DATA, ctypes.POINTER(HidCapabilities)] setupapi.SetupDiGetClassDevsA.argtypes = [ctypes.POINTER(GUID), ctypes.c_char_p, wintypes.HWND, wintypes.DWORD] setupapi.SetupDiGetClassDevsA.restype = HDEVINFO setupapi.SetupDiEnumDeviceInterfaces.restype = wintypes.BOOL setupapi.SetupDiEnumDeviceInterfaces.argtypes = [ HDEVINFO, ctypes.c_void_p, ctypes.POINTER(GUID), wintypes.DWORD, ctypes.POINTER(DeviceInterfaceData)] setupapi.SetupDiGetDeviceInterfaceDetailA.restype = wintypes.BOOL setupapi.SetupDiGetDeviceInterfaceDetailA.argtypes = [ HDEVINFO, ctypes.POINTER(DeviceInterfaceData), ctypes.POINTER(DeviceInterfaceDetailData), wintypes.DWORD, ctypes.POINTER(wintypes.DWORD), ctypes.c_void_p] kernel32.CreateFileA.restype = HANDLE kernel32.CreateFileA.argtypes = [ ctypes.c_char_p, wintypes.DWORD, wintypes.DWORD, ctypes.c_void_p, wintypes.DWORD, wintypes.DWORD, HANDLE] kernel32.CloseHandle.restype = wintypes.BOOL kernel32.CloseHandle.argtypes = [HANDLE] kernel32.ReadFile.restype = wintypes.BOOL kernel32.ReadFile.argtypes = [ HANDLE, ctypes.c_void_p, wintypes.DWORD, ctypes.POINTER(wintypes.DWORD), ctypes.c_void_p] kernel32.WriteFile.restype = wintypes.BOOL kernel32.WriteFile.argtypes = [ HANDLE, ctypes.c_void_p, wintypes.DWORD, ctypes.POINTER(wintypes.DWORD), ctypes.c_void_p] def FillDeviceAttributes(device, descriptor): """Fill out the attributes of the device. Fills the devices HidAttributes and product string into the descriptor. Args: device: A handle to the open device descriptor: The DeviceDescriptor to populate with the attributes. Returns: None Raises: WindowsError when unable to obtain attributes or product string. """ attributes = HidAttributes() result = hid.HidD_GetAttributes(device, ctypes.byref(attributes)) if not result: raise ctypes.WinError() buf = ctypes.create_string_buffer(1024) result = hid.HidD_GetProductString(device, buf, 1024) if not result: raise ctypes.WinError() descriptor.vendor_id = attributes.VendorID descriptor.product_id = attributes.ProductID descriptor.product_string = ctypes.wstring_at(buf) def FillDeviceCapabilities(device, descriptor): """Fill out device capabilities. Fills the HidCapabilitites of the device into descriptor. Args: device: A handle to the open device descriptor: DeviceDescriptor to populate with the capabilities Returns: none Raises: WindowsError when unable to obtain capabilitites. """ preparsed_data = PHIDP_PREPARSED_DATA(0) ret = hid.HidD_GetPreparsedData(device, ctypes.byref(preparsed_data)) if not ret: raise ctypes.WinError() try: caps = HidCapabilities() ret = hid.HidP_GetCaps(preparsed_data, ctypes.byref(caps)) if ret != HIDP_STATUS_SUCCESS: raise ctypes.WinError() descriptor.usage = caps.Usage descriptor.usage_page = caps.UsagePage descriptor.internal_max_in_report_len = caps.InputReportByteLength descriptor.internal_max_out_report_len = caps.OutputReportByteLength finally: hid.HidD_FreePreparsedData(preparsed_data) # The python os.open() implementation uses the windows libc # open() function, which writes CreateFile but does so in a way # that doesn't let us open the device with the right set of permissions. # Therefore, we have to directly use the Windows API calls. # We could use PyWin32, which provides simple wrappers. However, to avoid # requiring a PyWin32 dependency for clients, we simply also implement it # using ctypes. def OpenDevice(path, enum=False): """Open the device and return a handle to it.""" desired_access = GENERIC_WRITE | GENERIC_READ share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE if enum: desired_access = 0 h = kernel32.CreateFileA(path, desired_access, share_mode, None, OPEN_EXISTING, 0, None) if h == INVALID_HANDLE_VALUE: raise ctypes.WinError() return h class WindowsHidDevice(base.HidDevice): """Implementation of raw HID interface on Windows.""" @staticmethod def Enumerate(): """See base class.""" hid_guid = GUID() hid.HidD_GetHidGuid(ctypes.byref(hid_guid)) devices = setupapi.SetupDiGetClassDevsA( ctypes.byref(hid_guid), None, None, 0x12) index = 0 interface_info = DeviceInterfaceData() interface_info.cbSize = ctypes.sizeof(DeviceInterfaceData) # pylint: disable=invalid-name out = [] while True: result = setupapi.SetupDiEnumDeviceInterfaces( devices, 0, ctypes.byref(hid_guid), index, ctypes.byref(interface_info)) index += 1 if not result: break detail_len = wintypes.DWORD() result = setupapi.SetupDiGetDeviceInterfaceDetailA( devices, ctypes.byref(interface_info), None, 0, ctypes.byref(detail_len), None) detail_len = detail_len.value if detail_len == 0: # skip this device, some kind of error continue buf = ctypes.create_string_buffer(detail_len) interface_detail = DeviceInterfaceDetailData.from_buffer(buf) interface_detail.cbSize = ctypes.sizeof(DeviceInterfaceDetailData) result = setupapi.SetupDiGetDeviceInterfaceDetailA( devices, ctypes.byref(interface_info), ctypes.byref(interface_detail), detail_len, None, None) if not result: raise ctypes.WinError() descriptor = base.DeviceDescriptor() # This is a bit of a hack to work around a limitation of ctypes and # "header" structures that are common in windows. DevicePath is a # ctypes array of length 1, but it is backed with a buffer that is much # longer and contains a null terminated string. So, we read the null # terminated string off DevicePath here. Per the comment above, the # alignment of this struct varies depending on architecture, but # in all cases the path string starts 1 DWORD into the structure. # # The path length is: # length of detail buffer - header length (1 DWORD) path_len = detail_len - ctypes.sizeof(wintypes.DWORD) descriptor.path = ctypes.string_at( ctypes.addressof(interface_detail.DevicePath), path_len) device = None try: device = OpenDevice(descriptor.path, True) except WindowsError as e: # pylint: disable=undefined-variable if e.winerror == ERROR_ACCESS_DENIED: # Access Denied, e.g. a keyboard continue else: raise e try: FillDeviceAttributes(device, descriptor) FillDeviceCapabilities(device, descriptor) out.append(descriptor.ToPublicDict()) except Exception: continue # Try with next device finally: kernel32.CloseHandle(device) return out def __init__(self, path): """See base class.""" base.HidDevice.__init__(self, path) self.dev = OpenDevice(path) self.desc = base.DeviceDescriptor() FillDeviceCapabilities(self.dev, self.desc) def GetInReportDataLength(self): """See base class.""" return self.desc.internal_max_in_report_len - 1 def GetOutReportDataLength(self): """See base class.""" return self.desc.internal_max_out_report_len - 1 def Write(self, packet): """See base class.""" if len(packet) != self.GetOutReportDataLength(): raise OSError('Packet length must match report data length.') out = bytes(bytearray([0] + packet)) # Prepend the zero-byte (report ID) num_written = wintypes.DWORD() ret = ( kernel32.WriteFile( self.dev, out, len(out), ctypes.byref(num_written), None)) if num_written.value != len(out): raise OSError( 'Failed to write complete packet. ' + 'Expected %d, but got %d' % (len(out), num_written.value)) if not ret: raise ctypes.WinError() def Read(self): """See base class.""" buf = ctypes.create_string_buffer(self.desc.internal_max_in_report_len) num_read = wintypes.DWORD() ret = kernel32.ReadFile( self.dev, buf, len(buf), ctypes.byref(num_read), None) if num_read.value != self.desc.internal_max_in_report_len: raise OSError('Failed to read full length report from device.') if not ret: raise ctypes.WinError() # Convert the string buffer to a list of numbers. Throw away the first # byte, which is the report id (which we don't care about). return b''.join(buf)[1:] fido2-0.8.1/fido2/_pyu2f/__init__.py0000644000175000017500000000336213544600444016723 0ustar daindain00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # 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. """Implements interface for talking to hid devices. This module implenets an interface for talking to low level hid devices using various methods on different platforms. """ from __future__ import absolute_import import sys class hid(object): @staticmethod def Enumerate(): return InternalPlatformSwitch('Enumerate') @staticmethod def Open(path): return InternalPlatformSwitch('__init__', path) def InternalPlatformSwitch(funcname, *args, **kwargs): """Determine, on a platform-specific basis, which module to use.""" # pylint: disable=g-import-not-at-top clz = None if sys.platform.startswith('linux'): from . import linux clz = linux.LinuxHidDevice elif sys.platform.startswith('win32'): from . import windows clz = windows.WindowsHidDevice elif sys.platform.startswith('darwin'): from . import macos clz = macos.MacOsHidDevice elif sys.platform.startswith('freebsd'): from . import freebsd clz = freebsd.FreeBSDHidDevice if not clz: raise Exception('Unsupported platform: ' + sys.platform) if funcname == '__init__': return clz(*args, **kwargs) return getattr(clz, funcname)(*args, **kwargs) fido2-0.8.1/fido2/_tpm.py0000644000175000017500000004141113554322463014717 0ustar daindain00000000000000# -*- 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. import struct import six from enum import IntEnum from collections import namedtuple from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa, ec from cryptography.hazmat.primitives import hashes from .utils import bytes2int, ByteBuffer if six.PY2: # Workaround for int max size on Python 2. from enum import Enum class _LongEnum(long, Enum): # noqa F821 """Like IntEnum, but supports larger values""" IntEnum = _LongEnum # Use instead of IntEnum # noqa F811 TPM_ALG_NULL = 0x0010 class TpmRsaScheme(IntEnum): RSASSA = 0x0014 RSAPSS = 0x0016 OAEP = 0x0017 RSAES = 0x0015 class TpmAlgAsym(IntEnum): RSA = 0x0001 ECC = 0x0023 class TpmAlgHash(IntEnum): SHA1 = 0x0004 SHA256 = 0x000B SHA384 = 0x000C SHA512 = 0x000D def _hash_alg(self): if self == TpmAlgHash.SHA1: return hashes.SHA1() elif self == TpmAlgHash.SHA256: return hashes.SHA256() elif self == TpmAlgHash.SHA384: return hashes.SHA384() elif self == TpmAlgHash.SHA512: return hashes.SHA512() return NotImplementedError( "_hash_alg is not implemented for {0!r}".format(self) ) TpmsCertifyInfo = namedtuple("TpmsCertifyInfo", "name qualified_name") class TpmAttestationFormat(object): """the signature data is defined by [TPMv2-Part2] Section 10.12.8 (TPMS_ATTEST) as: TPM_GENERATED_VALUE (0xff544347 aka "\xffTCG") TPMI_ST_ATTEST - always TPM_ST_ATTEST_CERTIFY (0x8017) because signing procedure defines it should call TPM_Certify [TPMv2-Part3] Section 18.2 TPM2B_NAME size (uint16) name (size long) TPM2B_DATA size (uint16) name (size long) TPMS_CLOCK_INFO clock (uint64) resetCount (uint32) restartCount (uint32) safe (byte) 1 yes, 0 no firmwareVersion uint64 attested TPMS_CERTIFY_INFO (because TPM_ST_ATTEST_CERTIFY) name TPM2B_NAME qualified_name TPM2B_NAME See: https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-3-Commands-01.38.pdf """ TPM_GENERATED_VALUE = b"\xffTCG" TPM_ST_ATTEST_CERTIFY = b"\x80\x17" @classmethod def parse(cls, data): reader = ByteBuffer(data) generated_value = reader.read(4) # Verify that magic is set to TPM_GENERATED_VALUE. # see https://w3c.github.io/webauthn/#sctn-tpm-attestation # verification procedure if generated_value != cls.TPM_GENERATED_VALUE: raise ValueError("generated value field is invalid") # Verify that type is set to TPM_ST_ATTEST_CERTIFY. # see https://w3c.github.io/webauthn/#sctn-tpm-attestation # verification procedure tpmi_st_attest = reader.read(2) if tpmi_st_attest != cls.TPM_ST_ATTEST_CERTIFY: raise ValueError("tpmi_st_attest field is invalid") try: name = reader.read(reader.unpack("!H")) data = reader.read(reader.unpack("!H")) clock = reader.unpack("!Q") reset_count = reader.unpack("!L") restart_count = reader.unpack("!L") safe_value = reader.unpack("B") if safe_value not in (0, 1): raise ValueError("invalid value 0x{0:x} for boolean".format(safe_value)) safe = safe_value == 1 firmware_version = reader.unpack("!Q") attested_name = reader.read(reader.unpack("!H")) attested_qualified_name = reader.read(reader.unpack("!H")) except struct.error as e: raise ValueError(e) return cls( name=name, data=data, clock_info=(clock, reset_count, restart_count, safe), firmware_version=firmware_version, attested=TpmsCertifyInfo( name=attested_name, qualified_name=attested_qualified_name ), ) def __init__(self, name, data, clock_info, firmware_version, attested): self.name = name self.data = data self.clock_info = clock_info self.firmware_version = firmware_version assert attested.__class__ == TpmsCertifyInfo self.attested = attested def __repr__(self): return ( "".format(self=self) ) class TpmsRsaParms(object): """ 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 """ @classmethod def parse(cls, reader, attributes): ATTRIBUTES = TpmPublicFormat.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) def __init__(self, symmetric, scheme, key_bits, exponent): self.symmetric = symmetric self.scheme = scheme self.key_bits = key_bits self.exponent = exponent def __repr__(self): return ( "".format(self=self) ) class Tpm2bPublicKeyRsa(bytes): @classmethod def parse(cls, reader): buffer = reader.read(reader.unpack("!H")) return cls(buffer) 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): 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) 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 class TpmsEccParms(object): @classmethod def parse(cls, reader): 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) def __init__(self, symmetric, scheme, curve_id, kdf): self.symmetric = symmetric self.scheme = scheme self.curve_id = curve_id self.kdf = kdf def __repr__(self): return ( "".format(self=self) ) class TpmsEccPoint(object): """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 """ @classmethod def parse(cls, reader): x = reader.read(reader.unpack("!H")) y = reader.read(reader.unpack("!H")) return cls(x, y) def __init__(self, x, y): self.x = y self.y = y def __repr__(self): return "".format(self=self) class TpmPublicFormat(object): """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 """ 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 ) @classmethod def parse(cls, data): reader = ByteBuffer(data) sign_alg = TpmAlgAsym(reader.unpack("!H")) name_alg = TpmAlgHash(reader.unpack("!H")) attributes = reader.unpack("!L") if attributes & TpmPublicFormat.ATTRIBUTES.SHALL_BE_ZERO != 0: raise ValueError( "attributes is not formated correctly: 0x{:x}".format(attributes) ) auth_policy = reader.read(reader.unpack("!H")) if sign_alg == TpmAlgAsym.RSA: parameters = TpmsRsaParms.parse(reader, attributes) unique = Tpm2bPublicKeyRsa.parse(reader) elif sign_alg == TpmAlgAsym.ECC: parameters = TpmsEccParms.parse(reader) unique = TpmsEccPoint.parse(reader) else: raise NotImplementedError( "sign alg {:x} is not " "supported".format(sign_alg) ) 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 __init__( self, sign_alg, name_alg, attributes, auth_policy, parameters, unique, data ): self.sign_alg = sign_alg self.name_alg = name_alg self.attributes = attributes self.auth_policy = auth_policy self.parameters = parameters self.unique = unique self.data = data def __repr__(self): return ( "".format(self=self) ) def public_key(self): if self.sign_alg == TpmAlgAsym.RSA: exponent = self.parameters.exponent modulus = bytes2int(self.unique) return rsa.RSAPublicNumbers(exponent, modulus).public_key(default_backend()) elif self.sign_alg == TpmAlgAsym.ECC: return ec.EllipticCurvePublicNumbers( bytes2int(self.unique.x), bytes2int(self.unique.y), self.parameters.to_curve(), ).public_key(default_backend()) raise NotImplementedError( "public_key not implemented for {0!r}".format(self.sign_alg) ) def name(self): """ 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 fido2-0.8.1/fido2/__init__.py0000644000175000017500000000276413566742004015527 0ustar daindain00000000000000# 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 abc import six if six.PY2: @six.add_metaclass(abc.ABCMeta) class ABC(object): pass abc.ABC = ABC __version__ = "0.8.1" fido2-0.8.1/fido2.egg-info/0000755000175000017500000000000013566742204015101 5ustar daindain00000000000000fido2-0.8.1/fido2.egg-info/dependency_links.txt0000744000175000017500000000000113566742203021147 0ustar daindain00000000000000 fido2-0.8.1/fido2.egg-info/PKG-INFO0000744000175000017500000000240113566742203016173 0ustar daindain00000000000000Metadata-Version: 2.1 Name: fido2 Version: 0.8.1 Summary: Python based FIDO 2.0 library Home-page: https://github.com/Yubico/python-fido2 Author: Dain Nilsson Author-email: dain@yubico.com License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: License :: OSI Approved :: BSD License Classifier: License :: OSI Approved :: Apache Software 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 Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Topic :: Internet Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >=2.7.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* Provides-Extra: pcsc fido2-0.8.1/fido2.egg-info/requires.txt0000744000175000017500000000017513566742203017504 0ustar daindain00000000000000six cryptography>=1.5 [:platform_system == "FreeBSD"] uhid-freebsd>=1.2.1 [:python_version < "3.4"] enum34 [pcsc] pyscard fido2-0.8.1/fido2.egg-info/SOURCES.txt0000744000175000017500000000333113566742203016765 0ustar daindain00000000000000COPYING COPYING.APLv2 COPYING.MPLv2 MANIFEST.in NEWS README.adoc setup.cfg setup.py examples/acr122u.py examples/acr122usam.py examples/acr1252u.py examples/credential.py examples/get_info.py examples/hmac_secret.py examples/multi_device.py examples/resident_key.py examples/u2f_nfc.py examples/server/Pipfile examples/server/Pipfile.lock examples/server/README.adoc examples/server/server-u2f.py examples/server/server.py examples/server/static/authenticate.html examples/server/static/cbor.js examples/server/static/index-u2f.html examples/server/static/index.html examples/server/static/register.html examples/server/static/u2f-api.js examples/server/static/u2f.html fido2/__init__.py fido2/_tpm.py fido2/attestation.py fido2/cbor.py fido2/client.py fido2/cose.py fido2/ctap.py fido2/ctap1.py fido2/ctap2.py fido2/extensions.py fido2/hid.py fido2/nfc.py fido2/pcsc.py fido2/public_suffix_list.dat fido2/rpid.py fido2/server.py fido2/utils.py fido2/webauthn.py fido2/win_api.py fido2.egg-info/PKG-INFO fido2.egg-info/SOURCES.txt fido2.egg-info/dependency_links.txt fido2.egg-info/requires.txt fido2.egg-info/top_level.txt fido2/_pyu2f/__init__.py fido2/_pyu2f/base.py fido2/_pyu2f/freebsd.py fido2/_pyu2f/hidtransport.py fido2/_pyu2f/linux.py fido2/_pyu2f/macos.py fido2/_pyu2f/windows.py test/__init__.py test/test_attestation.py test/test_cbor.py test/test_client.py test/test_cose.py test/test_ctap1.py test/test_ctap2.py test/test_hid.py test/test_pcsc.py test/test_rpid.py test/test_server.py test/test_tpm.py test/test_utils.py test/test_webauthn.py test/utils.py test/_pyu2f/__init__.py test/_pyu2f/freebsd_test.py test/_pyu2f/hidtransport_test.py test/_pyu2f/linux_test.py test/_pyu2f/macos_test.py test/_pyu2f/util.py test/_pyu2f/util_test.pyfido2-0.8.1/fido2.egg-info/top_level.txt0000744000175000017500000000000613566742203017627 0ustar daindain00000000000000fido2 fido2-0.8.1/MANIFEST.in0000644000175000017500000000050213407160713014130 0ustar daindain00000000000000include COPYING* include README.adoc include NEWS include ChangeLog include fido2/public_suffix_list.dat include examples/*.py include examples/server/*.py include examples/server/README.adoc include examples/server/Pipfile include examples/server/Pipfile.lock include examples/server/static/* recursive-include test *.py fido2-0.8.1/NEWS0000644000175000017500000000777213566741761013127 0ustar daindain00000000000000* 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. fido2-0.8.1/PKG-INFO0000644000175000017500000000240113566742204013476 0ustar daindain00000000000000Metadata-Version: 2.1 Name: fido2 Version: 0.8.1 Summary: Python based FIDO 2.0 library Home-page: https://github.com/Yubico/python-fido2 Author: Dain Nilsson Author-email: dain@yubico.com License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: License :: OSI Approved :: BSD License Classifier: License :: OSI Approved :: Apache Software 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 Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Topic :: Internet Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >=2.7.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* Provides-Extra: pcsc fido2-0.8.1/README.adoc0000644000175000017500000000703513544577340014201 0ustar daindain00000000000000== python-fido2 image:https://travis-ci.org/Yubico/python-fido2.svg?branch=master["Travis CI Status", link="https://travis-ci.org/Yubico/python-fido2"] image:https://ci.appveyor.com/api/projects/status/8orx9nbdfq52w47s/branch/master?svg=true["Appveyor Status", link="https://ci.appveyor.com/project/Yubico53275/python-fido-host/branch/master"] Provides library functionality for communicating with a FIDO device over USB as well as verifying attestation and assertion signatures. WARNING: This project is in beta. Expect things to change or break at any time! This library aims to support the FIDO U2F and FIDO 2.0 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/_pyu2f/` and `test/_pyu2f/`. 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. === 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 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" ---- 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 fido2 is compatible with CPython 2.7 (2.7.6 and up), 3.4 onwards, and is tested on Windows, MacOS, FreeBSD, and Linux. This project depends on Cryptography. For instructions on installing this dependency, see https://cryptography.io/en/latest/installation/. NFC support is optionally available via PCSC, 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 recommend using `pipenv`. To set up the dev environment, run this command in the root directory of the repository: pipenv install --dev ==== Running tests While many tests can run on their own, some require a connected U2F or FIDO2 device to run. pipenv run test fido2-0.8.1/setup.cfg0000644000175000017500000000016613566742204014230 0ustar daindain00000000000000[bdist_wheel] universal = 1 [flake8] max-line-length = 88 ignore = E203, W503 [egg_info] tag_build = tag_date = 0 fido2-0.8.1/setup.py0000755000175000017500000000665513564737357014150 0ustar daindain00000000000000# 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 distutils.version import LooseVersion from setuptools import setup, find_packages, __version__ import re import sys if LooseVersion(__version__) < LooseVersion("20.2"): sys.exit( "Your setuptools version does not support PEP 508.\n" "Please install setuptools 20.2 or later." ) def get_version(): with open("fido2/__init__.py", "r") as f: match = re.search(r"(?m)^__version__\s*=\s*['\"](.+)['\"]$", f.read()) return match.group(1) setup( name="fido2", version=get_version(), packages=find_packages(exclude=["test", "test.*"]), include_package_data=True, author="Dain Nilsson", author_email="dain@yubico.com", description="Python based FIDO 2.0 library", url="https://github.com/Yubico/python-fido2", python_requires=">=2.7.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*", install_requires=[ "six", "cryptography>=1.5", 'uhid-freebsd>=1.2.1;platform_system=="FreeBSD"', ], extras_require={':python_version < "3.4"': ["enum34"], "pcsc": ["pyscard"]}, test_suite="test", tests_require=["mock>=1.0.1", 'pyfakefs>=3.4;platform_system=="Linux"'], 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", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: System Administrators", "Topic :: Internet", "Topic :: Security :: Cryptography", "Topic :: Software Development :: Libraries :: Python Modules", ], ) fido2-0.8.1/test/0000755000175000017500000000000013566742204013363 5ustar daindain00000000000000fido2-0.8.1/test/test_attestation.py0000644000175000017500000006035113554254405017336 0ustar daindain00000000000000# 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.ctap2 import AuthenticatorData from fido2.attestation import ( Attestation, UnsupportedAttestation, FidoU2FAttestation, PackedAttestation, TpmAttestation, NoneAttestation, AndroidSafetynetAttestation, InvalidData, InvalidSignature, UnsupportedType, ) from binascii import a2b_hex import unittest 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( a2b_hex( b"0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12410000002BF8A011F38C0A4D15800617111F9EDC7D0040A17370D9C1759005700C8DE77E7DFD3A0A5300E0A26E5213AA40D6DF10EE4028B58B5F34167035D840BEBAE0C5CE8FD05AD9BD33E3BE7D1C558D81AB4803570BA5010203262001215820A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1225820FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C" # noqa E501 ) ) attestation.verify({}, auth_data, b"deadbeef" * 8) 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( a2b_hex( b"54ce651ed715b4aaa755eecebd4ea0950815b334bd07d109893e963018cddbd945000000006028b017b1d44c02b4b3afcdafc96bb200201decfcd6d6a05c2826d52348afdc70a9800df007845047b1a23706aa6e2f315ca401030339010020590100af59f4ad4f71da800bb91045b267e240e06317f7b2b1d76f78e239a433811faeca58a1869fb00225eb2727f81b6b20cbc18c0ad8d38fa450e8df11b4ad3bc3ee5d13c77ed172fa3af0195ec6ac0c4bac8c950115dfce6d38737cbafefbe117d8401cd56c638043a0d585131bc48a153b17a8dcb96671e15a90ba1b4ff810b138b77ac0a050b039b87b6089dd8dfa45611b992109d554aad3e6b72ac82d801496e4d2d230aa466090bbbf4f5632fe4b588e4f571462378fa6f514a536a5945b223c8d98f730b7cf85de86b98c217090f9e9ebf9643cf3feceeacb837d7a18542e03271cd8c70cf81186cdb63e4cbf4efc0cbbd3c93231b06f19580d0a980264d12143010001" # noqa ) ) # noqa attestation.verify({}, auth_data, b"deadbeef" * 8) 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": a2b_hex( b"""80e564d8cbb236577de68d2e68ecae200a8eaf6992889b5 fdc24624a4cb69caaab18df965058fbac39df9714b9c80b9a12d715cfc4dd15ed3a6e191a6d26e 7206fd402b0733c2c8b91f62ad44e4d41c940e2e914253b1d1a1c8889b1cdaf668b5449245dc33 1fab12e0b0dcdfc530cbe1f370e1f2b06c163fbd6177925a1a8998edd2e726989246a1980fa34e 6d65d3ca284944cb10254d85db0d8948294fb8174a41206c6b5e36406bae447343f8c9f97420e3 9f361815dfb268b33ccde5f29e4348a70f95abc30754c839fa7126e5bd882377d6abe3c0c95ba5 c21190a5e4fff5380b2c23cc1655e593244019e172ba8284618471d95b92c231c1ffe98ff23 """.replace( b"\n", b"" ) ), "x5c": [ a2b_hex( b"""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( b"\n", b"" ) ), a2b_hex( b"""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( b"\n", b"" ) ), ], "certInfo": a2b_hex( b"""ff54434780170022000b68cec627cc6411099a1f80 9fde4379f649aa170c7072d1adf230de439efc80810014f7c8b0cdeb31328648130a19733d6fff 16e76e1300000003ef605603446ed8c56aa7608d01a6ea5651ee67a8a20022000bdf681917e185 29c61e1b85a1e7952f3201eb59c609ed5d8e217e5de76b228bbd0022000b0a10d216b0c3ab82bf dc1f0a016ab9493384c7aee1937ee8800f76b30c9b71a7""".replace( b"\n", b"" ) ), "pubArea": a2b_hex( b"""0001000b0006047200209dffcbf36c383ae699fb986 8dc6dcb89d7153884be2803922c124158bfad22ae001000100800000000000100c706586c7f46c dffede0ee0c5ebc8b7a08b36555c8091669e9ef2cb4fd858134a01e9522d3ef924069aeeec2271 823fe9879b5079eb3123be2eb39a7e954f8b83b5ebefefda25aed01bd19eab6db1962a3713985b 7a2dd1aa7770b5c1567fb0d18521e14abebbccc16832ef10bb05dcc818bbb70c91c224475928ad a6f6181ed64f1cfb40db5e01687454cfacafa8318bdc6a677550baa6e24f8af864fa5324e9d930 a97cdeb1995b476f21a017b33ab7fe4139f2524c784fcb04cf5241c89f0c145eb23da914ad1722 d47a843692a0b2a567d94dd808c13678a51c5a0583dc042dcbba1b9ceff12b159d0539248b0994 ee18128ed50dd7a855e54d2459db005""".replace( b"\n", b"" ) ), } auth_data = AuthenticatorData( a2b_hex( b"54ce651ed715b4aaa755eecebd4ea0950815b334bd07d109893e963018cddbd9450000000008987058cadc4b81b6e130de50dcbe9600206053b7b599d16fb3fb11ea17a344850ebd0d18183a5b7ca6dfbd20c63cdb462aa401030339010020590100c706586c7f46cdffede0ee0c5ebc8b7a08b36555c8091669e9ef2cb4fd858134a01e9522d3ef924069aeeec2271823fe9879b5079eb3123be2eb39a7e954f8b83b5ebefefda25aed01bd19eab6db1962a3713985b7a2dd1aa7770b5c1567fb0d18521e14abebbccc16832ef10bb05dcc818bbb70c91c224475928ada6f6181ed64f1cfb40db5e01687454cfacafa8318bdc6a677550baa6e24f8af864fa5324e9d930a97cdeb1995b476f21a017b33ab7fe4139f2524c784fcb04cf5241c89f0c145eb23da914ad1722d47a843692a0b2a567d94dd808c13678a51c5a0583dc042dcbba1b9ceff12b159d0539248b0994ee18128ed50dd7a855e54d2459db0052143010001" # noqa ) ) client_param = a2b_hex( b"057a0ecbe7e3e99e8926941614f6af078c802b110be89eb221d69be2e17a1ba4" ) attestation.verify(statement, auth_data, client_param) def test_fido_u2f_attestation(self): attestation = Attestation.for_type("fido-u2f")() self.assertIsInstance(attestation, FidoU2FAttestation) statement = { "sig": a2b_hex( b"30450220324779C68F3380288A1197B6095F7A6EB9B1B1C127F66AE12A99FE8532EC23B9022100E39516AC4D61EE64044D50B415A6A4D4D84BA6D895CB5AB7A1AA7D081DE341FA" # noqa E501 ), "x5c": [ a2b_hex( b"3082024A30820132A0030201020204046C8822300D06092A864886F70D01010B0500302E312C302A0603550403132359756269636F2055324620526F6F742043412053657269616C203435373230303633313020170D3134303830313030303030305A180F32303530303930343030303030305A302C312A302806035504030C2159756269636F205532462045452053657269616C203234393138323332343737303059301306072A8648CE3D020106082A8648CE3D030107034200043CCAB92CCB97287EE8E639437E21FCD6B6F165B2D5A3F3DB131D31C16B742BB476D8D1E99080EB546C9BBDF556E6210FD42785899E78CC589EBE310F6CDB9FF4A33B3039302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C020101040403020430300D06092A864886F70D01010B050003820101009F9B052248BC4CF42CC5991FCAABAC9B651BBE5BDCDC8EF0AD2C1C1FFB36D18715D42E78B249224F92C7E6E7A05C49F0E7E4C881BF2E94F45E4A21833D7456851D0F6C145A29540C874F3092C934B43D222B8962C0F410CEF1DB75892AF116B44A96F5D35ADEA3822FC7146F6004385BCB69B65C99E7EB6919786703C0D8CD41E8F75CCA44AA8AB725AD8E799FF3A8696A6F1B2656E631B1E40183C08FDA53FA4A8F85A05693944AE179A1339D002D15CABD810090EC722EF5DEF9965A371D415D624B68A2707CAD97BCDD1785AF97E258F33DF56A031AA0356D8E8D5EBCADC74E071636C6B110ACE5CC9B90DFEACAE640FF1BB0F1FE5DB4EFF7A95F060733F5" # noqa E501 ) ], } auth_data = AuthenticatorData( a2b_hex( b"1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE41000000000000000000000000000000000000000000403EBD89BF77EC509755EE9C2635EFAAAC7B2B9C5CEF1736C3717DA48534C8C6B654D7FF945F50B5CC4E78055BDD396B64F78DA2C5F96200CCD415CD08FE420038A5010203262001215820E87625896EE4E46DC032766E8087962F36DF9DFE8B567F3763015B1990A60E1422582027DE612D66418BDA1950581EBC5C8C1DAD710CB14C22F8C97045F4612FB20C91" # noqa E501 ) ) client_param = a2b_hex( b"687134968222EC17202E42505F8ED2B16AE22F16BB05B88C25DB9E602645F141" ) attestation.verify(statement, auth_data, client_param) 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": a2b_hex( b"304502200D15DAF337D727AB4719B4027114A2AC43CD565D394CED62C3D9D1D90825F0B3022100989615E7394C87F4AD91F8FDAE86F7A3326DF332B3633DB088AAC76BFFB9A46B" # noqa E501 ), "x5c": [ a2b_hex( b"308202B73082019FA00302010202041D31330D300D06092A864886F70D01010B0500302A3128302606035504030C1F59756269636F2050726576696577204649444F204174746573746174696F6E301E170D3138303332383036333932345A170D3139303332383036333932345A306E310B300906035504061302534531123010060355040A0C0959756269636F20414231223020060355040B0C1941757468656E74696361746F72204174746573746174696F6E3127302506035504030C1E59756269636F205532462045452053657269616C203438393736333539373059301306072A8648CE3D020106082A8648CE3D030107034200047D71E8367CAFD0EA6CF0D61E4C6A416BA5BB6D8FAD52DB2389AD07969F0F463BFDDDDDC29D39D3199163EE49575A3336C04B3309D607F6160C81E023373E0197A36C306A302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C0201010404030204303021060B2B0601040182E51C01010404120410F8A011F38C0A4D15800617111F9EDC7D300C0603551D130101FF04023000300D06092A864886F70D01010B050003820101009B904CEADBE1F1985486FEAD02BAEAA77E5AB4E6E52B7E6A2666A4DC06E241578169193B63DADEC5B2B78605A128B2E03F7FE2A98EAEB4219F52220995F400CE15D630CF0598BA662D7162459F1AD1FC623067376D4E4091BE65AC1A33D8561B9996C0529EC1816D1710786384D5E8783AA1F7474CB99FE8F5A63A79FF454380361C299D67CB5CC7C79F0D8C09F8849B0500F6D625408C77CBBC26DDEE11CB581BEB7947137AD4F05AAF38BD98DA10042DDCAC277604A395A5B3EAA88A5C8BB27AB59C8127D59D6BBBA5F11506BF7B75FDA7561A0837C46F025FD54DCF1014FC8D17C859507AC57D4B1DEA99485DF0BA8F34D00103C3EEF2EF3BBFEC7A6613DE" # noqa E501 ) ], } auth_data = AuthenticatorData( a2b_hex( b"0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE124100000003F8A011F38C0A4D15800617111F9EDC7D004060A386206A3AACECBDBB22D601853D955FDC5D11ADFBD1AA6A950D966B348C7663D40173714A9F987DF6461BEADFB9CD6419FFDFE4D4CF2EEC1AA605A4F59BDAA50102032620012158200EDB27580389494D74D2373B8F8C2E8B76FA135946D4F30D0E187E120B423349225820E03400D189E85A55DE9AB0F538ED60736EB750F5F0306A80060FE1B13010560D" # noqa E501 ) ) client_param = a2b_hex( b"985B6187D042FB1258892ED637CEC88617DDF5F6632351A545617AA2B75261BF" ) attestation.verify(statement, auth_data, client_param) 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( a2b_hex( b"720c20fde835785e0f5ebcad8ef6a7bd88804a91612a2e820e0059b8d5358797450000000000000000000000000000000000000000004101c8fd9b533d6adacf6710ebcfb39f6361c4d7e8787db47dc0a75ae0e7c862198c9c83b81ef2547bb5669314095fc846af4ecac6875f7b230cac7359c76b0c20f7a5010203262001215820a28851e2d411b5b2c289da50d41cc41be88498941fc256dab500b21c8dafe8d1225820d289dd467715be06a622771a7b21e1bbe2372f8713d20dd7888a6e7ae1845ca8" # noqa E501 ) ) client_param = a2b_hex( b"8422c80f3428e4e6465f76ebc8a4a93759a0a2e1fb845ee5eea7a02027408520" ) attestation.verify(statement, auth_data, client_param) fido2-0.8.1/test/test_cbor.py0000644000175000017500000001562113544577440015732 0ustar daindain00000000000000# 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 __future__ import absolute_import, unicode_literals from fido2 import cbor from binascii import a2b_hex, b2a_hex 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 b2a_hex(cbor.encode(data)).decode() 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(a2b_hex(data)), (value, b"")) self.assertEqual(cbor.decode(a2b_hex(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") fido2-0.8.1/test/test_client.py0000644000175000017500000004622613564737350016270 0ustar daindain00000000000000# 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 __future__ import absolute_import, unicode_literals import mock import unittest from threading import Event, Timer from binascii import a2b_hex from fido2.utils import sha256, websafe_decode from fido2.hid import CAPABILITY from fido2.ctap import CtapError from fido2.ctap1 import ApduError, APDU, RegistrationData, SignatureData from fido2.ctap2 import Info, AttestationObject from fido2.client import ClientData, U2fClient, ClientError, Fido2Client from fido2.webauthn import PublicKeyCredentialCreationOptions class TestClientData(unittest.TestCase): def test_client_data(self): client_data = ClientData( b'{"typ":"navigator.id.finishEnrollment","challenge":"vqrS6WXDe1JUs5_c3i4-LkKIHRr-3XVb3azuA5TifHo","cid_pubkey":{"kty":"EC","crv":"P-256","x":"HzQwlfXX7Q4S5MtCCnZUNBw3RMzPO9tOyWjBqRl4tJ8","y":"XVguGFLIZx1fXg3wNqfdbn75hi4-_7-BxhMljw42Ht4"},"origin":"http://example.com"}' # noqa E501 ) self.assertEqual( client_data.hash, a2b_hex("4142d21c00d94ffb9d504ada8f99b721f4b191ae4e37ca0140f696b6983cfacb"), ) self.assertEqual(client_data.get("origin"), "http://example.com") self.assertEqual(client_data, ClientData.from_b64(client_data.b64)) self.assertEqual( client_data.data, { "typ": "navigator.id.finishEnrollment", "challenge": "vqrS6WXDe1JUs5_c3i4-LkKIHRr-3XVb3azuA5TifHo", "cid_pubkey": { "kty": "EC", "crv": "P-256", "x": "HzQwlfXX7Q4S5MtCCnZUNBw3RMzPO9tOyWjBqRl4tJ8", "y": "XVguGFLIZx1fXg3wNqfdbn75hi4-_7-BxhMljw42Ht4", }, "origin": "http://example.com", }, ) APP_ID = "https://foo.example.com" REG_DATA = RegistrationData( a2b_hex( b"0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c253082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" # noqa E501 ) ) SIG_DATA = SignatureData( a2b_hex( b"0100000001304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f" # noqa E501 ) ) class TestU2fClient(unittest.TestCase): def test_register_wrong_app_id(self): client = U2fClient(None, APP_ID) try: client.register( "https://bar.example.com", [{"version": "U2F_V2", "challenge": "foobar"}], [], ) self.fail("register did not raise error") except ClientError as e: self.assertEqual(e.code, ClientError.ERR.BAD_REQUEST) def test_register_unsupported_version(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_XXX" try: client.register(APP_ID, [{"version": "U2F_V2", "challenge": "foobar"}], []) self.fail("register did not raise error") except ClientError as e: self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE) client.ctap.get_version.assert_called_with() def test_register_existing_key(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" client.ctap.authenticate.side_effect = ApduError(APDU.USE_NOT_SATISFIED) try: client.register( APP_ID, [{"version": "U2F_V2", "challenge": "foobar"}], [{"version": "U2F_V2", "keyHandle": "a2V5"}], ) self.fail("register did not raise error") except ClientError as e: self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE) client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called_once() # Check keyHandle self.assertEqual(client.ctap.authenticate.call_args[0][2], b"key") # Ensure check-only was set self.assertTrue(client.ctap.authenticate.call_args[0][3]) def test_register(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" client.ctap.authenticate.side_effect = ApduError(APDU.WRONG_DATA) client.ctap.register.return_value = REG_DATA resp = client.register( APP_ID, [{"version": "U2F_V2", "challenge": "foobar"}], [{"version": "U2F_V2", "keyHandle": "a2V5"}], ) client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called_once() client.ctap.register.assert_called_once() client_param, app_param = client.ctap.register.call_args[0] self.assertEqual(sha256(websafe_decode(resp["clientData"])), client_param) self.assertEqual(websafe_decode(resp["registrationData"]), REG_DATA) self.assertEqual(sha256(APP_ID.encode()), app_param) def test_register_await_timeout(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" client.ctap.authenticate.side_effect = ApduError(APDU.WRONG_DATA) client.ctap.register.side_effect = ApduError(APDU.USE_NOT_SATISFIED) client.poll_delay = 0.01 event = Event() timer = Timer(0.1, event.set) timer.start() try: client.register( APP_ID, [{"version": "U2F_V2", "challenge": "foobar"}], [{"version": "U2F_V2", "keyHandle": "a2V5"}], event=event, ) except ClientError as e: self.assertEqual(e.code, ClientError.ERR.TIMEOUT) def test_register_await_touch(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" client.ctap.authenticate.side_effect = ApduError(APDU.WRONG_DATA) client.ctap.register.side_effect = [ ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), REG_DATA, ] event = Event() event.wait = mock.MagicMock() resp = client.register( APP_ID, [{"version": "U2F_V2", "challenge": "foobar"}], [{"version": "U2F_V2", "keyHandle": "a2V5"}], event=event, ) event.wait.assert_called() client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called_once() client.ctap.register.assert_called() client_param, app_param = client.ctap.register.call_args[0] self.assertEqual(sha256(websafe_decode(resp["clientData"])), client_param) self.assertEqual(websafe_decode(resp["registrationData"]), REG_DATA) self.assertEqual(sha256(APP_ID.encode()), app_param) def test_sign_wrong_app_id(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" try: client.sign( "http://foo.example.com", "challenge", [{"version": "U2F_V2", "keyHandle": "a2V5"}], ) self.fail("sign did not raise error") except ClientError as e: self.assertEqual(e.code, ClientError.ERR.BAD_REQUEST) def test_sign_unsupported_version(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_XXX" try: client.sign( APP_ID, "challenge", [{"version": "U2F_V2", "keyHandle": "a2V5"}] ) self.fail("sign did not raise error") except ClientError as e: self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE) client.ctap.get_version.assert_called_with() def test_sign_missing_key(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" client.ctap.authenticate.side_effect = ApduError(APDU.WRONG_DATA) try: client.sign( APP_ID, "challenge", [{"version": "U2F_V2", "keyHandle": "a2V5"}] ) self.fail("sign did not raise error") except ClientError as e: self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE) client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called_once() _, app_param, key_handle = client.ctap.authenticate.call_args[0] self.assertEqual(app_param, sha256(APP_ID.encode())) self.assertEqual(key_handle, b"key") def test_sign(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" client.ctap.authenticate.return_value = SIG_DATA resp = client.sign( APP_ID, "challenge", [{"version": "U2F_V2", "keyHandle": "a2V5"}] ) client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called_once() client_param, app_param, key_handle = client.ctap.authenticate.call_args[0] self.assertEqual(client_param, sha256(websafe_decode(resp["clientData"]))) self.assertEqual(app_param, sha256(APP_ID.encode())) self.assertEqual(key_handle, b"key") self.assertEqual(websafe_decode(resp["signatureData"]), SIG_DATA) def test_sign_await_touch(self): client = U2fClient(None, APP_ID) client.ctap = mock.MagicMock() client.ctap.get_version.return_value = "U2F_V2" client.ctap.authenticate.side_effect = [ ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), ApduError(APDU.USE_NOT_SATISFIED), SIG_DATA, ] event = Event() event.wait = mock.MagicMock() resp = client.sign( APP_ID, "challenge", [{"version": "U2F_V2", "keyHandle": "a2V5"}], event=event, ) event.wait.assert_called() client.ctap.get_version.assert_called_with() client.ctap.authenticate.assert_called() client_param, app_param, key_handle = client.ctap.authenticate.call_args[0] self.assertEqual(client_param, sha256(websafe_decode(resp["clientData"]))) self.assertEqual(app_param, sha256(APP_ID.encode())) self.assertEqual(key_handle, b"key") self.assertEqual(websafe_decode(resp["signatureData"]), SIG_DATA) rp = {"id": "example.com", "name": "Example RP"} user = {"id": b"user_id", "name": "A. User"} challenge = b"Y2hhbGxlbmdl" _INFO_NO_PIN = a2b_hex( "a60182665532465f5632684649444f5f325f3002826375766d6b686d61632d7365637265740350f8a011f38c0a4d15800617111f9edc7d04a462726bf5627570f564706c6174f469636c69656e7450696ef4051904b0068101" # noqa E501 ) _MC_RESP = a2b_hex( "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_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(_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(_INFO_NO_PIN) 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.get_info.assert_called_with() 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(_INFO_NO_PIN) ctap2.make_credential.return_value = AttestationObject(_MC_RESP) PatchedCTAP2.return_value = ctap2 client = Fido2Client(dev, APP_ID) attestation, client_data = client.make_credential( PublicKeyCredentialCreationOptions( rp, user, challenge, [{"type": "public-key", "alg": -7}], timeout=1000, authenticator_selection={"userVerification": "discouraged"}, ) ) self.assertIsInstance(attestation, AttestationObject) self.assertIsInstance(client_data, ClientData) ctap2.get_info.assert_called_with() ctap2.make_credential.assert_called_with( client_data.hash, rp, user, [{"type": "public-key", "alg": -7}], None, None, None, None, None, mock.ANY, None, ) self.assertEqual(client_data.get("origin"), APP_ID) self.assertEqual(client_data.get("type"), "webauthn.create") self.assertEqual(client_data.challenge, challenge) def test_make_credential_ctap1(self): dev = mock.Mock() dev.capabilities = 0 # No CTAP2 client = Fido2Client(dev, APP_ID) client.ctap1 = mock.MagicMock() client.ctap1.get_version.return_value = "U2F_V2" client.ctap1.register.return_value = REG_DATA attestation, client_data = client.make_credential( PublicKeyCredentialCreationOptions( rp, user, challenge, [{"type": "public-key", "alg": -7}] ) ) self.assertIsInstance(attestation, AttestationObject) self.assertIsInstance(client_data, ClientData) client.ctap1.register.assert_called_with( client_data.hash, sha256(rp["id"].encode()) ) self.assertEqual(client_data.get("origin"), APP_ID) self.assertEqual(client_data.get("type"), "webauthn.create") self.assertEqual(client_data.challenge, challenge) self.assertEqual(attestation.fmt, "fido-u2f") fido2-0.8.1/test/test_cose.py0000644000175000017500000001465213544577440015741 0ustar daindain00000000000000# 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 cryptography.exceptions import UnsupportedAlgorithm 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" ), }, ) try: key.verify( a2b_hex( b"a379a6f6eeafb9a55e378c118034e2751e682fab9f2d30ab13d2125586ce1947010000000500a11a323057d1103784ddff99a354ddd42348c2f00e88d8977b916cabf92268" # noqa E501 ), a2b_hex( b"e8c927ef1a57c738ff4ba8d6f90e06d837a5219eee47991f96b126b0685d512520c9c2eedebe4b88ff2de2b19cb5f8686efc7c4261e9ed1cb3ac5de50869be0a" # noqa E501 ), ) except UnsupportedAlgorithm: self.skipTest("EdDSA support missing") 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"}) fido2-0.8.1/test/test_ctap1.py0000644000175000017500000001755113544577440016021 0ustar daindain00000000000000# 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.ctap1 import CTAP1, ApduError from binascii import a2b_hex import unittest import mock 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 = ( a2b_hex( "0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c253082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" # noqa E501 ) + b"\x90\x00" ) client_param = a2b_hex( b"4142d21c00d94ffb9d504ada8f99b721f4b191ae4e37ca0140f696b6983cfacb" ) app_param = a2b_hex( b"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, a2b_hex( "04b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9" # noqa E501 ), ) self.assertEqual( resp.key_handle, a2b_hex( "2a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c25" # noqa E501 ), ) self.assertEqual( resp.certificate, a2b_hex( "3082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df" # noqa E501 ), ) self.assertEqual( resp.signature, a2b_hex( "304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" # noqa E501 ), ) resp.verify(app_param, client_param) def test_authenticate(self): ctap = CTAP1(mock.MagicMock()) ctap.device.call.return_value = ( a2b_hex( "0100000001304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f" # noqa E501 ) + b"\x90\x00" ) client_param = a2b_hex( b"ccd6ee2e47baef244d49a222db496bad0ef5b6f93aa7cc4d30c4821b3b9dbc57" ) app_param = a2b_hex( b"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, a2b_hex( "304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f" # noqa E501 ), ) public_key = a2b_hex( b"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", ) fido2-0.8.1/test/test_ctap2.py0000644000175000017500000005133613564737350016021 0ustar daindain00000000000000# 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.ctap1 import RegistrationData from fido2.ctap2 import ( CTAP2, PinProtocolV1, Info, AttestedCredentialData, AuthenticatorData, AttestationObject, AssertionResponse, ) from fido2.attestation import Attestation from fido2 import cbor from binascii import a2b_hex from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec import unittest import mock _AAGUID = a2b_hex("F8A011F38C0A4D15800617111F9EDC7D") _INFO = a2b_hex( "a60182665532465f5632684649444f5f325f3002826375766d6b686d61632d7365637265740350f8a011f38c0a4d15800617111f9edc7d04a462726bf5627570f564706c6174f469636c69656e7450696ef4051904b0068101" # noqa E501 ) _INFO_EXTRA_KEY = a2b_hex( "A70182665532465F5632684649444F5F325F3002826375766D6B686D61632D7365637265740350F8A011F38C0A4D15800617111F9EDC7D04A462726BF5627570F564706C6174F469636C69656E7450696EF4051904B00681010708" # noqa E501 ) class TestInfo(unittest.TestCase): def test_parse_bytes(self): info = Info(_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_protocols, [1]) self.assertEqual( info.data, { Info.KEY.VERSIONS: ["U2F_V2", "FIDO_2_0"], Info.KEY.EXTENSIONS: ["uvm", "hmac-secret"], Info.KEY.AAGUID: _AAGUID, Info.KEY.OPTIONS: { "clientPin": False, "plat": False, "rk": True, "up": True, }, Info.KEY.MAX_MSG_SIZE: 1200, Info.KEY.PIN_PROTOCOLS: [1], }, ) def test_info_with_extra_field(self): info = Info(_INFO_EXTRA_KEY) self.assertEqual(info.versions, ["U2F_V2", "FIDO_2_0"]) self.assertEqual(info.data[7], 8) _ATT_CRED_DATA = a2b_hex( "f8a011f38c0a4d15800617111f9edc7d0040fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a5290" # noqa E501 ) _CRED_ID = a2b_hex( "fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783" # noqa E501 ) _PUB_KEY = { 1: 2, 3: -7, -1: 1, -2: a2b_hex("643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf"), -3: a2b_hex("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 = a2b_hex( "0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12410000001CF8A011F38C0A4D15800617111F9EDC7D0040FE3AAC036D14C1E1C65518B698DD1DA8F596BC33E11072813466C6BF3845691509B80FB76D59309B8D39E0A93452688F6CA3A39A76F3FC52744FB73948B15783A5010203262001215820643566C206DD00227005FA5DE69320616CA268043A38F08BDE2E9DC45A5CAFAF225820171353B2932434703726AAE579FA6542432861FE591E481EA22D63997E1A5290" # noqa E501 ) _AUTH_DATA_GA = a2b_hex( "0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12010000001D" ) _RP_ID_HASH = a2b_hex( "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 = a2b_hex( "a301667061636b65640258c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae12410000001cf8a011f38c0a4d15800617111f9edc7d0040fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a529003a363616c67266373696758483046022100cc1ef43edf07de8f208c21619c78a565ddcf4150766ad58781193be8e0a742ed022100f1ed7c7243e45b7d8e5bda6b1abf10af7391789d1ef21b70bd69fed48dba4cb163783563815901973082019330820138a003020102020900859b726cb24b4c29300a06082a8648ce3d0403023047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e301e170d3136313230343131353530305a170d3236313230323131353530305a3047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3059301306072a8648ce3d020106082a8648ce3d03010703420004ad11eb0e8852e53ad5dfed86b41e6134a18ec4e1af8f221a3c7d6e636c80ea13c3d504ff2e76211bb44525b196c44cb4849979cf6f896ecd2bb860de1bf4376ba30d300b30090603551d1304023000300a06082a8648ce3d0403020349003046022100e9a39f1b03197525f7373e10ce77e78021731b94d0c03f3fda1fd22db3d030e7022100c4faec3445a820cf43129cdb00aabefd9ae2d874f9c5d343cb2f113da23723f3" # noqa E501 ) _GA_RESP = a2b_hex( "a301a26269645840fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b1578364747970656a7075626c69632d6b65790258250021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae12010000001d035846304402206765cbf6e871d3af7f01ae96f06b13c90f26f54b905c5166a2c791274fc2397102200b143893586cc799fba4da83b119eaea1bd80ac3ce88fcedb3efbd596a1f4f63" # noqa E501 ) _CRED_ID = a2b_hex( "FE3AAC036D14C1E1C65518B698DD1DA8F596BC33E11072813466C6BF3845691509B80FB76D59309B8D39E0A93452688F6CA3A39A76F3FC52744FB73948B15783" # noqa E501 ) _CRED = {"type": "public-key", "id": _CRED_ID} _SIGNATURE = a2b_hex( "304402206765CBF6E871D3AF7F01AE96F06B13C90F26F54B905C5166A2C791274FC2397102200B143893586CC799FBA4DA83B119EAEA1BD80AC3CE88FCEDB3EFBD596A1F4F63" # noqa E501 ) class TestAttestationObject(unittest.TestCase): def test_string_keys(self): self.assertEqual(AttestationObject.KEY.FMT.string_key, "fmt") self.assertEqual(AttestationObject.KEY.AUTH_DATA.string_key, "authData") self.assertEqual(AttestationObject.KEY.ATT_STMT.string_key, "attStmt") def test_fido_u2f_attestation(self): att = AttestationObject.from_ctap1( a2b_hex( b"1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE" ), RegistrationData( a2b_hex( b"0504E87625896EE4E46DC032766E8087962F36DF9DFE8B567F3763015B1990A60E1427DE612D66418BDA1950581EBC5C8C1DAD710CB14C22F8C97045F4612FB20C91403EBD89BF77EC509755EE9C2635EFAAAC7B2B9C5CEF1736C3717DA48534C8C6B654D7FF945F50B5CC4E78055BDD396B64F78DA2C5F96200CCD415CD08FE4200383082024A30820132A0030201020204046C8822300D06092A864886F70D01010B0500302E312C302A0603550403132359756269636F2055324620526F6F742043412053657269616C203435373230303633313020170D3134303830313030303030305A180F32303530303930343030303030305A302C312A302806035504030C2159756269636F205532462045452053657269616C203234393138323332343737303059301306072A8648CE3D020106082A8648CE3D030107034200043CCAB92CCB97287EE8E639437E21FCD6B6F165B2D5A3F3DB131D31C16B742BB476D8D1E99080EB546C9BBDF556E6210FD42785899E78CC589EBE310F6CDB9FF4A33B3039302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C020101040403020430300D06092A864886F70D01010B050003820101009F9B052248BC4CF42CC5991FCAABAC9B651BBE5BDCDC8EF0AD2C1C1FFB36D18715D42E78B249224F92C7E6E7A05C49F0E7E4C881BF2E94F45E4A21833D7456851D0F6C145A29540C874F3092C934B43D222B8962C0F410CEF1DB75892AF116B44A96F5D35ADEA3822FC7146F6004385BCB69B65C99E7EB6919786703C0D8CD41E8F75CCA44AA8AB725AD8E799FF3A8696A6F1B2656E631B1E40183C08FDA53FA4A8F85A05693944AE179A1339D002D15CABD810090EC722EF5DEF9965A371D415D624B68A2707CAD97BCDD1785AF97E258F33DF56A031AA0356D8E8D5EBCADC74E071636C6B110ACE5CC9B90DFEACAE640FF1BB0F1FE5DB4EFF7A95F060733F530450220324779C68F3380288A1197B6095F7A6EB9B1B1C127F66AE12A99FE8532EC23B9022100E39516AC4D61EE64044D50B415A6A4D4D84BA6D895CB5AB7A1AA7D081DE341FA" # noqa E501 ) ), ) Attestation.for_type(att.fmt)().verify( att.att_statement, att.auth_data, a2b_hex( b"687134968222EC17202E42505F8ED2B16AE22F16BB05B88C25DB9E602645F141" ), ) def test_packed_attestation(self): att = AttestationObject( a2b_hex( b"a301667061636b65640258c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae124100000003f8a011f38c0a4d15800617111f9edc7d004060a386206a3aacecbdbb22d601853d955fdc5d11adfbd1aa6a950d966b348c7663d40173714a9f987df6461beadfb9cd6419ffdfe4d4cf2eec1aa605a4f59bdaa50102032620012158200edb27580389494d74d2373b8f8c2e8b76fa135946d4f30d0e187e120b423349225820e03400d189e85a55de9ab0f538ed60736eb750f5f0306a80060fe1b13010560d03a363616c6726637369675847304502200d15daf337d727ab4719b4027114a2ac43cd565d394ced62c3d9d1d90825f0b3022100989615e7394c87f4ad91f8fdae86f7a3326df332b3633db088aac76bffb9a46b63783563815902bb308202b73082019fa00302010202041d31330d300d06092a864886f70d01010b0500302a3128302606035504030c1f59756269636f2050726576696577204649444f204174746573746174696f6e301e170d3138303332383036333932345a170d3139303332383036333932345a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203438393736333539373059301306072a8648ce3d020106082a8648ce3d030107034200047d71e8367cafd0ea6cf0d61e4c6a416ba5bb6d8fad52db2389ad07969f0f463bfdddddc29d39d3199163ee49575a3336c04b3309d607f6160c81e023373e0197a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e323013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c01010404120410f8a011f38c0a4d15800617111f9edc7d300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101009b904ceadbe1f1985486fead02baeaa77e5ab4e6e52b7e6a2666a4dc06e241578169193b63dadec5b2b78605a128b2e03f7fe2a98eaeb4219f52220995f400ce15d630cf0598ba662d7162459f1ad1fc623067376d4e4091be65ac1a33d8561b9996c0529ec1816d1710786384d5e8783aa1f7474cb99fe8f5a63a79ff454380361c299d67cb5cc7c79f0d8c09f8849b0500f6d625408c77cbbc26ddee11cb581beb7947137ad4f05aaf38bd98da10042ddcac277604a395a5b3eaa88a5c8bb27ab59c8127d59d6bbba5f11506bf7b75fda7561a0837c46f025fd54dcf1014fc8d17c859507ac57d4b1dea99485df0ba8f34d00103c3eef2ef3bbfec7a6613de" # noqa E501 ) ) Attestation.for_type(att.fmt)().verify( att.att_statement, att.auth_data, a2b_hex( b"985B6187D042FB1258892ED637CEC88617DDF5F6632351A545617AA2B75261BF" ), ) def test_different_keys(self): att = AttestationObject( a2b_hex( b"a363666d74667061636b65646761747453746d74a363616c6726637369675847304502200d15daf337d727ab4719b4027114a2ac43cd565d394ced62c3d9d1d90825f0b3022100989615e7394c87f4ad91f8fdae86f7a3326df332b3633db088aac76bffb9a46b63783563815902bb308202b73082019fa00302010202041d31330d300d06092a864886f70d01010b0500302a3128302606035504030c1f59756269636f2050726576696577204649444f204174746573746174696f6e301e170d3138303332383036333932345a170d3139303332383036333932345a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203438393736333539373059301306072a8648ce3d020106082a8648ce3d030107034200047d71e8367cafd0ea6cf0d61e4c6a416ba5bb6d8fad52db2389ad07969f0f463bfdddddc29d39d3199163ee49575a3336c04b3309d607f6160c81e023373e0197a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e323013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c01010404120410f8a011f38c0a4d15800617111f9edc7d300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101009b904ceadbe1f1985486fead02baeaa77e5ab4e6e52b7e6a2666a4dc06e241578169193b63dadec5b2b78605a128b2e03f7fe2a98eaeb4219f52220995f400ce15d630cf0598ba662d7162459f1ad1fc623067376d4e4091be65ac1a33d8561b9996c0529ec1816d1710786384d5e8783aa1f7474cb99fe8f5a63a79ff454380361c299d67cb5cc7c79f0d8c09f8849b0500f6d625408c77cbbc26ddee11cb581beb7947137ad4f05aaf38bd98da10042ddcac277604a395a5b3eaa88a5c8bb27ab59c8127d59d6bbba5f11506bf7b75fda7561a0837c46f025fd54dcf1014fc8d17c859507ac57d4b1dea99485df0ba8f34d00103c3eef2ef3bbfec7a6613de68617574684461746158c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae124100000003f8a011f38c0a4d15800617111f9edc7d004060a386206a3aacecbdbb22d601853d955fdc5d11adfbd1aa6a950d966b348c7663d40173714a9f987df6461beadfb9cd6419ffdfe4d4cf2eec1aa605a4f59bdaa50102032620012158200edb27580389494d74d2373b8f8c2e8b76fa135946d4f30d0e187e120b423349225820e03400d189e85a55de9ab0f538ed60736eb750f5f0306a80060fe1b13010560d" # noqa E501 ) ) Attestation.for_type(att.fmt)().verify( att.att_statement, att.auth_data, a2b_hex( b"985B6187D042FB1258892ED637CEC88617DDF5F6632351A545617AA2B75261BF" ), ) att2 = att.with_int_keys() self.assertNotEqual(att, att2) self.assertEqual(att.data, att2.data) self.assertEqual(att.with_int_keys(), att2) self.assertEqual(att, att2.with_string_keys()) class TestCTAP2(unittest.TestCase): def test_send_cbor_ok(self): ctap = CTAP2(mock.MagicMock()) 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 = CTAP2(mock.MagicMock()) ctap.device.call.return_value = b"\0" + _INFO 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 = CTAP2(mock.MagicMock()) 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, AttestationObject) self.assertEqual(resp, _MC_RESP) self.assertEqual(resp.fmt, "packed") self.assertEqual(resp.auth_data, _AUTH_DATA_MC) self.assertSetEqual(set(resp.att_statement.keys()), {"alg", "sig", "x5c"}) def test_get_assertion(self): ctap = CTAP2(mock.MagicMock()) 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, _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 = a2b_hex("44D78D7989B97E62EA993496C9EF6E8FD58B8B00715F9A89153DDD9C4657E47F") EC_PUB_Y = a2b_hex("EC802EE7D22BD4E100F12E48537EB4E7E96ED3A47A0A3BD5F5EEAB65001664F9") DEV_PUB_X = a2b_hex("0501D5BC78DA9252560A26CB08FCC60CBE0B6D3B8E1D1FCEE514FAC0AF675168") DEV_PUB_Y = a2b_hex("D551B3ED46F665731F95B4532939C25D91DB7EB844BD96D4ABD4083785F8DF47") SHARED = a2b_hex("c42a039d548100dfba521e487debcbbb8b66bb7496f8b1862a7a395ed83e1a1c") TOKEN_ENC = a2b_hex("7A9F98E31B77BE90F9C64D12E9635040") TOKEN = a2b_hex("aff12c6dcfbf9df52f7a09211e8865cd") PIN_HASH_ENC = a2b_hex("afe8327ce416da8ee3d057589c2ce1a9") class TestPinProtocolV1(unittest.TestCase): @mock.patch("cryptography.hazmat.primitives.asymmetric.ec.generate_private_key") def test_establish_shared_secret(self, patched_generate): prot = PinProtocolV1(mock.MagicMock()) patched_generate.return_value = ec.derive_private_key( EC_PRIV, ec.SECP256R1(), default_backend() ) prot.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): prot = PinProtocolV1(mock.MagicMock()) 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): prot = PinProtocolV1(mock.MagicMock()) 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=a2b_hex( "0222fc42c6dd76a274a7057858b9b29d98e8a722ec2dc6668476168c5320473cec9907b4cd76ce7943c96ba5683943211d84471e64d9c51e54763488cd66526a" # noqa E501 ), pin_auth=a2b_hex("7b40c084ccc5794194189ab57836475f"), ) def test_change_pin(self): prot = PinProtocolV1(mock.MagicMock()) 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=a2b_hex( "4280e14aac4fcbf02dd079985f0c0ffc9ea7d5f9c173fd1a4c843826f7590cb3c2d080c6923e2fe6d7a52c31ea1309d3fcca3dedae8a2ef14b6330cafc79339e" # noqa E501 ), pin_auth=a2b_hex("fb97e92f3724d7c85e001d7f93e6490a"), pin_hash_enc=a2b_hex("afe8327ce416da8ee3d057589c2ce1a9"), ) def test_short_pin(self): prot = PinProtocolV1(mock.MagicMock()) with self.assertRaises(ValueError): prot.set_pin("123") def test_long_pin(self): prot = PinProtocolV1(mock.MagicMock()) with self.assertRaises(ValueError): prot.set_pin("1" * 256) fido2-0.8.1/test/test_hid.py0000644000175000017500000000707513544577440015555 0ustar daindain00000000000000# 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.ctap import CtapError from fido2.hid import CtapHidDevice import unittest import mock 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) def test_call_error(self): dev = mock.Mock() hid_dev = CtapHidDevice(None, dev) dev.InternalRecv = mock.Mock(return_value=(0xBF, bytearray([7]))) try: hid_dev.call(0x01) self.fail("call did not raise exception") except CtapError as e: self.assertEqual(e.code, 7) def test_call_keepalive(self): dev = mock.Mock() hid_dev = CtapHidDevice(None, dev) on_keepalive = mock.MagicMock() dev.InternalRecv = mock.Mock( side_effect=[ (0xBB, bytearray([0])), (0xBB, bytearray([0])), (0xBB, bytearray([0])), (0xBB, bytearray([0])), (0x81, bytearray(b"done")), ] ) self.assertEqual(hid_dev.call(0x01, on_keepalive=on_keepalive), b"done") on_keepalive.assert_called_once_with(0) dev.InternalRecv.side_effect = [ (0xBB, bytearray([1])), (0xBB, bytearray([0])), (0xBB, bytearray([0])), (0xBB, bytearray([1])), (0xBB, bytearray([1])), (0xBB, bytearray([1])), (0xBB, bytearray([1])), (0xBB, bytearray([0])), (0x81, bytearray(b"done")), ] on_keepalive.reset_mock() self.assertEqual(hid_dev.call(0x01, on_keepalive=on_keepalive), b"done") self.assertEqual( on_keepalive.call_args_list, [mock.call(1), mock.call(0), mock.call(1), mock.call(0)], ) fido2-0.8.1/test/test_pcsc.py0000644000175000017500000000630213544577440015731 0ustar daindain00000000000000# Copyright (c) 2019 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals import unittest import mock import sys 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.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) fido2-0.8.1/test/test_rpid.py0000644000175000017500000001326713565753506015751 0ustar daindain00000000000000# 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 __future__ import absolute_import, unicode_literals from fido2.rpid import verify_app_id, verify_rp_id import unittest 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_valid_ids_mixed_type(self): self.assertTrue( verify_app_id(b"https://example.com", "https://register.example.com") ) self.assertTrue( verify_app_id("https://example.com", b"https://fido.example.com") ) self.assertTrue( verify_app_id(b"https://example.com", b"https://www.example.com:444") ) 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_invalid_ids_mixed_type(self): self.assertFalse(verify_app_id(b"https://example.com", "http://example.com")) self.assertFalse( verify_app_id("https://example.com", b"http://www.example.com") ) self.assertFalse( verify_app_id(b"https://example.com", b"https://example-test.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 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_valid_ids_mixed_type(self): self.assertTrue(verify_rp_id(b"example.com", "https://register.example.com")) self.assertTrue(verify_rp_id("example.com", b"https://fido.example.com")) self.assertTrue(verify_rp_id(b"example.com", b"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_invalid_ids_mixed_type(self): self.assertFalse(verify_rp_id(b"example.com", "http://example.com")) self.assertFalse(verify_rp_id("example.com", b"http://www.example.com")) self.assertFalse(verify_rp_id(b"example.com", b"https://example-test.com")) def test_suffix_list(self): self.assertFalse(verify_rp_id(b"co.uk", "https://foobar.co.uk")) self.assertTrue(verify_rp_id(b"foobar.co.uk", "https://site.foobar.co.uk")) self.assertFalse(verify_rp_id(b"appspot.com", "https://example.appspot.com")) self.assertTrue( verify_rp_id(b"example.appspot.com", "https://example.appspot.com") ) fido2-0.8.1/test/test_server.py0000644000175000017500000001457013564737350016315 0ustar daindain00000000000000from __future__ import absolute_import, unicode_literals import json import unittest from binascii import a2b_hex import six from fido2.client import WEBAUTHN_TYPE, ClientData from fido2.ctap2 import AttestedCredentialData, AuthenticatorData from fido2.server import Fido2Server, U2FFido2Server from fido2.webauthn import PublicKeyCredentialRpEntity, UserVerificationRequirement from .test_ctap2 import _ATT_CRED_DATA, _CRED_ID from .utils import U2FDevice class TestPublicKeyCredentialRpEntity(unittest.TestCase): def test_id_hash(self): rp = PublicKeyCredentialRpEntity("example.com", "Example") 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_no_icon(self): rp = PublicKeyCredentialRpEntity("example.com", "Example") server = Fido2Server(rp) request, state = server.register_begin(USER) self.assertEqual( request["publicKey"]["rp"], {"id": "example.com", "name": "Example"} ) def test_register_begin_rp_icon(self): rp = PublicKeyCredentialRpEntity( "example.com", "Example", "http://example.com/icon.svg" ) server = Fido2Server(rp) request, state = server.register_begin(USER) data = { "id": "example.com", "name": "Example", "icon": "http://example.com/icon.svg", } self.assertEqual(request["publicKey"]["rp"], data) def test_register_begin_custom_challenge(self): rp = PublicKeyCredentialRpEntity("example.com", "Example") server = Fido2Server(rp) challenge = b"1234567890123456" request, state = server.register_begin(USER, challenge=challenge) self.assertEqual(request["publicKey"]["challenge"], challenge) def test_register_begin_custom_challenge_too_short(self): rp = PublicKeyCredentialRpEntity("example.com", "Example") 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.com", "Example") server = Fido2Server(rp) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.PREFERRED, } client_data_dict = { "challenge": "GAZPACHO!", "origin": "https://example.com", "type": WEBAUTHN_TYPE.GET_ASSERTION, } client_data = ClientData(json.dumps(client_data_dict).encode("utf-8")) _AUTH_DATA = a2b_hex( "A379A6F6EEAFB9A55E378C118034E2751E682FAB9F2D30AB13D2125586CE1947010000001D" ) with six.assertRaisesRegex(self, 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.com", "Example", "http://example.com/icon.svg" ) 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_dict = { "challenge": "GAZPACHO!", "origin": "https://example.com", "type": WEBAUTHN_TYPE.GET_ASSERTION, } client_data = ClientData(json.dumps(client_data_dict).encode("utf-8")) 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.com", "Example", "http://example.com/icon.svg" ) 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_dict = { "challenge": "GAZPACHO!", "origin": "https://oauth.example.com", "type": WEBAUTHN_TYPE.GET_ASSERTION, } client_data = ClientData(json.dumps(client_data_dict).encode("utf-8")) 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_dict = { "challenge": "GAZPACHO!", "origin": "https://publicthingy.example.com", "type": WEBAUTHN_TYPE.GET_ASSERTION, } client_data = ClientData(json.dumps(client_data_dict).encode("utf-8")) authenticator_data, signature = device.sign(client_data) with six.assertRaisesRegex( self, ValueError, "Invalid origin in " "ClientData." ): server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, ) fido2-0.8.1/test/test_tpm.py0000644000175000017500000000562013554254405015575 0ustar daindain00000000000000# Copyright (c) 2019 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals from fido2._tpm import TpmAttestationFormat, TpmPublicFormat from binascii import a2b_hex import unittest class TestTpmObject(unittest.TestCase): def test_parse_tpm(self): data = a2b_hex( "ff54434780170022000b68cec627cc6411099a1f809fde4379f649aa170c7072d1adf230de439efc80810014f7c8b0cdeb31328648130a19733d6fff16e76e1300000003ef605603446ed8c56aa7608d01a6ea5651ee67a8a20022000bdf681917e18529c61e1b85a1e7952f3201eb59c609ed5d8e217e5de76b228bbd0022000b0a10d216b0c3ab82bfdc1f0a016ab9493384c7aee1937ee8800f76b30c9b71a7" # noqa ) tpm = TpmAttestationFormat.parse(data) self.assertEqual(tpm.data, a2b_hex("f7c8b0cdeb31328648130a19733d6fff16e76e13")) def test_parse_too_short_of_a_tpm(self): with self.assertRaises(ValueError): TpmAttestationFormat.parse(a2b_hex("ff5443")) with self.assertRaises(ValueError) as e: data = a2b_hex( "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 = a2b_hex( "0023000b00060472000000100010000300100020b9174cd199f77552afcffe6b1f069c032ffdc4f56068dec4e189e7967b3bf6b0002037bf8aa7d93fddb9507319141c6fa31c8e48a1c6da013603a9f6e3913d157c66" # noqa ) TpmPublicFormat.parse(data) fido2-0.8.1/test/test_utils.py0000644000175000017500000000715213564737350016145 0ustar daindain00000000000000# 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 binascii import a2b_hex 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"), a2b_hex( b"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" ), ) self.assertEqual( sha256(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), a2b_hex( b"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" ), ) class TestHmacSha256(unittest.TestCase): def test_hmac_sha256_vectors(self): self.assertEqual( hmac_sha256(b"\x0b" * 20, b"Hi There"), a2b_hex( b"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7" ), ) self.assertEqual( hmac_sha256(b"Jefe", b"what do ya want for nothing?"), a2b_hex( b"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(u""), b"") self.assertEqual(websafe_decode(u"Zm9vYmFy"), b"foobar") def test_websafe_encode(self): self.assertEqual(websafe_encode(b""), u"") self.assertEqual(websafe_encode(b"f"), u"Zg") self.assertEqual(websafe_encode(b"fo"), u"Zm8") self.assertEqual(websafe_encode(b"foo"), u"Zm9v") self.assertEqual(websafe_encode(b"foob"), u"Zm9vYg") self.assertEqual(websafe_encode(b"fooba"), u"Zm9vYmE") self.assertEqual(websafe_encode(b"foobar"), u"Zm9vYmFy") fido2-0.8.1/test/test_webauthn.py0000644000175000017500000002023413565774314016620 0ustar daindain00000000000000# Copyright (c) 2019 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals from fido2.webauthn import ( AuthenticatorSelectionCriteria, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, PublicKeyCredentialParameters, PublicKeyCredentialDescriptor, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, ) import unittest class TestWebAuthnDataTypes(unittest.TestCase): def test_authenticator_selection_criteria(self): o = AuthenticatorSelectionCriteria("platform", True, "required") self.assertEqual( o, { "authenticatorAttachment": "platform", "requireResidentKey": True, "userVerification": "required", }, ) self.assertEqual(o.authenticator_attachment, "platform") self.assertEqual(o.require_resident_key, True) self.assertEqual(o.user_verification, "required") with self.assertRaises(ValueError): AuthenticatorSelectionCriteria(authenticator_attachment="invalid") with self.assertRaises(ValueError): AuthenticatorSelectionCriteria(user_verification="invalid") o = AuthenticatorSelectionCriteria() self.assertEqual(o, {}) self.assertIsNone(o.authenticator_attachment) self.assertIsNone(o.require_resident_key) self.assertIsNone(o.user_verification) def test_rp_entity(self): o = PublicKeyCredentialRpEntity("example.com", "Example") self.assertEqual(o, {"id": "example.com", "name": "Example"}) self.assertEqual(o.id, "example.com") self.assertEqual(o.name, "Example") self.assertIsNone(o.icon) with self.assertRaises(TypeError): PublicKeyCredentialRpEntity("example.com") with self.assertRaises(TypeError): PublicKeyCredentialRpEntity() def test_user_entity(self): o = PublicKeyCredentialUserEntity(b"user", "Example", 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") self.assertIsNone(o.icon) with self.assertRaises(TypeError): PublicKeyCredentialUserEntity(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) with self.assertRaises(ValueError): PublicKeyCredentialParameters("invalid-type", -7) 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": 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": b"credential_id", "transports": ["usb", "nfc"], }, ) self.assertEqual(o.transports, ["usb", "nfc"]) PublicKeyCredentialDescriptor("public-key", b"credential_id", ["valid_value"]) with self.assertRaises(ValueError): PublicKeyCredentialDescriptor("wrong-type", b"credential_id") with self.assertRaises(TypeError): PublicKeyCredentialDescriptor("wrong-type") with self.assertRaises(TypeError): PublicKeyCredentialDescriptor() 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", "requireResidentKey": True, "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) 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) with self.assertRaises(ValueError): PublicKeyCredentialCreationOptions( {"id": "example.com", "name": "Example"}, {"id": b"user_id", "name": "A. User"}, b"request_challenge", [{"type": "public-key", "alg": -7}], attestation="invalid", ) 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) with self.assertRaises(ValueError): PublicKeyCredentialRequestOptions( b"request_challenge", user_verification="invalid" ) def test_update_value(self): o = PublicKeyCredentialRpEntity("example.com", "Example") self.assertEqual(o, {"id": "example.com", "name": "Example"}) self.assertEqual(o.id, "example.com") self.assertEqual(o.name, "Example") o.id = "new-id.com" self.assertEqual(o, {"id": "new-id.com", "name": "Example"}) self.assertEqual(o.id, "new-id.com") o["name"] = "New Name" self.assertEqual(o, {"id": "new-id.com", "name": "New Name"}) self.assertEqual(o.name, "New Name") fido2-0.8.1/test/utils.py0000644000175000017500000000401713544577440015103 0ustar daindain00000000000000import six from binascii import a2b_hex from 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.ctap2 import AuthenticatorData class U2FDevice(object): _priv_key_bytes = a2b_hex( "308184020100301006072a8648ce3d020106052b8104000a046d306b02010104201672f5ceb963e07d475f5db9a975b7ad42ac3bf71ccddfb6539cc651a1156a6ba144034200045a4be44eb94eebff3ed665dde31deb74a060fabd214c5f5713aa043efa805dac8f45e0e17afe2eafbd1802c413c1e485fd854af9f06ef20938398f6795f12e0e" # noqa E501 ) def __init__(self, credential_id, app_id): assert isinstance(credential_id, six.binary_type) assert isinstance(app_id, six.binary_type) # 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.USER_PRESENT, counter=0 ) signature = self.priv_key.sign( authenticator_data + client_data.hash, ec.ECDSA(hashes.SHA256()) ) return authenticator_data, signature fido2-0.8.1/test/_pyu2f/0000755000175000017500000000000013566742204014567 5ustar daindain00000000000000fido2-0.8.1/test/_pyu2f/freebsd_test.py0000644000175000017500000001025713544600444017612 0ustar daindain00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # 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. """Tests for _pyu2f.hid.freebsd.""" import base64 import os import sys import six from six.moves import builtins from mock import patch if sys.platform.startswith('freebsd'): from fido2._pyu2f import freebsd if sys.version_info[:2] < (2, 7): import unittest2 as unittest # pylint: disable=g-import-not-at-top else: import unittest # pylint: disable=g-import-not-at-top # These are base64 encoded report descriptors of a Yubico token # and a Logitech keyboard. YUBICO_RD = 'BtDxCQGhAQkgFQAm/wB1CJVAgQIJIRUAJv8AdQiVQJECwA==' KEYBOARD_RD = ( 'BQEJAqEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVAXUDgQEFAQkwCTEJOBWBJX91CJUDgQbAwA==') class FakeUhidFreeBSDModule(): def enumerate(self): return [{'device': 'uhid0', 'path': '/dev/uhid0', 'vendor_id': 0x046d, 'product_id': 0xc31c, 'product_desc': 'Logitech Keyboard'}, {'device': 'uhid1', 'path': '/dev/uhid1', 'vendor_id': 0x1050, 'product_id': 0x0407, 'product_desc': 'Yubico U2F'}] def get_report_data(self, fd, unused_report_id): if fd: return base64.b64decode(YUBICO_RD) else: return base64.b64decode(KEYBOARD_RD) class FakeOsModule(): O_RDONLY = os.O_RDONLY O_RDWR = os.O_RDWR path = os.path data_written = None data_to_return = None def open(self, path, unused_opts): # pylint: disable=invalid-name if path.find('uhid1') >= 0: return 1 # fd == 1 => magic number to return Yubikey RD below else: return 0 def write(self, unused_dev, data): # pylint: disable=invalid-name self.data_written = data def read(self, unused_dev, unused_length): # pylint: disable=invalid-name return self.data_to_return def close(self, unused_dev): # pylint: disable=invalid-name pass @unittest.skipIf(not sys.platform.startswith('freebsd'), 'FreeBSD specific test case') class FreeBSDTest(unittest.TestCase): @patch('fido2._pyu2f.freebsd.os', FakeOsModule()) @patch('fido2._pyu2f.freebsd.uhid_freebsd', FakeUhidFreeBSDModule()) def testCallEnumerate(self): devs = list(freebsd.FreeBSDHidDevice.Enumerate()) devs = sorted(devs, key=lambda k: k['vendor_id']) self.assertEqual(len(devs), 2) self.assertEqual(devs[0]['vendor_id'], 0x046d) self.assertEqual(devs[0]['product_id'], 0xc31c) self.assertEqual(devs[1]['vendor_id'], 0x1050) self.assertEqual(devs[1]['product_id'], 0x0407) self.assertEqual(devs[1]['usage_page'], 0xf1d0) self.assertEqual(devs[1]['usage'], 1) @patch('fido2._pyu2f.freebsd.uhid_freebsd', FakeUhidFreeBSDModule()) def testCallOpen(self): fake_os = FakeOsModule() with patch('fido2._pyu2f.linux.os', fake_os): with patch('fido2._pyu2f.freebsd.os', fake_os): dev = freebsd.FreeBSDHidDevice('/dev/uhid1') self.assertEqual(dev.GetInReportDataLength(), 64) self.assertEqual(dev.GetOutReportDataLength(), 64) dev.Write(list(range(0, 64))) # The HidDevice implementation prepends one zero-byte-packet # (64 bytes) representing the report ID + padding self.assertEqual(list(six.iterbytes(fake_os.data_written)), [0]*64 + list(range(0, 64))) fake_os.data_to_return = b'x' * 64 self.assertEqual(dev.Read(), [120] * 64) # chr(120) = 'x' if __name__ == '__main__': unittest.main() fido2-0.8.1/test/_pyu2f/hidtransport_test.py0000644000175000017500000001431413544600444020717 0ustar daindain00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # 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. """Tests for _pyu2f.hidtransport.""" from __future__ import absolute_import import six import mock from fido2._pyu2f import hidtransport from . import util import unittest def MakeKeyboard(path, usage): d = {} d['vendor_id'] = 1133 # Logitech d['product_id'] = 49948 d['path'] = path d['usage'] = usage d['usage_page'] = 1 return d def MakeU2F(path): d = {} d['vendor_id'] = 4176 d['product_id'] = 1031 d['path'] = path d['usage'] = 1 d['usage_page'] = 0xf1d0 return d def RPad(collection, to_size): while len(collection) < to_size: collection.append(0) return collection class DiscoveryTest(unittest.TestCase): def testHidUsageSelector(self): u2f_device = {'usage_page': 0xf1d0, 'usage': 0x01} other_device = {'usage_page': 0x06, 'usage': 0x01} self.assertTrue(hidtransport.HidUsageSelector(u2f_device)) self.assertFalse(hidtransport.HidUsageSelector(other_device)) def testDiscoverLocalDevices(self): def FakeHidDevice(path): mock_hid_dev = mock.MagicMock() mock_hid_dev.GetInReportDataLength.return_value = 64 mock_hid_dev.GetOutReportDataLength.return_value = 64 mock_hid_dev.path = path return mock_hid_dev with mock.patch.object(hidtransport, 'hid') as hid_mock: # We mock out init so that it doesn't try to do the whole init # handshake with the device, which isn't what we're trying to test # here. with mock.patch.object(hidtransport.UsbHidTransport, 'InternalInit') as _: hid_mock.Enumerate.return_value = [ MakeKeyboard('path1', 6), MakeKeyboard('path2', 2), MakeU2F('path3'), MakeU2F('path4') ] mock_hid_dev = mock.MagicMock() mock_hid_dev.GetInReportDataLength.return_value = 64 mock_hid_dev.GetOutReportDataLength.return_value = 64 hid_mock.Open.side_effect = FakeHidDevice # Force the iterator into a list devs = list(hidtransport.DiscoverLocalHIDU2FDevices()) self.assertEqual(hid_mock.Enumerate.call_count, 1) self.assertEqual(hid_mock.Open.call_count, 2) self.assertEqual(len(devs), 2) self.assertEqual(devs[0].hid_device.path, 'path3') self.assertEqual(devs[1].hid_device.path, 'path4') class TransportTest(unittest.TestCase): def testInit(self): fake_hid_dev = util.FakeHidDevice(bytearray([0x00, 0x00, 0x00, 0x01])) t = hidtransport.UsbHidTransport(fake_hid_dev) self.assertEqual(t.cid, bytearray([0x00, 0x00, 0x00, 0x01])) self.assertEqual(t.u2fhid_version, 0x01) def testPing(self): fake_hid_dev = util.FakeHidDevice(bytearray([0x00, 0x00, 0x00, 0x01])) t = hidtransport.UsbHidTransport(fake_hid_dev) reply = t.SendPing(b'1234') self.assertEqual(reply, b'1234') def testMsg(self): fake_hid_dev = util.FakeHidDevice( bytearray([0x00, 0x00, 0x00, 0x01]), bytearray([0x01, 0x90, 0x00])) t = hidtransport.UsbHidTransport(fake_hid_dev) reply = t.SendMsgBytes([0x00, 0x01, 0x00, 0x00]) self.assertEqual(reply, bytearray([0x01, 0x90, 0x00])) def testMsgBusy(self): fake_hid_dev = util.FakeHidDevice( bytearray([0x00, 0x00, 0x00, 0x01]), bytearray([0x01, 0x90, 0x00])) t = hidtransport.UsbHidTransport(fake_hid_dev) # Each call will retry twice: the first attempt will fail after 2 retreis, # the second will succeed on the second retry. fake_hid_dev.SetChannelBusyCount(3) with mock.patch.object(hidtransport, 'time') as _: six.assertRaisesRegex(self, OSError, '^Device Busy', t.SendMsgBytes, [0x00, 0x01, 0x00, 0x00]) reply = t.SendMsgBytes([0x00, 0x01, 0x00, 0x00]) self.assertEqual(reply, bytearray([0x01, 0x90, 0x00])) def testFragmentedResponseMsg(self): body = bytearray([x % 256 for x in range(0, 1000)]) fake_hid_dev = util.FakeHidDevice(bytearray([0x00, 0x00, 0x00, 0x01]), body) t = hidtransport.UsbHidTransport(fake_hid_dev) reply = t.SendMsgBytes([0x00, 0x01, 0x00, 0x00]) # Confirm we properly reassemble the message self.assertEqual(reply, bytearray(x % 256 for x in range(0, 1000))) def testFragmentedSendApdu(self): body = bytearray(x % 256 for x in range(0, 1000)) fake_hid_dev = util.FakeHidDevice( bytearray([0x00, 0x00, 0x00, 0x01]), [0x90, 0x00]) t = hidtransport.UsbHidTransport(fake_hid_dev) reply = t.SendMsgBytes(body) self.assertEqual(reply, bytearray([0x90, 0x00])) # 1 init packet from creating transport, 18 packets to send # the fragmented message self.assertEqual(len(fake_hid_dev.received_packets), 18) def testInitPacketShape(self): packet = hidtransport.UsbHidTransport.InitPacket( 64, bytearray(b'\x00\x00\x00\x01'), 0x83, 2, bytearray(b'\x01\x02')) self.assertEqual(packet.ToWireFormat(), RPad( [0, 0, 0, 1, 0x83, 0, 2, 1, 2], 64)) copy = hidtransport.UsbHidTransport.InitPacket.FromWireFormat( 64, packet.ToWireFormat()) self.assertEqual(copy.cid, bytearray(b'\x00\x00\x00\x01')) self.assertEqual(copy.cmd, 0x83) self.assertEqual(copy.size, 2) self.assertEqual(copy.payload, bytearray(b'\x01\x02')) def testContPacketShape(self): packet = hidtransport.UsbHidTransport.ContPacket( 64, bytearray(b'\x00\x00\x00\x01'), 5, bytearray(b'\x01\x02')) self.assertEqual(packet.ToWireFormat(), RPad([0, 0, 0, 1, 5, 1, 2], 64)) copy = hidtransport.UsbHidTransport.ContPacket.FromWireFormat( 64, packet.ToWireFormat()) self.assertEqual(copy.cid, bytearray(b'\x00\x00\x00\x01')) self.assertEqual(copy.seq, 5) self.assertEqual(copy.payload, RPad(bytearray(b'\x01\x02'), 59)) if __name__ == '__main__': unittest.main() fido2-0.8.1/test/_pyu2f/linux_test.py0000644000175000017500000001104313544600444017331 0ustar daindain00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # 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. """Tests for _pyu2f.hid.linux.""" import base64 import os import sys import mock import six from six.moves import builtins from fido2._pyu2f import linux try: from pyfakefs import fake_filesystem # pylint: disable=g-import-not-at-top except ImportError: try: from fakefs import fake_filesystem # pylint: disable=g-import-not-at-top except ImportError: if sys.platform.startswith('linux'): raise if sys.version_info[:2] < (2, 7): import unittest2 as unittest # pylint: disable=g-import-not-at-top else: import unittest # pylint: disable=g-import-not-at-top # These are base64 encoded report descriptors of a Yubico token # and a Logitech keyboard. YUBICO_RD = 'BtDxCQGhAQkgFQAm/wB1CJVAgQIJIRUAJv8AdQiVQJECwA==' KEYBOARD_RD = ( 'BQEJAqEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVAXUDgQEFAQkwCTEJOBWBJX91CJUDgQbAwA==') def AddDevice(fs, dev_name, product_name, vendor_id, product_id, report_descriptor_b64): uevent = fs.create_file('/sys/class/hidraw/%s/device/uevent' % dev_name) rd = fs.create_file('/sys/class/hidraw/%s/device/report_descriptor' % dev_name) report_descriptor = base64.b64decode(report_descriptor_b64) rd.set_contents(report_descriptor) buf = 'HID_NAME=%s\n' % product_name.encode('utf8') buf += 'HID_ID=0001:%08X:%08X\n' % (vendor_id, product_id) uevent.set_contents(buf) class FakeDeviceOsModule(object): O_RDWR = os.O_RDWR path = os.path data_written = None data_to_return = None def open(self, unused_path, unused_opts): # pylint: disable=invalid-name return 0 def write(self, unused_dev, data): # pylint: disable=invalid-name self.data_written = data def read(self, unused_dev, unused_length): # pylint: disable=invalid-name return self.data_to_return @unittest.skipIf(not sys.platform.startswith('linux'), 'Linux specific test case') class LinuxTest(unittest.TestCase): def setUp(self): self.fs = fake_filesystem.FakeFilesystem() self.fs.create_dir('/sys/class/hidraw') def tearDown(self): self.fs.remove_object('/sys/class/hidraw') def testCallEnumerate(self): AddDevice(self.fs, 'hidraw1', 'Logitech USB Keyboard', 0x046d, 0xc31c, KEYBOARD_RD) AddDevice(self.fs, 'hidraw2', 'Yubico U2F', 0x1050, 0x0407, YUBICO_RD) with mock.patch.object(linux, 'os', fake_filesystem.FakeOsModule(self.fs)): fake_open = fake_filesystem.FakeFileOpen(self.fs) with mock.patch.object(builtins, 'open', fake_open): devs = list(linux.LinuxHidDevice.Enumerate()) devs = sorted(devs, key=lambda k: k['vendor_id']) self.assertEqual(len(devs), 2) self.assertEqual(devs[0]['vendor_id'], 0x046d) self.assertEqual(devs[0]['product_id'], 0x0c31c) self.assertEqual(devs[1]['vendor_id'], 0x1050) self.assertEqual(devs[1]['product_id'], 0x0407) self.assertEqual(devs[1]['usage_page'], 0xf1d0) self.assertEqual(devs[1]['usage'], 1) def testCallOpen(self): AddDevice(self.fs, 'hidraw1', 'Yubico U2F', 0x1050, 0x0407, YUBICO_RD) fake_open = fake_filesystem.FakeFileOpen(self.fs) # The open() builtin is used to read from the fake sysfs with mock.patch.object(builtins, 'open', fake_open): # The os.open function is used to interact with the low # level device. We don't want to use fakefs for that. fake_dev_os = FakeDeviceOsModule() with mock.patch.object(linux, 'os', fake_dev_os): dev = linux.LinuxHidDevice('/dev/hidraw1') self.assertEqual(dev.GetInReportDataLength(), 64) self.assertEqual(dev.GetOutReportDataLength(), 64) dev.Write(list(range(0, 64))) # The HidDevice implementation prepends a zero-byte representing the # report ID self.assertEqual(list(six.iterbytes(fake_dev_os.data_written)), [0] + list(range(0, 64))) fake_dev_os.data_to_return = b'x' * 64 self.assertEqual(dev.Read(), [120] * 64) # chr(120) = 'x' if __name__ == '__main__': unittest.main() fido2-0.8.1/test/_pyu2f/macos_test.py0000644000175000017500000001175513544600444017306 0ustar daindain00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # 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. """Tests for _pyu2f.hid.macos.""" import ctypes import sys import mock from fido2._pyu2f import macos if sys.version_info[:2] < (2, 7): import unittest2 as unittest # pylint: disable=g-import-not-at-top else: import unittest # pylint: disable=g-import-not-at-top def init_mock_iokit(mock_iokit): # Device open should always return 0 (success) mock_iokit.IOHIDDeviceOpen = mock.Mock(return_value=0) mock_iokit.IOHIDDeviceSetReport = mock.Mock(return_value=0) mock_iokit.IOHIDDeviceCreate = mock.Mock(return_value='handle') def init_mock_cf(mock_cf): mock_cf.CFGetTypeID = mock.Mock(return_value=123) mock_cf.CFNumberGetTypeID = mock.Mock(return_value=123) mock_cf.CFStringGetTypeID = mock.Mock(return_value=123) def init_mock_get_int_property(mock_get_int_property): mock_get_int_property.return_value = 64 @unittest.skipIf(not sys.platform.startswith('darwin'), 'MacOS specific test case') class MacOsTest(unittest.TestCase): @classmethod def setUpClass(cls): macos.MacOsHidDevice.__del__ = lambda x: None @mock.patch.object(macos.threading, 'Thread') @mock.patch.multiple(macos, iokit=mock.DEFAULT, cf=mock.DEFAULT, GetDeviceIntProperty=mock.DEFAULT) def testInitHidDevice(self, thread, iokit, cf, GetDeviceIntProperty): # pylint: disable=invalid-name init_mock_iokit(iokit) init_mock_cf(cf) init_mock_get_int_property(GetDeviceIntProperty) device = macos.MacOsHidDevice('fakepath') self.assertEqual(64, device.GetInReportDataLength()) self.assertEqual(64, device.GetOutReportDataLength()) @mock.patch.object(macos.threading, 'Thread') @mock.patch.multiple(macos, iokit=mock.DEFAULT, cf=mock.DEFAULT, GetDeviceIntProperty=mock.DEFAULT) def testCallWriteSuccess(self, thread, iokit, cf, GetDeviceIntProperty): # pylint: disable=invalid-name init_mock_iokit(iokit) init_mock_cf(cf) init_mock_get_int_property(GetDeviceIntProperty) device = macos.MacOsHidDevice('fakepath') # Write 64 bytes to device data = bytearray(range(64)) # Write to device device.Write(data) # Validate that write calls into IOKit set_report_call_args = iokit.IOHIDDeviceSetReport.call_args self.assertIsNotNone(set_report_call_args) set_report_call_pos_args = iokit.IOHIDDeviceSetReport.call_args[0] self.assertEqual(len(set_report_call_pos_args), 5) self.assertEqual(set_report_call_pos_args[0], 'handle') self.assertEqual(set_report_call_pos_args[1], 1) self.assertEqual(set_report_call_pos_args[2], 0) self.assertEqual(set_report_call_pos_args[4], 64) report_buffer = set_report_call_pos_args[3] self.assertEqual(len(report_buffer), 64) self.assertEqual(bytearray(report_buffer), data, 'Data sent to ' 'IOHIDDeviceSetReport should match data sent to the ' 'device') @mock.patch.object(macos.threading, 'Thread') @mock.patch.multiple(macos, iokit=mock.DEFAULT, cf=mock.DEFAULT, GetDeviceIntProperty=mock.DEFAULT) def testCallWriteFailure(self, thread, iokit, cf, GetDeviceIntProperty): # pylint: disable=invalid-name init_mock_iokit(iokit) init_mock_cf(cf) init_mock_get_int_property(GetDeviceIntProperty) # Make set report call return failure (non-zero) iokit.IOHIDDeviceSetReport.return_value = -1 device = macos.MacOsHidDevice('fakepath') # Write 64 bytes to device data = bytearray(range(64)) # Write should throw an OsHidError exception with self.assertRaises(OSError): device.Write(data) @mock.patch.object(macos.threading, 'Thread') @mock.patch.multiple(macos, iokit=mock.DEFAULT, cf=mock.DEFAULT, GetDeviceIntProperty=mock.DEFAULT) def testCallReadSuccess(self, thread, iokit, cf, GetDeviceIntProperty): # pylint: disable=invalid-name init_mock_iokit(iokit) init_mock_cf(cf) init_mock_get_int_property(GetDeviceIntProperty) device = macos.MacOsHidDevice('fakepath') # Call callback for IN report report = (ctypes.c_uint8 * 64)() report[:] = range(64)[:] q = device.read_queue macos.HidReadCallback(q, None, None, None, 0, report, 64) # Device read should return the callback data read_result = device.Read() self.assertEqual(read_result, list(range(64)), 'Read data should match data ' 'passed into the callback') if __name__ == '__main__': unittest.main() fido2-0.8.1/test/_pyu2f/util.py0000644000175000017500000001236113544600445016115 0ustar daindain00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # 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. """Testing utilties for _pyu2f. Testing utilities such as a fake implementation of the pyhidapi device object that implements parts of the U2FHID frame protocol. This makes it easy to tests of higher level abstractions without having to use mock to mock out low level framing details. """ from fido2._pyu2f import base, hidtransport class UnsupportedCommandError(Exception): pass class FakeHidDevice(base.HidDevice): """Implements a fake hiddevice per the pyhidapi interface. This class implemetns a fake hiddevice that can be patched into code that uses pyhidapi to communicate with a hiddevice. This device impelents part of U2FHID protocol and can be used to test interactions with a security key. It supports arbitrary MSG replies as well as channel allocation, and ping. """ def __init__(self, cid_to_allocate, msg_reply=None): self.cid_to_allocate = cid_to_allocate self.msg_reply = msg_reply self.transaction_active = False self.full_packet_received = False self.init_packet = None self.packet_body = None self.reply = None self.seq = 0 self.received_packets = [] self.busy_count = 0 def GetInReportDataLength(self): return 64 def GetOutReportDataLength(self): return 64 def Write(self, data): """Write to the device. Writes to the fake hid device. This function is stateful: if a transaction is currently open with the client, it will continue to accumulate data for the client->device messages until the expected size is reached. Args: data: A list of integers to accept as data payload. It should be 64 bytes in length: just the report data--NO report ID. """ if len(data) < 64: data = bytearray(data) + bytearray(0 for i in range(0, 64 - len(data))) if not self.transaction_active: self.transaction_active = True self.init_packet = hidtransport.UsbHidTransport.InitPacket.FromWireFormat( 64, data) self.packet_body = self.init_packet.payload self.full_packet_received = False self.received_packets.append(self.init_packet) else: cont_packet = hidtransport.UsbHidTransport.ContPacket.FromWireFormat( 64, data) self.packet_body += cont_packet.payload self.received_packets.append(cont_packet) if len(self.packet_body) >= self.init_packet.size: self.packet_body = self.packet_body[0:self.init_packet.size] self.full_packet_received = True def Read(self): """Read from the device. Reads from the fake hid device. This function only works if a transaction is open and a complete write has taken place. If so, it will return the next reply packet. It should be called repeatedly until all expected data has been received. It always reads one report. Returns: A list of ints containing the next packet. Raises: UnsupportedCommandError: if the requested amount is not 64. """ if not self.transaction_active or not self.full_packet_received: return None ret = None if self.busy_count > 0: ret = hidtransport.UsbHidTransport.InitPacket( 64, self.init_packet.cid, hidtransport.UsbHidTransport.U2FHID_ERROR, 1, hidtransport.UsbHidTransport.ERR_CHANNEL_BUSY) self.busy_count -= 1 elif self.reply: # reply in progress next_frame = self.reply[0:59] self.reply = self.reply[59:] ret = hidtransport.UsbHidTransport.ContPacket(64, self.init_packet.cid, self.seq, next_frame) self.seq += 1 else: self.InternalGenerateReply() first_frame = self.reply[0:57] ret = hidtransport.UsbHidTransport.InitPacket( 64, self.init_packet.cid, self.init_packet.cmd, len(self.reply), first_frame) self.reply = self.reply[57:] if not self.reply: # done after this packet self.reply = None self.transaction_active = False self.seq = 0 return ret.ToWireFormat() def SetChannelBusyCount(self, busy_count): # pylint: disable=invalid-name """Mark the channel busy for next busy_count read calls.""" self.busy_count = busy_count def InternalGenerateReply(self): # pylint: disable=invalid-name if self.init_packet.cmd == hidtransport.UsbHidTransport.U2FHID_INIT: nonce = self.init_packet.payload[0:8] self.reply = nonce + self.cid_to_allocate + bytearray( b'\x01\x00\x00\x00\x00') elif self.init_packet.cmd == hidtransport.UsbHidTransport.U2FHID_PING: self.reply = self.init_packet.payload elif self.init_packet.cmd == hidtransport.UsbHidTransport.U2FHID_MSG: self.reply = self.msg_reply else: raise UnsupportedCommandError() fido2-0.8.1/test/_pyu2f/util_test.py0000644000175000017500000000451413544600445017155 0ustar daindain00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # 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. """Tests for _pyu2f.tests.lib.util.""" from __future__ import absolute_import import sys from . import util if sys.version_info[:2] < (2, 7): import unittest2 as unittest # pylint: disable=g-import-not-at-top else: import unittest # pylint: disable=g-import-not-at-top class UtilTest(unittest.TestCase): def testSimplePing(self): dev = util.FakeHidDevice(cid_to_allocate=None) dev.Write([0, 0, 0, 1, 0x81, 0, 3, 1, 2, 3]) self.assertEqual( dev.Read(), [0, 0, 0, 1, 0x81, 0, 3, 1, 2, 3] + [0 for _ in range(54)]) def testErrorBusy(self): dev = util.FakeHidDevice(cid_to_allocate=None) dev.SetChannelBusyCount(2) dev.Write([0, 0, 0, 1, 0x81, 0, 3, 1, 2, 3]) self.assertEqual( dev.Read(), [0, 0, 0, 1, 0xbf, 0, 1, 6] + [0 for _ in range(56)]) dev.Write([0, 0, 0, 1, 0x81, 0, 3, 1, 2, 3]) self.assertEqual( dev.Read(), [0, 0, 0, 1, 0xbf, 0, 1, 6] + [0 for _ in range(56)]) dev.Write([0, 0, 0, 1, 0x81, 0, 3, 1, 2, 3]) self.assertEqual( dev.Read(), [0, 0, 0, 1, 0x81, 0, 3, 1, 2, 3] + [0 for _ in range(54)]) def testFragmentedApdu(self): dev = util.FakeHidDevice(cid_to_allocate=None, msg_reply=range(85, 0, -1)) dev.Write([0, 0, 0, 1, 0x83, 0, 100] + [x for x in range(57)]) dev.Write([0, 0, 0, 1, 0] + [x for x in range(57, 100)]) self.assertEqual( dev.Read(), [0, 0, 0, 1, 0x83, 0, 85] + [x for x in range(85, 28, -1)]) self.assertEqual( dev.Read(), [0, 0, 0, 1, 0] + [x for x in range(28, 0, -1)] + [0 for _ in range(31)]) if __name__ == '__main__': unittest.main() fido2-0.8.1/test/_pyu2f/__init__.py0000644000175000017500000000000013317145510016655 0ustar daindain00000000000000fido2-0.8.1/test/__init__.py0000644000175000017500000000000013542675455015471 0ustar daindain00000000000000